From 99a9a0f6fa17cc03fe785b5d10188334f363f3ab Mon Sep 17 00:00:00 2001 From: Simon Stone Date: Wed, 12 Jul 2017 20:24:58 +0100 Subject: [PATCH 1/2] Cannot activate issued identity as identity not found --- packages/composer-runtime/lib/context.js | 11 ++++++----- packages/composer-runtime/test/context.js | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/composer-runtime/lib/context.js b/packages/composer-runtime/lib/context.js index 3e8b264432..08f238dc09 100644 --- a/packages/composer-runtime/lib/context.js +++ b/packages/composer-runtime/lib/context.js @@ -105,7 +105,6 @@ class Context { this.api = null; this.queryExecutor = null; this.identityManager = null; - this.identity = null; this.participant = null; this.transaction = null; this.accessController = null; @@ -290,9 +289,6 @@ class Context { return this.getIdentityManager().getIdentity() .then((identity) => { - // Save the current identity. - this.identity = identity; - // Validate the identity. try { this.getIdentityManager().validateIdentity(identity); @@ -300,7 +296,12 @@ class Context { // Check for the case of activation required, and the user is trying to activate. if (e.activationRequired && this.getFunction() === 'activateIdentity') { - // Ignore. + + // Don't throw the error as we are activating the identity, but return null + // so that the participant is not set because there is no current participant + // until the identity is activated and not revoked. + return null; + } else { throw e; } diff --git a/packages/composer-runtime/test/context.js b/packages/composer-runtime/test/context.js index 60344d2f5a..ac19778530 100644 --- a/packages/composer-runtime/test/context.js +++ b/packages/composer-runtime/test/context.js @@ -310,7 +310,7 @@ describe('Context', () => { error.activationRequired = true; mockIdentityManager.validateIdentity.withArgs(mockIdentity).throws(error); return context.loadCurrentParticipant() - .should.eventually.be.equal(mockParticipant) + .should.eventually.be.null .then(() => { sinon.assert.calledOnce(mockIdentityManager.validateIdentity); sinon.assert.calledWith(mockIdentityManager.validateIdentity, mockIdentity); From afd4028c16650e64540bb461657b9fd134c7efb0 Mon Sep 17 00:00:00 2001 From: Simon Stone Date: Wed, 12 Jul 2017 22:23:25 +0100 Subject: [PATCH 2/2] Get identities working again on embedded and web runtimes --- .../lib/embeddedconnection.js | 109 +++++++++------ .../lib/embeddedsecuritycontext.js | 14 +- .../test/embeddedconnection.js | 113 ++++++++------- .../test/embeddedsecuritycontext.js | 20 ++- .../lib/webconnection.js | 129 +++++++++++------- .../lib/websecuritycontext.js | 14 +- .../test/webconnection.js | 119 +++++++++------- .../test/websecuritycontext.js | 20 ++- .../lib/embeddedcontext.js | 6 +- .../lib/embeddedidentityservice.js | 39 ++++-- .../test/embeddedcontext.js | 21 +-- .../test/embeddedidentityservice.js | 51 ++++++- .../composer-runtime-web/lib/webcontext.js | 6 +- .../lib/webidentityservice.js | 39 ++++-- .../composer-runtime-web/test/webcontext.js | 19 +-- .../test/webidentityservice.js | 42 +++++- 16 files changed, 488 insertions(+), 273 deletions(-) diff --git a/packages/composer-connector-embedded/lib/embeddedconnection.js b/packages/composer-connector-embedded/lib/embeddedconnection.js index cc5f0f541c..ad56eb8d18 100644 --- a/packages/composer-connector-embedded/lib/embeddedconnection.js +++ b/packages/composer-connector-embedded/lib/embeddedconnection.js @@ -15,6 +15,7 @@ 'use strict'; const Connection = require('composer-common').Connection; +const createHash = require('sha.js'); const Engine = require('composer-runtime').Engine; const EmbeddedContainer = require('composer-runtime-embedded').EmbeddedContainer; const EmbeddedContext = require('composer-runtime-embedded').EmbeddedContext; @@ -28,6 +29,9 @@ const businessNetworks = {}; // A mapping of chaincode IDs to their instance objects. const chaincodes = {}; +// The issuer for all identities. +const DEFAULT_ISSUER = createHash('sha256').update('org1').digest('hex'); + /** * Base class representing a connection to a business network. * @protected @@ -148,20 +152,19 @@ class EmbeddedConnection extends Connection { * object representing the logged in participant, or rejected with a login error. */ login(enrollmentID, enrollmentSecret) { - // The 'admin' ID is special for the moment as it is not bound to a participant. - let result = new EmbeddedSecurityContext(this, enrollmentID !== 'admin' ? enrollmentID : null); if (!this.businessNetworkIdentifier) { return this.testIdentity(enrollmentID, enrollmentSecret) - .then(() => { - return result; + .then((identity) => { + return new EmbeddedSecurityContext(this, identity); }); } return this.testIdentity(enrollmentID, enrollmentSecret) - .then(() => { + .then((identity) => { let chaincodeUUID = EmbeddedConnection.getBusinessNetwork(this.businessNetworkIdentifier, this.connectionProfile); if (!chaincodeUUID) { throw new Error(`No chaincode ID found for business network '${this.businessNetworkIdentifier}'`); } + const result = new EmbeddedSecurityContext(this, identity); result.setChaincodeID(chaincodeUUID); return result; }); @@ -177,12 +180,12 @@ class EmbeddedConnection extends Connection { */ deploy(securityContext, businessNetwork, deployOptions) { let container = EmbeddedConnection.createContainer(); - let userID = securityContext.getUserID(); + let identity = securityContext.getIdentity(); let chaincodeUUID = container.getUUID(); let engine = EmbeddedConnection.createEngine(container); EmbeddedConnection.addBusinessNetwork(businessNetwork.getName(), this.connectionProfile, chaincodeUUID); EmbeddedConnection.addChaincode(chaincodeUUID, container, engine); - let context = new EmbeddedContext(engine, userID, this); + let context = new EmbeddedContext(engine, identity, this); return businessNetwork.toArchive({ date: new Date(545184000000) }) .then((businessNetworkArchive) => { const initArgs = {}; @@ -244,10 +247,10 @@ class EmbeddedConnection extends Connection { * chaincode function once it has been invoked, or rejected with an error. */ queryChainCode(securityContext, functionName, args) { - let userID = securityContext.getUserID(); + let identity = securityContext.getIdentity(); let chaincodeUUID = securityContext.getChaincodeID(); let chaincode = EmbeddedConnection.getChaincode(chaincodeUUID); - let context = new EmbeddedContext(chaincode.engine, userID, this); + let context = new EmbeddedContext(chaincode.engine, identity, this); return chaincode.engine.query(context, functionName, args) .then((data) => { return Buffer.from(JSON.stringify(data)); @@ -263,10 +266,10 @@ class EmbeddedConnection extends Connection { * has been invoked, or rejected with an error. */ invokeChainCode(securityContext, functionName, args) { - let userID = securityContext.getUserID(); + let identity = securityContext.getIdentity(); let chaincodeUUID = securityContext.getChaincodeID(); let chaincode = EmbeddedConnection.getChaincode(chaincodeUUID); - let context = new EmbeddedContext(chaincode.engine, userID, this); + let context = new EmbeddedContext(chaincode.engine, identity, this); return chaincode.engine.invoke(context, functionName, args) .then((data) => { return undefined; @@ -275,47 +278,57 @@ class EmbeddedConnection extends Connection { /** * Get the data collection that stores identities. - * @return {DataCollection} The data collection that stores identities. + * @return {Promise} A promise that is resolved with the data collection + * that stores identities. */ getIdentities() { - return this.dataService.existsCollection('identities') - .then((exists) => { - if (exists) { - return this.dataService.getCollection('identities'); - } else { - return this.dataService.createCollection('identities'); - } - }); + return this.dataService.ensureCollection('identities'); } /** - * Test the specified user ID and secret to ensure that it is valid. - * @param {string} userID The user ID. - * @param {string} userSecret The user secret. - * @return {Promise} A promise that is resolved if the user ID and secret - * is valid, or rejected with an error. + * Get the identity for the specified name. + * @param {string} identityName The name for the identity. + * @return {Promise} A promise that is resolved with the identity, or + * rejected with an error. */ - testIdentity(userID, userSecret) { - // The 'admin' ID is special for the moment as it is not bound to a participant. - if (userID === 'admin') { - return Promise.resolve(); + getIdentity(identityName) { + if (identityName === 'admin') { + return Promise.resolve({ + identifier: '', + name: 'admin', + issuer: DEFAULT_ISSUER, + secret: 'adminpw' + }); } return this.getIdentities() .then((identities) => { - return identities.get(userID); - }) + return identities.get(identityName); + }); + } + + /** + * Test the specified identity name and secret to ensure that it is valid. + * @param {string} identityName The name for the identity. + * @param {string} identitySecret The secret for the identity. + * @return {Promise} A promise that is resolved if the user ID and secret + * is valid, or rejected with an error. + */ + testIdentity(identityName, identitySecret) { + return this.getIdentity(identityName) .then((identity) => { - if (identity.userSecret !== userSecret) { - throw new Error(`The user secret ${userSecret} specified for the user ID ${userID} does not match the stored user secret ${identity.userSecret}`); + if (identityName !== 'admin') { + if (identity.secret !== identitySecret) { + throw new Error(`The secret ${identitySecret} specified for the identity ${identityName} does not match the stored secret ${identity.secret}`); + } } + return identity; }); - } /** - * Create a new identity for the specified user ID. + * Create a new identity for the specified name. * @param {SecurityContext} securityContext The participant's security context. - * @param {string} userID The user ID. + * @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. @@ -324,22 +337,32 @@ class EmbeddedConnection extends Connection { * @return {Promise} A promise that is resolved with a generated user * secret once the new identity has been created, or rejected with an error. */ - createIdentity(securityContext, userID, options) { + createIdentity(securityContext, identityName, options) { let identities; return this.getIdentities() .then((identities_) => { identities = identities_; - return identities.exists(userID); + return identities.exists(identityName); }) .then((exists) => { if (exists) { - return identities.get(userID); + return identities.get(identityName); } - const userSecret = uuid.v4().substring(0, 8); - const identity = { userID: userID, userSecret: userSecret }; - return identities.add(userID, identity) + const identifier = createHash('sha256').update(uuid.v4()).digest('hex'); + const secret = uuid.v4().substring(0, 8); + const identity = { + identifier, + name: identityName, + issuer: DEFAULT_ISSUER, + secret, + certificate: '' + }; + return identities.add(identityName, identity) .then(() => { - return identity; + return { + userID: identity.name, + userSecret: identity.secret + }; }); }); } diff --git a/packages/composer-connector-embedded/lib/embeddedsecuritycontext.js b/packages/composer-connector-embedded/lib/embeddedsecuritycontext.js index f89ed9e435..671a1d7c3b 100644 --- a/packages/composer-connector-embedded/lib/embeddedsecuritycontext.js +++ b/packages/composer-connector-embedded/lib/embeddedsecuritycontext.js @@ -24,20 +24,20 @@ class EmbeddedSecurityContext extends SecurityContext { /** * Constructor. * @param {Connection} connection The owning connection. - * @param {String} userID The current user ID. + * @param {Object} identity The current identity. */ - constructor(connection, userID) { + constructor(connection, identity) { super(connection); - this.userID = userID; + this.identity = identity; this.chaincodeID = null; } /** - * Get the current user ID. - * @return {string} The current user ID. + * Get the current identity. + * @return {string} The current identity. */ - getUserID() { - return this.userID; + getIdentity() { + return this.identity; } /** diff --git a/packages/composer-connector-embedded/test/embeddedconnection.js b/packages/composer-connector-embedded/test/embeddedconnection.js index fd69228848..bd3b395791 100644 --- a/packages/composer-connector-embedded/test/embeddedconnection.js +++ b/packages/composer-connector-embedded/test/embeddedconnection.js @@ -35,6 +35,14 @@ require('sinon-as-promised'); describe('EmbeddedConnection', () => { + const identity = { + identifier: 'ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a', + name: 'bob1', + issuer: 'ce295bc0df46512670144b84af55f3d9a3e71b569b1e38baba3f032dc3000665', + secret: 'suchsecret', + certificate: '' + }; + let sandbox; let mockConnectionManager; let mockSecurityContext; @@ -94,25 +102,13 @@ describe('EmbeddedConnection', () => { sandbox.stub(connection, 'testIdentity').resolves(); }); - it('should return a new security context with a null user ID if the admin ID is specified', () => { - connection = new EmbeddedConnection(mockConnectionManager, 'devFabric1'); - sandbox.stub(connection, 'testIdentity').resolves(); - return connection.login('admin', 'suchs3cret') - .then((securityContext) => { - securityContext.should.be.an.instanceOf(EmbeddedSecurityContext); - should.equal(securityContext.getUserID(), null); - should.equal(securityContext.getChaincodeID(), null); - sinon.assert.calledWith(connection.testIdentity, 'admin', 'suchs3cret'); - }); - }); - it('should return a new security context with a null chaincode ID if the business network was not specified', () => { connection = new EmbeddedConnection(mockConnectionManager, 'devFabric1'); - sandbox.stub(connection, 'testIdentity').resolves(); + sandbox.stub(connection, 'testIdentity').resolves(identity); return connection.login('doge', 'suchs3cret') .then((securityContext) => { securityContext.should.be.an.instanceOf(EmbeddedSecurityContext); - securityContext.getUserID().should.equal('doge'); + securityContext.getIdentity().should.deep.equal(identity); should.equal(securityContext.getChaincodeID(), null); sinon.assert.calledWith(connection.testIdentity, 'doge', 'suchs3cret'); }); @@ -143,7 +139,7 @@ describe('EmbeddedConnection', () => { mockBusinessNetwork.getName.returns('testnetwork'); let mockContainer = sinon.createStubInstance(EmbeddedContainer); mockContainer.getUUID.returns('6eeb8858-eced-4a32-b1cd-2491f1e3718f'); - mockSecurityContext.getUserID.returns('bob1'); + mockSecurityContext.getIdentity.returns(identity); sandbox.stub(EmbeddedConnection, 'createContainer').returns(mockContainer); let mockEngine = sinon.createStubInstance(Engine); mockEngine.getContainer.returns(mockContainer); @@ -155,7 +151,7 @@ describe('EmbeddedConnection', () => { sinon.assert.calledOnce(mockEngine.init); sinon.assert.calledWith(mockEngine.init, sinon.match((context) => { context.should.be.an.instanceOf(Context); - context.getIdentityService().getCurrentUserID().should.equal('bob1'); + context.getIdentityService().getIdentifier().should.equal('ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a'); return true; }), 'init', ['aGVsbG8gd29ybGQ=', '{}']); sinon.assert.calledOnce(connection.ping); @@ -245,7 +241,7 @@ describe('EmbeddedConnection', () => { mockEngine.getContainer.returns(mockContainer); EmbeddedConnection.addBusinessNetwork('org.acme.Business', 'devFabric1', '6eeb8858-eced-4a32-b1cd-2491f1e3718f'); EmbeddedConnection.addChaincode('6eeb8858-eced-4a32-b1cd-2491f1e3718f', mockContainer, mockEngine); - mockSecurityContext.getUserID.returns('bob1'); + mockSecurityContext.getIdentity.returns(identity); mockSecurityContext.getChaincodeID.returns('6eeb8858-eced-4a32-b1cd-2491f1e3718f'); mockEngine.query.resolves({ test: 'data from engine' }); return connection.queryChainCode(mockSecurityContext, 'testFunction', ['arg1', 'arg2']) @@ -253,7 +249,7 @@ describe('EmbeddedConnection', () => { sinon.assert.calledOnce(mockEngine.query); sinon.assert.calledWith(mockEngine.query, sinon.match((context) => { context.should.be.an.instanceOf(Context); - context.getIdentityService().getCurrentUserID().should.equal('bob1'); + context.getIdentityService().getIdentifier().should.equal('ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a'); return true; }), 'testFunction', ['arg1', 'arg2']); result.should.be.an.instanceOf(Buffer); @@ -271,7 +267,7 @@ describe('EmbeddedConnection', () => { mockEngine.getContainer.returns(mockContainer); EmbeddedConnection.addBusinessNetwork('org.acme.Business', 'devFabric1', '6eeb8858-eced-4a32-b1cd-2491f1e3718f'); EmbeddedConnection.addChaincode('6eeb8858-eced-4a32-b1cd-2491f1e3718f', mockContainer, mockEngine); - mockSecurityContext.getUserID.returns('bob1'); + mockSecurityContext.getIdentity.returns(identity); mockSecurityContext.getChaincodeID.returns('6eeb8858-eced-4a32-b1cd-2491f1e3718f'); mockEngine.invoke.resolves({ test: 'data from engine' }); return connection.invokeChainCode(mockSecurityContext, 'testFunction', ['arg1', 'arg2']) @@ -279,7 +275,7 @@ describe('EmbeddedConnection', () => { sinon.assert.calledOnce(mockEngine.invoke); sinon.assert.calledWith(mockEngine.invoke, sinon.match((context) => { context.should.be.an.instanceOf(Context); - context.getIdentityService().getCurrentUserID().should.equal('bob1'); + context.getIdentityService().getIdentifier().should.equal('ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a'); return true; }), 'testFunction', ['arg1', 'arg2']); should.equal(result, undefined); @@ -296,21 +292,10 @@ describe('EmbeddedConnection', () => { beforeEach(() => { connection.dataService = mockDataService = sinon.createStubInstance(DataService); mockIdentitiesDataCollection = sinon.createStubInstance(DataCollection); - }); - it('should create and return the identities collection if it does not exist', () => { - mockDataService.existsCollection.withArgs('identities').resolves(false); - mockDataService.createCollection.withArgs('identities').resolves(mockIdentitiesDataCollection); - return connection.getIdentities() - .then((identities) => { - identities.should.equal(mockIdentitiesDataCollection); - }); - }); - - it('should return the existing identities collection if it already exists', () => { - mockDataService.existsCollection.withArgs('identities').resolves(true); - mockDataService.getCollection.withArgs('identities').resolves(mockIdentitiesDataCollection); + it('should ensure and return the identities collection', () => { + mockDataService.ensureCollection.withArgs('identities').resolves(mockIdentitiesDataCollection); return connection.getIdentities() .then((identities) => { identities.should.equal(mockIdentitiesDataCollection); @@ -319,34 +304,57 @@ describe('EmbeddedConnection', () => { }); - describe('#testIdentity', () => { + describe('#getIdentity', () => { let mockIdentitiesDataCollection; beforeEach(() => { mockIdentitiesDataCollection = sinon.createStubInstance(DataCollection); - sandbox.stub(connection, 'getIdentities').resolves(mockIdentitiesDataCollection); + sinon.stub(connection, 'getIdentities').resolves(mockIdentitiesDataCollection); }); - it('should resolve if the user ID is admin', () => { - return connection.testIdentity('admin', 'password'); + it('should return the hardcoded admin identity', () => { + return connection.getIdentity('admin') + .should.eventually.be.deep.equal({ + identifier: '', + name: 'admin', + issuer: '89e0c13fa652f52d91fc90d568b70070d6ed1a59c5d9f452dfb1b2a199b1928e', + secret: 'adminpw' + }); }); - it('should resolve if the user ID exists', () => { - mockIdentitiesDataCollection.get.withArgs('doge').resolves({ userID: 'doge', userSecret: 'password' }); - return connection.testIdentity('doge', 'password'); + it('should return the specified identity', () => { + mockIdentitiesDataCollection.get.withArgs('bob1').resolves(identity); + return connection.getIdentity('bob1') + .should.eventually.be.equal(identity); + }); + + }); + + describe('#testIdentity', () => { + + it('should not check the secret if the name is admin', () => { + const identity = { + identifier: '', + name: 'admin', + issuer: '89e0c13fa652f52d91fc90d568b70070d6ed1a59c5d9f452dfb1b2a199b1928e', + secret: 'adminpw' + }; + sinon.stub(connection, 'getIdentity').resolves(identity); + return connection.testIdentity('admin', 'blahblah') + .should.eventually.be.equal(identity); }); - it('should throw an error if the user ID does not exist', () => { - mockIdentitiesDataCollection.get.withArgs('doge').rejects(new Error('such error')); - return connection.testIdentity('doge', 'password') - .should.be.rejectedWith(/such error/); + it('should throw if the secret does not match', () => { + sinon.stub(connection, 'getIdentity').resolves(identity); + return connection.testIdentity('bob1', 'blahblah') + .should.be.rejectedWith(/The secret blahblah specified for the identity bob1 does not match the stored secret suchsecret/); }); - it('should throw an error if the user secret does not match', () => { - mockIdentitiesDataCollection.get.withArgs('doge').resolves({ userID: 'doge', userSecret: 'not correct' }); - return connection.testIdentity('doge', 'password') - .should.be.rejectedWith(/ does not match/); + it('should not throw if the secret does match', () => { + sinon.stub(connection, 'getIdentity').resolves(identity); + return connection.testIdentity('bob1', 'suchsecret') + .should.eventually.be.equal(identity); }); }); @@ -365,6 +373,7 @@ describe('EmbeddedConnection', () => { mockIdentitiesDataCollection.get.withArgs('doge').resolves({ userID: 'doge', userSecret: 'password' }); return connection.createIdentity(mockSecurityContext, 'doge') .then((result) => { + sinon.assert.notCalled(mockIdentitiesDataCollection.add); result.should.be.deep.equal({ userID: 'doge', userSecret: 'password' }); }); }); @@ -375,6 +384,14 @@ describe('EmbeddedConnection', () => { mockIdentitiesDataCollection.add.withArgs('doge').resolves(); return connection.createIdentity(mockSecurityContext, 'doge') .then((result) => { + sinon.assert.calledOnce(mockIdentitiesDataCollection.add); + sinon.assert.calledWith(mockIdentitiesDataCollection.add, 'doge', { + certificate: '', + identifier: '8f00d1b8319abc0ad87ccb6c1baae0a54c406c921c01e1ed165c33b93f3e5b6a', + issuer: '89e0c13fa652f52d91fc90d568b70070d6ed1a59c5d9f452dfb1b2a199b1928e', + name: 'doge', + secret: 'f892c30a' + }); result.should.be.deep.equal({ userID: 'doge', userSecret: 'f892c30a' }); }); }); diff --git a/packages/composer-connector-embedded/test/embeddedsecuritycontext.js b/packages/composer-connector-embedded/test/embeddedsecuritycontext.js index b89899e4d0..83a0b704df 100644 --- a/packages/composer-connector-embedded/test/embeddedsecuritycontext.js +++ b/packages/composer-connector-embedded/test/embeddedsecuritycontext.js @@ -23,27 +23,35 @@ const sinon = require('sinon'); describe('EmbeddedSecurityContext', () => { + const identity = { + identifier: 'ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a', + name: 'bob1', + issuer: 'ce295bc0df46512670144b84af55f3d9a3e71b569b1e38baba3f032dc3000665', + secret: 'suchsecret', + certificate: '' + }; + let mockConnection; + let securityContext; beforeEach(() => { mockConnection = sinon.createStubInstance(Connection); + securityContext = new EmbeddedSecurityContext(mockConnection, identity); }); describe('#constructor', () => { it('should construct a new security context', () => { - let securityContext = new EmbeddedSecurityContext(mockConnection, 'bob1'); securityContext.should.be.an.instanceOf(SecurityContext); - securityContext.userID.should.equal('bob1'); + securityContext.identity.should.equal(identity); }); }); - describe('#getUserID', () => { + describe('#getIdentity', () => { it('should get the current user ID', () => { - let securityContext = new EmbeddedSecurityContext(mockConnection, 'bob1'); - securityContext.getUserID().should.equal('bob1'); + securityContext.getIdentity().should.deep.equal(identity); }); }); @@ -51,7 +59,6 @@ describe('EmbeddedSecurityContext', () => { describe('#getChaincodeID', () => { it('should get the chaincode ID', () => { - let securityContext = new EmbeddedSecurityContext(mockConnection, 'bob1'); securityContext.chaincodeID = 'ed916d6a-21af-4a2a-a9be-a86f69aa641b'; securityContext.getChaincodeID().should.equal('ed916d6a-21af-4a2a-a9be-a86f69aa641b'); }); @@ -61,7 +68,6 @@ describe('EmbeddedSecurityContext', () => { describe('#setChaincodeID', () => { it('should set the chaincode ID', () => { - let securityContext = new EmbeddedSecurityContext(mockConnection, 'bob1'); securityContext.setChaincodeID('ed916d6a-21af-4a2a-a9be-a86f69aa641b'); securityContext.chaincodeID.should.equal('ed916d6a-21af-4a2a-a9be-a86f69aa641b'); }); diff --git a/packages/composer-connector-web/lib/webconnection.js b/packages/composer-connector-web/lib/webconnection.js index c62debdf06..86c241f936 100644 --- a/packages/composer-connector-web/lib/webconnection.js +++ b/packages/composer-connector-web/lib/webconnection.js @@ -14,8 +14,8 @@ 'use strict'; -// const Resource = require('composer-common').Resource; const Connection = require('composer-common').Connection; +const createHash = require('sha.js'); const Engine = require('composer-runtime').Engine; const uuid = require('uuid'); const WebContainer = require('composer-runtime-web').WebContainer; @@ -29,6 +29,9 @@ const businessNetworks = {}; // A mapping of chaincode IDs to their instance objects. const chaincodes = {}; +// The issuer for all identities. +const DEFAULT_ISSUER = createHash('sha256').update('org1').digest('hex'); + /** * Base class representing a connection to a business network. * @protected @@ -150,30 +153,30 @@ class WebConnection extends Connection { * object representing the logged in participant, or rejected with a login error. */ login(enrollmentID, enrollmentSecret) { - // The 'admin' ID is special for the moment as it is not bound to a participant. - let result = new WebSecurityContext(this, enrollmentID !== 'admin' ? enrollmentID : null); if (!this.businessNetworkIdentifier) { return this.testIdentity(enrollmentID, enrollmentSecret) - .then(() => { - return result; + .then((identity) => { + return new WebSecurityContext(this, identity); }); } + let identity; return this.testIdentity(enrollmentID, enrollmentSecret) - .then(() => { + .then((identity_) => { + identity = identity_; return this.getChaincodeID(this.businessNetworkIdentifier); }) .then((chaincodeID) => { - if (chaincodeID) { - if (!WebConnection.getBusinessNetwork(this.businessNetworkIdentifier, this.connectionProfile)) { - let container = WebConnection.createContainer(chaincodeID); - let engine = WebConnection.createEngine(container); - WebConnection.addBusinessNetwork(this.businessNetworkIdentifier, this.connectionProfile, chaincodeID); - WebConnection.addChaincode(chaincodeID, container, engine); - } - result.setChaincodeID(chaincodeID); - } else { + if (!chaincodeID) { throw new Error(`No chaincode ID found for business network '${this.businessNetworkIdentifier}'`); } + if (!WebConnection.getBusinessNetwork(this.businessNetworkIdentifier, this.connectionProfile)) { + let container = WebConnection.createContainer(chaincodeID); + let engine = WebConnection.createEngine(container); + WebConnection.addBusinessNetwork(this.businessNetworkIdentifier, this.connectionProfile, chaincodeID); + WebConnection.addChaincode(chaincodeID, container, engine); + } + const result = new WebSecurityContext(this, identity); + result.setChaincodeID(chaincodeID); return result; }); } @@ -188,12 +191,12 @@ class WebConnection extends Connection { */ deploy(securityContext, businessNetwork, deployOptions) { let container = WebConnection.createContainer(); - let userID = securityContext.getUserID(); + let identity = securityContext.getIdentity(); let chaincodeID = container.getUUID(); let engine = WebConnection.createEngine(container); WebConnection.addBusinessNetwork(businessNetwork.getName(), this.connectionProfile, chaincodeID); WebConnection.addChaincode(chaincodeID, container, engine); - let context = new WebContext(engine, userID, this); + let context = new WebContext(engine, identity, this); return businessNetwork.toArchive() .then((businessNetworkArchive) => { const initArgs = {}; @@ -258,10 +261,10 @@ class WebConnection extends Connection { * chaincode function once it has been invoked, or rejected with an error. */ queryChainCode(securityContext, functionName, args) { - let userID = securityContext.getUserID(); + let identity = securityContext.getIdentity(); let chaincodeID = securityContext.getChaincodeID(); let chaincode = WebConnection.getChaincode(chaincodeID); - let context = new WebContext(chaincode.engine, userID, this); + let context = new WebContext(chaincode.engine, identity, this); return chaincode.engine.query(context, functionName, args) .then((data) => { return Buffer.from(JSON.stringify(data)); @@ -277,10 +280,10 @@ class WebConnection extends Connection { * has been invoked, or rejected with an error. */ invokeChainCode(securityContext, functionName, args) { - let userID = securityContext.getUserID(); + let identity = securityContext.getIdentity(); let chaincodeID = securityContext.getChaincodeID(); let chaincode = WebConnection.getChaincode(chaincodeID); - let context = new WebContext(chaincode.engine, userID, this); + let context = new WebContext(chaincode.engine, identity, this); return chaincode.engine.invoke(context, functionName, args) .then((data) => { return undefined; @@ -289,47 +292,57 @@ class WebConnection extends Connection { /** * Get the data collection that stores identities. - * @return {DataCollection} The data collection that stores identities. + * @return {Promise} A promise that is resolved with the data collection + * that stores identities. */ getIdentities() { - return this.dataService.existsCollection('identities') - .then((exists) => { - if (exists) { - return this.dataService.getCollection('identities'); - } else { - return this.dataService.createCollection('identities'); - } - }); + return this.dataService.ensureCollection('identities'); } /** - * Test the specified user ID and secret to ensure that it is valid. - * @param {string} userID The user ID. - * @param {string} userSecret The user secret. - * @return {Promise} A promise that is resolved if the user ID and secret - * is valid, or rejected with an error. + * Get the identity for the specified name. + * @param {string} identityName The name for the identity. + * @return {Promise} A promise that is resolved with the identity, or + * rejected with an error. */ - testIdentity(userID, userSecret) { - // The 'admin' ID is special for the moment as it is not bound to a participant. - if (userID === 'admin') { - return Promise.resolve(); + getIdentity(identityName) { + if (identityName === 'admin') { + return Promise.resolve({ + identifier: '', + name: 'admin', + issuer: DEFAULT_ISSUER, + secret: 'adminpw' + }); } return this.getIdentities() .then((identities) => { - return identities.get(userID); - }) + return identities.get(identityName); + }); + } + + /** + * Test the specified identity name and secret to ensure that it is valid. + * @param {string} identityName The name for the identity. + * @param {string} identitySecret The secret for the identity. + * @return {Promise} A promise that is resolved if the user ID and secret + * is valid, or rejected with an error. + */ + testIdentity(identityName, identitySecret) { + return this.getIdentity(identityName) .then((identity) => { - if (identity.userSecret !== userSecret) { - throw new Error(`The user secret ${userSecret} specified for the user ID ${userID} does not match the stored user secret ${identity.userSecret}`); + if (identityName !== 'admin') { + if (identity.secret !== identitySecret) { + throw new Error(`The secret ${identitySecret} specified for the identity ${identityName} does not match the stored secret ${identity.secret}`); + } } + return identity; }); - } /** - * Create a new identity for the specified user ID. + * Create a new identity for the specified name. * @param {SecurityContext} securityContext The participant's security context. - * @param {string} userID The user ID. + * @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. @@ -338,22 +351,32 @@ class WebConnection extends Connection { * @return {Promise} A promise that is resolved with a generated user * secret once the new identity has been created, or rejected with an error. */ - createIdentity(securityContext, userID, options) { + createIdentity(securityContext, identityName, options) { let identities; return this.getIdentities() .then((identities_) => { identities = identities_; - return identities.exists(userID); + return identities.exists(identityName); }) .then((exists) => { if (exists) { - return identities.get(userID); + return identities.get(identityName); } - const userSecret = uuid.v4().substring(0, 8); - const identity = { userID: userID, userSecret: userSecret }; - return identities.add(userID, identity) + const identifier = createHash('sha256').update(uuid.v4()).digest('hex'); + const secret = uuid.v4().substring(0, 8); + const identity = { + identifier, + name: identityName, + issuer: DEFAULT_ISSUER, + secret, + certificate: '' + }; + return identities.add(identityName, identity) .then(() => { - return identity; + return { + userID: identity.name, + userSecret: identity.secret + }; }); }); } diff --git a/packages/composer-connector-web/lib/websecuritycontext.js b/packages/composer-connector-web/lib/websecuritycontext.js index 9681ede74b..e67195fe94 100644 --- a/packages/composer-connector-web/lib/websecuritycontext.js +++ b/packages/composer-connector-web/lib/websecuritycontext.js @@ -24,20 +24,20 @@ class WebSecurityContext extends SecurityContext { /** * Constructor. * @param {Connection} connection The owning connection. - * @param {String} userID The current user ID. + * @param {Object} identity The current identity. */ - constructor(connection, userID) { + constructor(connection, identity) { super(connection); - this.userID = userID; + this.identity = identity; this.chaincodeID = null; } /** - * Get the current user ID. - * @return {string} The current user ID. + * Get the current identity. + * @return {string} The current identity. */ - getUserID() { - return this.userID; + getIdentity() { + return this.identity; } /** diff --git a/packages/composer-connector-web/test/webconnection.js b/packages/composer-connector-web/test/webconnection.js index 1b1eb11dae..f1ffd4f9a2 100644 --- a/packages/composer-connector-web/test/webconnection.js +++ b/packages/composer-connector-web/test/webconnection.js @@ -37,6 +37,14 @@ require('sinon-as-promised'); describe('WebConnection', () => { + const identity = { + identifier: 'ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a', + name: 'bob1', + issuer: 'ce295bc0df46512670144b84af55f3d9a3e71b569b1e38baba3f032dc3000665', + secret: 'suchsecret', + certificate: '' + }; + let sandbox; let mockConnectionManager; let mockConnectionProfileManager; @@ -107,25 +115,13 @@ describe('WebConnection', () => { sandbox.stub(connection, 'testIdentity').resolves(); }); - it('should return a new security context with a null user ID if the admin ID is specified', () => { - connection = new WebConnection(mockConnectionManager, 'devFabric1'); - sandbox.stub(connection, 'testIdentity').resolves(); - return connection.login('admin', 'suchs3cret') - .then((securityContext) => { - securityContext.should.be.an.instanceOf(WebSecurityContext); - should.equal(securityContext.getUserID(), null); - should.equal(securityContext.getChaincodeID(), null); - sinon.assert.calledWith(connection.testIdentity, 'admin', 'suchs3cret'); - }); - }); - it('should return a new security context with a null chaincode ID if the business network was not specified', () => { connection = new WebConnection(mockConnectionManager, 'devFabric1'); - sandbox.stub(connection, 'testIdentity').resolves(); + sandbox.stub(connection, 'testIdentity').resolves(identity); return connection.login('doge', 'suchs3cret') .then((securityContext) => { securityContext.should.be.an.instanceOf(WebSecurityContext); - securityContext.getUserID().should.equal('doge'); + securityContext.getIdentity().should.deep.equal(identity); should.equal(securityContext.getChaincodeID(), null); sinon.assert.calledWith(connection.testIdentity, 'doge', 'suchs3cret'); }); @@ -175,7 +171,7 @@ describe('WebConnection', () => { mockBusinessNetwork.getName.returns('testnetwork'); let mockContainer = sinon.createStubInstance(WebContainer); mockContainer.getUUID.returns('133c00a3-8555-4aa5-9165-9de9a8f8a838'); - mockSecurityContext.getUserID.returns('bob1'); + mockSecurityContext.getIdentity.returns(identity); sandbox.stub(WebConnection, 'createContainer').returns(mockContainer); let mockEngine = sinon.createStubInstance(Engine); mockEngine.getContainer.returns(mockContainer); @@ -187,7 +183,7 @@ describe('WebConnection', () => { sinon.assert.calledOnce(mockEngine.init); sinon.assert.calledWith(mockEngine.init, sinon.match((context) => { context.should.be.an.instanceOf(Context); - context.getIdentityService().getCurrentUserID().should.equal('bob1'); + context.getIdentityService().getIdentifier().should.equal('ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a'); return true; }), 'init', ['aGVsbG8gd29ybGQ=', '{}']); sinon.assert.calledOnce(connection.ping); @@ -277,7 +273,7 @@ describe('WebConnection', () => { mockEngine.getContainer.returns(mockContainer); WebConnection.addBusinessNetwork('org.acme.Business', 'devFabric1', '6eeb8858-eced-4a32-b1cd-2491f1e3718f'); WebConnection.addChaincode('6eeb8858-eced-4a32-b1cd-2491f1e3718f', mockContainer, mockEngine); - mockSecurityContext.getUserID.returns('bob1'); + mockSecurityContext.getIdentity.returns(identity); mockSecurityContext.getChaincodeID.returns('6eeb8858-eced-4a32-b1cd-2491f1e3718f'); mockEngine.query.resolves({ test: 'data from engine' }); return connection.queryChainCode(mockSecurityContext, 'testFunction', ['arg1', 'arg2']) @@ -285,7 +281,7 @@ describe('WebConnection', () => { sinon.assert.calledOnce(mockEngine.query); sinon.assert.calledWith(mockEngine.query, sinon.match((context) => { context.should.be.an.instanceOf(Context); - context.getIdentityService().getCurrentUserID().should.equal('bob1'); + context.getIdentityService().getIdentifier().should.equal('ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a'); return true; }), 'testFunction', ['arg1', 'arg2']); result.should.be.an.instanceOf(Buffer); @@ -303,7 +299,7 @@ describe('WebConnection', () => { mockEngine.getContainer.returns(mockContainer); WebConnection.addBusinessNetwork('org.acme.Business', 'devFabric1', '6eeb8858-eced-4a32-b1cd-2491f1e3718f'); WebConnection.addChaincode('6eeb8858-eced-4a32-b1cd-2491f1e3718f', mockContainer, mockEngine); - mockSecurityContext.getUserID.returns('bob1'); + mockSecurityContext.getIdentity.returns(identity); mockSecurityContext.getChaincodeID.returns('6eeb8858-eced-4a32-b1cd-2491f1e3718f'); mockEngine.invoke.resolves({ test: 'data from engine' }); return connection.invokeChainCode(mockSecurityContext, 'testFunction', ['arg1', 'arg2']) @@ -311,7 +307,7 @@ describe('WebConnection', () => { sinon.assert.calledOnce(mockEngine.invoke); sinon.assert.calledWith(mockEngine.invoke, sinon.match((context) => { context.should.be.an.instanceOf(Context); - context.getIdentityService().getCurrentUserID().should.equal('bob1'); + context.getIdentityService().getIdentifier().should.equal('ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a'); return true; }), 'testFunction', ['arg1', 'arg2']); should.equal(result, undefined); @@ -328,57 +324,69 @@ describe('WebConnection', () => { beforeEach(() => { connection.dataService = mockDataService = sinon.createStubInstance(DataService); mockIdentitiesDataCollection = sinon.createStubInstance(DataCollection); - - }); - - it('should create and return the identities collection if it does not exist', () => { - mockDataService.existsCollection.withArgs('identities').resolves(false); - mockDataService.createCollection.withArgs('identities').resolves(mockIdentitiesDataCollection); - return connection.getIdentities() - .then((identities) => { - identities.should.equal(mockIdentitiesDataCollection); - }); }); - it('should return the existing identities collection if it already exists', () => { - mockDataService.existsCollection.withArgs('identities').resolves(true); - mockDataService.getCollection.withArgs('identities').resolves(mockIdentitiesDataCollection); + it('should ensure and return the identities collection', () => { + mockDataService.ensureCollection.withArgs('identities').resolves(mockIdentitiesDataCollection); return connection.getIdentities() - .then((identities) => { - identities.should.equal(mockIdentitiesDataCollection); - }); + .then((identities) => { + identities.should.equal(mockIdentitiesDataCollection); + }); }); }); - describe('#testIdentity', () => { + describe('#getIdentity', () => { let mockIdentitiesDataCollection; beforeEach(() => { mockIdentitiesDataCollection = sinon.createStubInstance(DataCollection); - sandbox.stub(connection, 'getIdentities').resolves(mockIdentitiesDataCollection); + sinon.stub(connection, 'getIdentities').resolves(mockIdentitiesDataCollection); }); - it('should resolve if the user ID is admin', () => { - return connection.testIdentity('admin', 'password'); + it('should return the hardcoded admin identity', () => { + return connection.getIdentity('admin') + .should.eventually.be.deep.equal({ + identifier: '', + name: 'admin', + issuer: '89e0c13fa652f52d91fc90d568b70070d6ed1a59c5d9f452dfb1b2a199b1928e', + secret: 'adminpw' + }); }); - it('should resolve if the user ID exists', () => { - mockIdentitiesDataCollection.get.withArgs('doge').resolves({ userID: 'doge', userSecret: 'password' }); - return connection.testIdentity('doge', 'password'); + it('should return the specified identity', () => { + mockIdentitiesDataCollection.get.withArgs('bob1').resolves(identity); + return connection.getIdentity('bob1') + .should.eventually.be.equal(identity); }); - it('should throw an error if the user ID does not exist', () => { - mockIdentitiesDataCollection.get.withArgs('doge').rejects(new Error('such error')); - return connection.testIdentity('doge', 'password') - .should.be.rejectedWith(/such error/); + }); + + describe('#testIdentity', () => { + + it('should not check the secret if the name is admin', () => { + const identity = { + identifier: '', + name: 'admin', + issuer: '89e0c13fa652f52d91fc90d568b70070d6ed1a59c5d9f452dfb1b2a199b1928e', + secret: 'adminpw' + }; + sinon.stub(connection, 'getIdentity').resolves(identity); + return connection.testIdentity('admin', 'blahblah') + .should.eventually.be.equal(identity); }); - it('should throw an error if the user secret does not match', () => { - mockIdentitiesDataCollection.get.withArgs('doge').resolves({ userID: 'doge', userSecret: 'not correct' }); - return connection.testIdentity('doge', 'password') - .should.be.rejectedWith(/ does not match/); + it('should throw if the secret does not match', () => { + sinon.stub(connection, 'getIdentity').resolves(identity); + return connection.testIdentity('bob1', 'blahblah') + .should.be.rejectedWith(/The secret blahblah specified for the identity bob1 does not match the stored secret suchsecret/); + }); + + it('should not throw if the secret does match', () => { + sinon.stub(connection, 'getIdentity').resolves(identity); + return connection.testIdentity('bob1', 'suchsecret') + .should.eventually.be.equal(identity); }); }); @@ -397,6 +405,7 @@ describe('WebConnection', () => { mockIdentitiesDataCollection.get.withArgs('doge').resolves({ userID: 'doge', userSecret: 'password' }); return connection.createIdentity(mockSecurityContext, 'doge') .then((result) => { + sinon.assert.notCalled(mockIdentitiesDataCollection.add); result.should.be.deep.equal({ userID: 'doge', userSecret: 'password' }); }); }); @@ -407,6 +416,14 @@ describe('WebConnection', () => { mockIdentitiesDataCollection.add.withArgs('doge').resolves(); return connection.createIdentity(mockSecurityContext, 'doge') .then((result) => { + sinon.assert.calledOnce(mockIdentitiesDataCollection.add); + sinon.assert.calledWith(mockIdentitiesDataCollection.add, 'doge', { + certificate: '', + identifier: '8f00d1b8319abc0ad87ccb6c1baae0a54c406c921c01e1ed165c33b93f3e5b6a', + issuer: '89e0c13fa652f52d91fc90d568b70070d6ed1a59c5d9f452dfb1b2a199b1928e', + name: 'doge', + secret: 'f892c30a' + }); result.should.be.deep.equal({ userID: 'doge', userSecret: 'f892c30a' }); }); }); diff --git a/packages/composer-connector-web/test/websecuritycontext.js b/packages/composer-connector-web/test/websecuritycontext.js index e08155c7d9..68c6256c50 100644 --- a/packages/composer-connector-web/test/websecuritycontext.js +++ b/packages/composer-connector-web/test/websecuritycontext.js @@ -23,27 +23,35 @@ const sinon = require('sinon'); describe('WebSecurityContext', () => { + const identity = { + identifier: 'ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a', + name: 'bob1', + issuer: 'ce295bc0df46512670144b84af55f3d9a3e71b569b1e38baba3f032dc3000665', + secret: 'suchsecret', + certificate: '' + }; + let mockConnection; + let securityContext; beforeEach(() => { mockConnection = sinon.createStubInstance(Connection); + securityContext = new WebSecurityContext(mockConnection, identity); }); describe('#constructor', () => { it('should construct a new security context', () => { - let securityContext = new WebSecurityContext(mockConnection, 'bob1'); securityContext.should.be.an.instanceOf(SecurityContext); - securityContext.userID.should.equal('bob1'); + securityContext.identity.should.equal(identity); }); }); - describe('#getUserID', () => { + describe('#getIdentity', () => { it('should get the current user ID', () => { - let securityContext = new WebSecurityContext(mockConnection, 'bob1'); - securityContext.getUserID().should.equal('bob1'); + securityContext.getIdentity().should.deep.equal(identity); }); }); @@ -51,7 +59,6 @@ describe('WebSecurityContext', () => { describe('#getChaincodeID', () => { it('should get the chaincode ID', () => { - let securityContext = new WebSecurityContext(mockConnection, 'bob1'); securityContext.chaincodeID = 'ed916d6a-21af-4a2a-a9be-a86f69aa641b'; securityContext.getChaincodeID().should.equal('ed916d6a-21af-4a2a-a9be-a86f69aa641b'); }); @@ -61,7 +68,6 @@ describe('WebSecurityContext', () => { describe('#setChaincodeID', () => { it('should set the chaincode ID', () => { - let securityContext = new WebSecurityContext(mockConnection, 'bob1'); securityContext.setChaincodeID('ed916d6a-21af-4a2a-a9be-a86f69aa641b'); securityContext.chaincodeID.should.equal('ed916d6a-21af-4a2a-a9be-a86f69aa641b'); }); diff --git a/packages/composer-runtime-embedded/lib/embeddedcontext.js b/packages/composer-runtime-embedded/lib/embeddedcontext.js index 12347cd70e..d690148a1e 100644 --- a/packages/composer-runtime-embedded/lib/embeddedcontext.js +++ b/packages/composer-runtime-embedded/lib/embeddedcontext.js @@ -30,13 +30,13 @@ class EmbeddedContext extends Context { /** * Constructor. * @param {Engine} engine The owning engine. - * @param {String} userID The current user ID. + * @param {Object} identity The current identity. * @param {EventEmitter} eventSink The event emitter */ - constructor(engine, userID, eventSink) { + constructor(engine, identity, eventSink) { super(engine); this.dataService = new EmbeddedDataService(engine.getContainer().getUUID()); - this.identityService = new EmbeddedIdentityService(userID); + this.identityService = new EmbeddedIdentityService(identity); this.eventSink = eventSink; } diff --git a/packages/composer-runtime-embedded/lib/embeddedidentityservice.js b/packages/composer-runtime-embedded/lib/embeddedidentityservice.js index 0cff6accca..b963d9e275 100644 --- a/packages/composer-runtime-embedded/lib/embeddedidentityservice.js +++ b/packages/composer-runtime-embedded/lib/embeddedidentityservice.js @@ -24,20 +24,43 @@ class EmbeddedIdentityService extends IdentityService { /** * Constructor. - * @param {String} userID The current user ID. + * @param {String} identity The current identity. */ - constructor(userID) { + constructor(identity) { super(); - this.userID = userID; + this.identity = identity; } /** - * Retrieve the current user ID. - * @return {string} The current user ID, or null if the current user ID cannot - * be determined or has not been specified. + * Get a unique identifier for the identity used to submit the transaction. + * @return {string} A unique identifier for the identity used to submit the transaction. */ - getCurrentUserID() { - return this.userID; + getIdentifier() { + return this.identity.identifier; + } + + /** + * Get the name of the identity used to submit the transaction. + * @return {string} The name of the identity used to submit the transaction. + */ + getName() { + return this.identity.name; + } + + /** + * Get the issuer of the identity used to submit the transaction. + * @return {string} The issuer of the identity used to submit the transaction. + */ + getIssuer() { + return this.identity.issuer; + } + + /** + * Get the certificate for the identity used to submit the transaction. + * @return {string} The certificate for the identity used to submit the transaction. + */ + getCertificate() { + return this.identity.certificate; } } diff --git a/packages/composer-runtime-embedded/test/embeddedcontext.js b/packages/composer-runtime-embedded/test/embeddedcontext.js index 4a2081eda8..62663100e5 100644 --- a/packages/composer-runtime-embedded/test/embeddedcontext.js +++ b/packages/composer-runtime-embedded/test/embeddedcontext.js @@ -30,9 +30,18 @@ const sinon = require('sinon'); describe('EmbeddedContext', () => { + const identity = { + identifier: 'ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a', + name: 'bob1', + issuer: 'ce295bc0df46512670144b84af55f3d9a3e71b569b1e38baba3f032dc3000665', + secret: 'suchsecret', + certificate: '' + }; + let mockEmbeddedContainer; let mockSerializer; let mockEngine; + let context; beforeEach(() => { mockEmbeddedContainer = sinon.createStubInstance(EmbeddedContainer); @@ -40,12 +49,12 @@ describe('EmbeddedContext', () => { mockEngine = sinon.createStubInstance(Engine); mockEngine.getContainer.returns(mockEmbeddedContainer); mockSerializer = sinon.createStubInstance(Serializer); + context = new EmbeddedContext(mockEngine, identity); }); describe('#constructor', () => { it('should construct a new context', () => { - let context = new EmbeddedContext(mockEngine, 'bob1'); context.should.be.an.instanceOf(Context); }); @@ -54,7 +63,6 @@ describe('EmbeddedContext', () => { describe('#getDataService', () => { it('should return the container data service', () => { - let context = new EmbeddedContext(mockEngine, 'bob1'); context.getDataService().should.be.an.instanceOf(EmbeddedDataService); }); @@ -63,9 +71,8 @@ describe('EmbeddedContext', () => { describe('#getIdentityService', () => { it('should return the container identity service', () => { - let context = new EmbeddedContext(mockEngine, 'bob1'); context.getIdentityService().should.be.an.instanceOf(EmbeddedIdentityService); - context.getIdentityService().getCurrentUserID().should.equal('bob1'); + context.getIdentityService().getIdentifier().should.equal('ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a'); }); }); @@ -73,14 +80,12 @@ describe('EmbeddedContext', () => { describe('#getEventService', () => { it('should return the container event service', () => { - let context = new EmbeddedContext(mockEngine, 'bob1'); context.getSerializer = sinon.stub().returns(mockSerializer); context.getEventService().should.be.an.instanceOf(EmbeddedEventService); }); it('should return the container event service if it is set', () => { const mockEmbeddedEventService = sinon.createStubInstance(EmbeddedEventService); - let context = new EmbeddedContext(mockEngine, 'bob1'); context.eventService = mockEmbeddedEventService; context.getEventService().should.equal(mockEmbeddedEventService); }); @@ -89,13 +94,11 @@ describe('EmbeddedContext', () => { describe('#getHTTPService', () => { it('should return the container HTTP service', () => { - let context = new EmbeddedContext(mockEngine, 'bob1'); context.getHTTPService().should.be.an.instanceOf(EmbeddedHTTPService); }); it('should return the container HTTP service if it is set', () => { const mockEmbeddedHTTPService = sinon.createStubInstance(EmbeddedHTTPService); - let context = new EmbeddedContext(mockEngine, 'bob1'); context.httpService = mockEmbeddedHTTPService; context.getHTTPService().should.equal(mockEmbeddedHTTPService); }); @@ -104,13 +107,11 @@ describe('EmbeddedContext', () => { describe('#getScriptCompiler', () => { it('should return the container script compiler', () => { - let context = new EmbeddedContext(mockEngine, 'bob1'); context.getScriptCompiler().should.be.an.instanceOf(EmbeddedScriptCompiler); }); it('should return the container script compiler if it is set', () => { const mockEmbeddedScriptCompiler = sinon.createStubInstance(EmbeddedScriptCompiler); - const context = new EmbeddedContext(mockEngine, 'bob1'); context.scriptCompiler = mockEmbeddedScriptCompiler; context.getScriptCompiler().should.equal(mockEmbeddedScriptCompiler); }); diff --git a/packages/composer-runtime-embedded/test/embeddedidentityservice.js b/packages/composer-runtime-embedded/test/embeddedidentityservice.js index 6476eaeb3f..37cc3ef33a 100644 --- a/packages/composer-runtime-embedded/test/embeddedidentityservice.js +++ b/packages/composer-runtime-embedded/test/embeddedidentityservice.js @@ -22,11 +22,19 @@ const sinon = require('sinon'); describe('EmbeddedIdentityService', () => { + const identity = { + identifier: 'ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a', + name: 'bob1', + issuer: 'ce295bc0df46512670144b84af55f3d9a3e71b569b1e38baba3f032dc3000665', + secret: 'suchsecret', + certificate: '' + }; + let identityService; let sandbox; beforeEach(() => { - identityService = new EmbeddedIdentityService('bob1'); + identityService = new EmbeddedIdentityService(identity); sandbox = sinon.sandbox.create(); }); @@ -38,15 +46,48 @@ describe('EmbeddedIdentityService', () => { it('should create a identity service', () => { identityService.should.be.an.instanceOf(IdentityService); - identityService.userID.should.equal('bob1'); + identityService.identity.should.equal(identity); + }); + + }); + + describe('#constructor', () => { + + it('should create a identity service', () => { + identityService.should.be.an.instanceOf(IdentityService); + identityService.identity.should.equal(identity); + }); + + }); + + describe('#getIdentifier', () => { + + it('should return the identifier', () => { + should.equal(identityService.getIdentifier(), 'ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a'); + }); + + }); + + describe('#getName', () => { + + it('should return the name', () => { + should.equal(identityService.getName(), 'bob1'); + }); + + }); + + describe('#getIssuer', () => { + + it('should return the issuer', () => { + should.equal(identityService.getIssuer(), 'ce295bc0df46512670144b84af55f3d9a3e71b569b1e38baba3f032dc3000665'); }); }); - describe('#getCurrentUserID', () => { + describe('#getCertificate', () => { - it('should return the current user ID', () => { - should.equal(identityService.getCurrentUserID(), 'bob1'); + it('should return the certificate', () => { + should.equal(identityService.getCertificate(), ''); }); }); diff --git a/packages/composer-runtime-web/lib/webcontext.js b/packages/composer-runtime-web/lib/webcontext.js index eac1fca501..9af70f645c 100644 --- a/packages/composer-runtime-web/lib/webcontext.js +++ b/packages/composer-runtime-web/lib/webcontext.js @@ -29,13 +29,13 @@ class WebContext extends Context { /** * Constructor. * @param {Engine} engine The owning engine. - * @param {String} userID The current user ID. + * @param {Object} identity The current identity. * @param {EventEmitter} eventSink The event emitter */ - constructor(engine, userID, eventSink) { + constructor(engine, identity, eventSink) { super(engine); this.dataService = new WebDataService(engine.getContainer().getUUID()); - this.identityService = new WebIdentityService(userID); + this.identityService = new WebIdentityService(identity); this.eventSink = eventSink; } diff --git a/packages/composer-runtime-web/lib/webidentityservice.js b/packages/composer-runtime-web/lib/webidentityservice.js index 3983081ba6..5295c309a9 100644 --- a/packages/composer-runtime-web/lib/webidentityservice.js +++ b/packages/composer-runtime-web/lib/webidentityservice.js @@ -24,20 +24,43 @@ class WebIdentityService extends IdentityService { /** * Constructor. - * @param {String} userID The current user ID. + * @param {String} identity The current identity. */ - constructor(userID) { + constructor(identity) { super(); - this.userID = userID; + this.identity = identity; } /** - * Retrieve the current user ID. - * @return {string} The current user ID, or null if the current user ID cannot - * be determined or has not been specified. + * Get a unique identifier for the identity used to submit the transaction. + * @return {string} A unique identifier for the identity used to submit the transaction. */ - getCurrentUserID() { - return this.userID; + getIdentifier() { + return this.identity.identifier; + } + + /** + * Get the name of the identity used to submit the transaction. + * @return {string} The name of the identity used to submit the transaction. + */ + getName() { + return this.identity.name; + } + + /** + * Get the issuer of the identity used to submit the transaction. + * @return {string} The issuer of the identity used to submit the transaction. + */ + getIssuer() { + return this.identity.issuer; + } + + /** + * Get the certificate for the identity used to submit the transaction. + * @return {string} The certificate for the identity used to submit the transaction. + */ + getCertificate() { + return this.identity.certificate; } } diff --git a/packages/composer-runtime-web/test/webcontext.js b/packages/composer-runtime-web/test/webcontext.js index 5b6da12773..62583aca5e 100644 --- a/packages/composer-runtime-web/test/webcontext.js +++ b/packages/composer-runtime-web/test/webcontext.js @@ -29,9 +29,18 @@ const sinon = require('sinon'); describe('WebContext', () => { + const identity = { + identifier: 'ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a', + name: 'bob1', + issuer: 'ce295bc0df46512670144b84af55f3d9a3e71b569b1e38baba3f032dc3000665', + secret: 'suchsecret', + certificate: '' + }; + let mockWebContainer; let mockSerializer; let mockEngine; + let context; beforeEach(() => { mockWebContainer = sinon.createStubInstance(WebContainer); @@ -39,12 +48,12 @@ describe('WebContext', () => { mockEngine = sinon.createStubInstance(Engine); mockEngine.getContainer.returns(mockWebContainer); mockSerializer = sinon.createStubInstance(Serializer); + context = new WebContext(mockEngine, identity); }); describe('#constructor', () => { it('should construct a new context', () => { - let context = new WebContext(mockEngine, 'bob1'); context.should.be.an.instanceOf(Context); }); @@ -53,7 +62,6 @@ describe('WebContext', () => { describe('#getDataService', () => { it('should return the container logging service', () => { - let context = new WebContext(mockEngine, 'bob1'); context.getDataService().should.be.an.instanceOf(WebDataService); }); @@ -62,9 +70,8 @@ describe('WebContext', () => { describe('#getIdentityService', () => { it('should return the container identity service', () => { - let context = new WebContext(mockEngine, 'bob1'); context.getIdentityService().should.be.an.instanceOf(WebIdentityService); - context.getIdentityService().getCurrentUserID().should.equal('bob1'); + context.getIdentityService().getIdentifier().should.equal('ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a'); }); }); @@ -72,14 +79,12 @@ describe('WebContext', () => { describe('#getEventService', () => { it('should return the container event service', () => { - let context = new WebContext(mockEngine, 'bob1'); context.getSerializer = sinon.stub().returns(mockSerializer); context.getEventService().should.be.an.instanceOf(WebEventService); }); it('should return this.eventService if it is set', () => { const mockWebEventService = sinon.createStubInstance(WebEventService); - let context = new WebContext(mockEngine, 'bob1'); context.eventService = mockWebEventService; context.getEventService().should.equal(mockWebEventService); }); @@ -88,13 +93,11 @@ describe('WebContext', () => { describe('#getHTTPService', () => { it('should return the container http service', () => { - let context = new WebContext(mockEngine, 'bob1'); context.getHTTPService().should.be.an.instanceOf(WebHTTPService); }); it('should return this.httpService if it is set', () => { const mockWebHTTPService = sinon.createStubInstance(WebHTTPService); - let context = new WebContext(mockEngine, 'bob1'); context.httpService = mockWebHTTPService; context.getHTTPService().should.equal(mockWebHTTPService); }); diff --git a/packages/composer-runtime-web/test/webidentityservice.js b/packages/composer-runtime-web/test/webidentityservice.js index 5c861b8525..a314a48b4d 100644 --- a/packages/composer-runtime-web/test/webidentityservice.js +++ b/packages/composer-runtime-web/test/webidentityservice.js @@ -22,11 +22,19 @@ const sinon = require('sinon'); describe('WebIdentityService', () => { + const identity = { + identifier: 'ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a', + name: 'bob1', + issuer: 'ce295bc0df46512670144b84af55f3d9a3e71b569b1e38baba3f032dc3000665', + secret: 'suchsecret', + certificate: '' + }; + let identityService; let sandbox; beforeEach(() => { - identityService = new WebIdentityService('bob1'); + identityService = new WebIdentityService(identity); sandbox = sinon.sandbox.create(); }); @@ -38,15 +46,39 @@ describe('WebIdentityService', () => { it('should create a identity service', () => { identityService.should.be.an.instanceOf(IdentityService); - identityService.userID.should.equal('bob1'); + identityService.identity.should.equal(identity); + }); + + }); + + describe('#getIdentifier', () => { + + it('should return the identifier', () => { + should.equal(identityService.getIdentifier(), 'ae360f8a430cc34deb2a8901ef3efed7a2eed753d909032a009f6984607be65a'); + }); + + }); + + describe('#getName', () => { + + it('should return the name', () => { + should.equal(identityService.getName(), 'bob1'); + }); + + }); + + describe('#getIssuer', () => { + + it('should return the issuer', () => { + should.equal(identityService.getIssuer(), 'ce295bc0df46512670144b84af55f3d9a3e71b569b1e38baba3f032dc3000665'); }); }); - describe('#getCurrentUserID', () => { + describe('#getCertificate', () => { - it('should return the current user ID', () => { - should.equal(identityService.getCurrentUserID(), 'bob1'); + it('should return the certificate', () => { + should.equal(identityService.getCertificate(), ''); }); });