Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.

Commit 3481c7e

Browse files
nklincolndavek-at-ibm
authored andcommitted
prevent passing cache contents by reference (#4184)
Signed-off-by: Nick Lincoln <[email protected]>
1 parent 8c90b3c commit 3481c7e

File tree

2 files changed

+71
-54
lines changed

2 files changed

+71
-54
lines changed

packages/composer-runtime/lib/registry.js

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class Registry extends EventEmitter {
4848
this.id = id;
4949
this.name = name;
5050
this.system = system;
51-
this.resourceMap = new Map();
51+
this.objectMap = new Map();
5252
}
5353

5454
/**
@@ -80,7 +80,7 @@ class Registry extends EventEmitter {
8080
const resource = this.serializer.fromJSON(object);
8181
await this.accessController.check(resource, 'READ');
8282
resources.push(resource);
83-
this.resourceMap.set(resource.getIdentifier(), resource);
83+
this.objectMap.set(resource.getIdentifier(), object);
8484
} catch (error) {
8585
// Ignore the error; we don't have access.
8686
}
@@ -95,15 +95,16 @@ class Registry extends EventEmitter {
9595
* object when complete, or rejected with an error.
9696
*/
9797
async get(id) {
98-
if (this.resourceMap.has(id)) {
99-
return this.resourceMap.get(id);
98+
if (this.objectMap.has(id)) {
99+
let object = this.objectMap.get(id);
100+
return this.serializer.fromJSON(object);
100101
} else {
101102
let object = await this.dataCollection.get(id);
102103
object = Registry.removeInternalProperties(object);
103104
try {
104105
const resource = this.serializer.fromJSON(object);
105106
await this.accessController.check(resource, 'READ');
106-
this.resourceMap.set(id, resource);
107+
this.objectMap.set(id, object);
107108
return resource;
108109
} catch (error) {
109110
throw new Error(`Object with ID '${id}' in collection with ID '${this.type}:${this.id}' does not exist`);
@@ -123,15 +124,15 @@ class Registry extends EventEmitter {
123124
return false;
124125
}
125126

126-
if (this.resourceMap.has(id)) {
127+
if (this.objectMap.has(id)) {
127128
return true;
128129
} else {
129130
let object = await this.dataCollection.get(id);
130131
object = Registry.removeInternalProperties(object);
131132
try {
132133
const resource = this.serializer.fromJSON(object);
133134
await this.accessController.check(resource, 'READ');
134-
this.resourceMap.set(id, resource);
135+
this.objectMap.set(id, object);
135136
return true;
136137
} catch (error) {
137138
return false;
@@ -255,8 +256,9 @@ class Registry extends EventEmitter {
255256
object = this.addInternalProperties(object);
256257

257258
let oldResource;
258-
if (this.resourceMap.has(id)) {
259-
oldResource = this.resourceMap.get(id);
259+
if (this.objectMap.has(id)) {
260+
let oldObject = this.objectMap.get(id);
261+
oldResource = this.serializer.fromJSON(oldObject);
260262
} else {
261263
let oldObject = await this.dataCollection.get(id);
262264
oldResource = this.serializer.fromJSON(oldObject);
@@ -266,9 +268,9 @@ class Registry extends EventEmitter {
266268
await this.accessController.check(oldResource, 'UPDATE');
267269
await this.dataCollection.update(id, object);
268270

269-
// If is within resourceMap cache, update may have changed the ability to READ item, so remove it
270-
if (this.resourceMap.has(id)) {
271-
this.resourceMap.delete(id);
271+
// If is within objectMap cache, update may have changed the ability to READ item, so remove it
272+
if (this.objectMap.has(id)) {
273+
this.objectMap.delete(id);
272274
}
273275

274276
this.emit('resourceupdated', {
@@ -307,8 +309,9 @@ class Registry extends EventEmitter {
307309
// the resource using its ID from the registry. We need to
308310
// do this to figure out the type of the resource for
309311
// access control.
310-
if (this.resourceMap.has(resource)) {
311-
resource = this.resourceMap.get(resource);
312+
if (this.objectMap.has(resource)) {
313+
let object = this.objectMap.get(resource);
314+
resource = this.serializer.fromJSON(object);
312315
} else {
313316
let object = await this.dataCollection.get(resource);
314317
object = Registry.removeInternalProperties(object);
@@ -320,8 +323,8 @@ class Registry extends EventEmitter {
320323
await this.dataCollection.remove(id);
321324

322325
// Remove from cache if present
323-
if (this.resourceMap.has(id)) {
324-
this.resourceMap.delete(id);
326+
if (this.objectMap.has(id)) {
327+
this.objectMap.delete(id);
325328
}
326329

327330
this.emit('resourceremoved', {

packages/composer-runtime/test/registry.js

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe('Registry', () => {
107107
}).returns(mockResource2);
108108
});
109109

110-
it('should get and parse all of the resources from the registry if not in the cache', () => {
110+
it('should get and parse all of the resources from the registry if not in the object cache', () => {
111111
return registry.getAll()
112112
.then((resources) => {
113113
sinon.assert.calledTwice(mockAccessController.check);
@@ -118,12 +118,14 @@ describe('Registry', () => {
118118
});
119119
});
120120

121-
it('should add resources to the cache during the retrieval', async () => {
121+
it('should add objects to the object cache during the retrieval', async () => {
122+
let map = registry.objectMap;
123+
map.size.should.be.equal(0);
122124
await registry.getAll();
123-
let map = registry.resourceMap;
124-
let keys = map.keys();
125-
for (let key of keys){
126-
map.get(key).should.be.an.instanceOf(Resource);
125+
map.size.should.be.equal(2);
126+
127+
for (let key of map.keys()){
128+
map.get(key).should.be.an.instanceOf(Object);
127129
}
128130
});
129131

@@ -167,17 +169,24 @@ describe('Registry', () => {
167169
describe('#get', () => {
168170

169171
let mockResource;
172+
let mockResource2;
170173

171174
beforeEach(() => {
172175
mockDataCollection.get.withArgs('doge1').resolves({
173176
$class: 'org.doge.Doge',
174177
assetId: 'doge1'
175178
});
176179
mockResource = sinon.createStubInstance(Resource);
180+
mockResource2 = sinon.createStubInstance(Resource);
181+
mockResource2.getIdentifier.returns('cached');
177182
mockSerializer.fromJSON.withArgs({
178183
$class: 'org.doge.Doge',
179184
assetId: 'doge1'
180185
}).returns(mockResource);
186+
mockSerializer.fromJSON.withArgs({
187+
$class: 'org.doge.Doge',
188+
assetId: 'doge2'
189+
}).returns(mockResource2);
181190
});
182191

183192
it('should get the specific resource from the registry if not in the cache', () => {
@@ -190,21 +199,23 @@ describe('Registry', () => {
190199
});
191200
});
192201

193-
it('should add to the cache after registry retrieval', async () => {
202+
it('should add to the object cache after registry retrieval', async () => {
194203
await registry.get('doge1');
195-
let resource = registry.resourceMap.get('doge1');
196-
resource.should.be.an.instanceOf(Resource);
204+
let obj = registry.objectMap.get('doge1');
205+
obj.should.be.an.instanceOf(Object);
197206
});
198207

199208
it('should get the specific resource from the cache if present', async () => {
200-
let mockResource = sinon.createStubInstance(Resource);
201-
mockResource.theValue = 'the value 1';
202-
mockResource.getIdentifier.returns('doge1');
203-
registry.resourceMap.set('doge1', mockResource);
209+
registry.objectMap.set('doge2', {
210+
$class: 'org.doge.Doge',
211+
assetId: 'doge2'
212+
});
204213

205-
let resource = await registry.get('doge1');
214+
let resource = await registry.get('doge2');
206215
resource.should.be.an.instanceOf(Resource);
207-
resource.getIdentifier().should.equal('doge1');
216+
resource.getIdentifier().should.equal('cached');
217+
218+
// Should not go the datacollection route
208219
sinon.assert.notCalled(mockDataCollection.get);
209220
sinon.assert.notCalled(mockAccessController.check);
210221
});
@@ -253,15 +264,19 @@ describe('Registry', () => {
253264
return registry.exists('doge1')
254265
.should.eventually.be.true
255266
.then((exists) => {
256-
registry.resourceMap.get('doge1').should.be.an.instanceOf(Resource);
267+
registry.objectMap.size.should.be.equal(1);
268+
registry.objectMap.get('doge1').should.deep.equal({
269+
$class: 'org.doge.Doge',
270+
assetId: 'doge1'
271+
});
257272
});
258273
});
259274

260275
it('should determine whether a specific resource exists via cache existence', async () => {
261276
let mockResource = sinon.createStubInstance(Resource);
262277
mockResource.theValue = 'the value 1';
263278
mockResource.getIdentifier.returns('doge1');
264-
registry.resourceMap.set('doge1', mockResource);
279+
registry.objectMap.set('doge1', mockResource);
265280

266281
return registry.exists('doge1')
267282
.should.eventually.be.true
@@ -590,6 +605,7 @@ describe('Registry', () => {
590605

591606
let mockResource, mockOldResource;
592607
let mockClassDeclaration, mockOldClassDeclaration;
608+
let mockObject, mockOldObject;
593609

594610
beforeEach(() => {
595611
// New resources.
@@ -600,11 +616,13 @@ describe('Registry', () => {
600616
mockClassDeclaration.getSystemType.returns('Asset');
601617
mockResource.getIdentifier.returns('doge1');
602618
mockResource.theValue = 'newValue';
603-
mockSerializer.toJSON.withArgs(mockResource).onFirstCall().returns({
619+
mockObject = {
604620
$class: 'org.doge.Doge',
605621
assetId: 'doge1',
606622
newValue: 'newValue'
607-
});
623+
};
624+
mockSerializer.toJSON.withArgs(mockResource).onFirstCall().returns(mockObject);
625+
mockSerializer.fromJSON.withArgs(mockObject).returns(mockResource);
608626
// Old resources.
609627
mockOldResource = sinon.createStubInstance(Resource);
610628
mockOldClassDeclaration = sinon.createStubInstance(ClassDeclaration);
@@ -613,16 +631,13 @@ describe('Registry', () => {
613631
mockOldClassDeclaration.getSystemType.returns('Asset');
614632
mockOldResource.getIdentifier.returns('doge1');
615633
mockOldResource.theValue = 'oldValue';
616-
mockDataCollection.get.withArgs('doge1').resolves({
617-
$class: 'org.doge.Doge',
618-
assetId: 'doge1',
619-
newValue: 'oldValue'
620-
});
621-
mockSerializer.fromJSON.withArgs({
634+
mockOldObject = {
622635
$class: 'org.doge.Doge',
623636
assetId: 'doge1',
624637
newValue: 'oldValue'
625-
}).returns(mockOldResource);
638+
};
639+
mockDataCollection.get.withArgs('doge1').resolves(mockOldObject);
640+
mockSerializer.fromJSON.withArgs(mockOldObject).returns(mockOldResource);
626641
});
627642

628643
it('should update the resource in the registry via datacollection retrieval if not in the cache', () => {
@@ -652,7 +667,7 @@ describe('Registry', () => {
652667
it('should update the resource in the registry via cache retrieval if present', () => {
653668
mockDataCollection.update.resolves();
654669
let mockEventHandler = sinon.stub();
655-
registry.resourceMap.set('doge1', mockResource);
670+
registry.objectMap.set('doge1', mockObject);
656671
registry.on('resourceupdated', mockEventHandler);
657672
return registry.update(mockResource)
658673
.then(() => {
@@ -673,10 +688,10 @@ describe('Registry', () => {
673688
it('should remove the item from teh cache if updated via cache retrieval', async () => {
674689
mockDataCollection.update.resolves();
675690
let mockEventHandler = sinon.stub();
676-
registry.resourceMap.set('doge1', mockResource);
691+
registry.objectMap.set('doge1', mockResource);
677692
registry.on('resourceupdated', mockEventHandler);
678693
await registry.update(mockResource);
679-
registry.resourceMap.has('doge1').should.be.false;
694+
registry.objectMap.has('doge1').should.be.false;
680695
});
681696

682697
it('should throw an error updating a resource to the wrong registry', () => {
@@ -776,21 +791,20 @@ describe('Registry', () => {
776791
describe('#remove', () => {
777792

778793
let mockResource;
794+
let mockObject;
779795

780796
beforeEach(() => {
781797
// Old resources.
782798
// Deleting a resource by ID currently requires that we read it from the registry.
783-
mockDataCollection.get.withArgs('doge1').resolves({
799+
mockObject = {
784800
$class: 'org.doge.Doge',
785801
assetId: 'doge1'
786-
});
802+
};
803+
mockDataCollection.get.withArgs('doge1').resolves(mockObject);
787804
mockResource = sinon.createStubInstance(Resource);
788805
mockResource.theValue = 'the value 1';
789806
mockResource.getIdentifier.returns('doge1');
790-
mockSerializer.fromJSON.withArgs({
791-
$class: 'org.doge.Doge',
792-
assetId: 'doge1'
793-
}).returns(mockResource);
807+
mockSerializer.fromJSON.withArgs(mockObject).returns(mockResource);
794808
// End of resources.
795809
});
796810

@@ -828,7 +842,7 @@ describe('Registry', () => {
828842

829843
it('should remove the resource by ID from the registry via cache retrieval if present', () => {
830844
mockDataCollection.remove.resolves();
831-
registry.resourceMap.set('doge1', mockResource);
845+
registry.objectMap.set('doge1', mockObject);
832846
let mockEventHandler = sinon.stub();
833847
registry.on('resourceremoved', mockEventHandler);
834848
return registry.remove('doge1')
@@ -845,11 +859,11 @@ describe('Registry', () => {
845859

846860
it('should remove the resource from the cache if present', async () => {
847861
mockDataCollection.remove.resolves();
848-
registry.resourceMap.set('doge1', mockResource);
862+
registry.objectMap.set('doge1', mockObject);
849863
let mockEventHandler = sinon.stub();
850864
registry.on('resourceremoved', mockEventHandler);
851865
await registry.remove('doge1');
852-
registry.resourceMap.has('doge1').should.be.false;
866+
registry.objectMap.has('doge1').should.be.false;
853867
});
854868

855869
it('should throw if the access controller throws an exception', () => {

0 commit comments

Comments
 (0)