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

Commit 4473d27

Browse files
authored
Solution for cyclic ACL rules (#4157)
* possible solution; g Signed-off-by: Matthew B White <[email protected]> * update error messages Signed-off-by: Matthew B White <[email protected]> * update tests Signed-off-by: Matthew B White <[email protected]>
1 parent 3481c7e commit 4473d27

File tree

3 files changed

+45
-2
lines changed

3 files changed

+45
-2
lines changed

packages/composer-runtime/lib/accesscontroller.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class AccessController {
3939
this.context = context;
4040
this.participant = null;
4141
this.transaction = null;
42+
this.aclRuleStack=[];
4243
LOG.exit(method);
4344
}
4445

@@ -120,7 +121,8 @@ class AccessController {
120121

121122
// Iterate over the ACL rules in order, but stop at the first rule
122123
// that permits the action.
123-
return filteredAclRules.reduce((promise, aclRule) => {
124+
return filteredAclRules.reduce((promise, aclRule,currentIndex) => {
125+
LOG.debug(`Processing rule ${aclRule.getName()}, index ${currentIndex} `);
124126
return promise.then((result) => {
125127
if (result) {
126128
return result;
@@ -211,11 +213,32 @@ class AccessController {
211213
checkRule(resource, access, participant, transaction, aclRule) {
212214
const method = 'checkRule';
213215
LOG.entry(method, resource, access, participant, transaction, aclRule);
216+
let pid='',tx='';
217+
if (participant){
218+
pid = participant.getFullyQualifiedIdentifier();
219+
}
220+
if (transaction){
221+
tx = transaction.getFullyQualifiedIdentifier();
222+
}
223+
let checkId = `${aclRule.getName()}/${access}/${pid}/${tx}/`;
224+
if (this.aclRuleStack.includes(checkId)){
225+
this.aclRuleStack=[];
226+
227+
// This must be an explicit deny rule, so throw.
228+
let e = new Error('Cyclic ACL Rule detected, rule condition is invoking the same rule');
229+
LOG.error(method, e);
230+
this.aclRuleStack=[];
231+
return Promise.reject(e);
232+
}
233+
this.aclRuleStack.push(checkId);
214234

215235
// Is the predicate met?
216236
return this.matchPredicate(resource, participant, transaction, aclRule)
217237
.then((result) => {
218238

239+
// pop...
240+
this.aclRuleStack.pop();
241+
219242
// No, predicate not met.
220243
if (!result) {
221244
LOG.debug(method, 'Predicate does not match');
@@ -232,6 +255,7 @@ class AccessController {
232255
// This must be an explicit deny rule, so throw.
233256
let e = new AccessException(resource, access, participant, transaction);
234257
LOG.error(method, e);
258+
this.aclRuleStack=[];
235259
throw e;
236260

237261
});

packages/composer-runtime/lib/registry.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ class Registry extends EventEmitter {
107107
this.objectMap.set(id, object);
108108
return resource;
109109
} catch (error) {
110-
throw new Error(`Object with ID '${id}' in collection with ID '${this.type}:${this.id}' does not exist`);
110+
let e = new Error(`Object with ID '${id}' in collection with ID '${this.type}:${this.id}' does not exist; [cause=${error.message}]`);
111+
throw e;
111112
}
112113
}
113114
}

packages/composer-runtime/test/accesscontroller.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,24 @@ describe('AccessController', () => {
380380
.should.be.rejectedWith(AccessException, /does not have/);
381381
});
382382

383+
it('should reject with cyclic error if rule seen before', () => {
384+
setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test.TestParticipant#P5678" operation: READ resource: "org.acme.test.TestAsset#A1234" condition: (true) action: ALLOW}');
385+
386+
// pretend that we've been here before....
387+
controller.aclRuleStack.push('R1/READ/org.acme.test.TestParticipant#P5678/org.acme.test.TestTransaction#T9012/');
388+
return controller.checkRule(asset, 'READ', participant, transaction, aclManager.getAclRules()[0]).
389+
should.be.rejectedWith(/Cyclic ACL Rule detected/);
390+
});
391+
392+
it('should reject with cyclic error if rule seen before - minimal data', () => {
393+
setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test.TestParticipant#P5678" operation: READ resource: "org.acme.test.TestAsset#A1234" condition: (true) action: ALLOW}');
394+
395+
// pretend that we've been here before....
396+
controller.aclRuleStack.push('R1/READ///');
397+
return controller.checkRule(asset, 'READ', null, null, aclManager.getAclRules()[0]).
398+
should.be.rejectedWith(/Cyclic ACL Rule detected/);
399+
});
400+
383401
});
384402

385403
describe('#matchNoun', () => {

0 commit comments

Comments
 (0)