From 7bc534102c794fe5a8c13b8db311271696483df5 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Thu, 1 Jun 2017 11:34:42 +0100 Subject: [PATCH 1/3] Update ACL and bump version to 0.9.0 - remove property syntax (wasn't fully implemented anyway) - support .* and .** globs on namespaces - check type hierarchy for resources (already implemented for participants and transactions) - add aditional validation Signed-off-by: James Taylor --- lerna.json | 2 +- package.json | 2 +- packages/composer-admin/package.json | 8 +- packages/composer-cli/package.json | 10 +- packages/composer-client/package.json | 8 +- packages/composer-common/index.js | 1 + packages/composer-common/lib/acl/aclrule.js | 12 +- .../composer-common/lib/acl/modelbinding.js | 75 ++--- packages/composer-common/lib/acl/parser.pegjs | 16 +- packages/composer-common/lib/modelutil.js | 46 ++- packages/composer-common/messages/en.json | 1 - packages/composer-common/package.json | 2 +- packages/composer-common/test/acl/aclfile.js | 309 ++++++++++++++---- packages/composer-common/test/acl/aclrule.js | 10 +- .../composer-common/test/acl/modelbinding.js | 73 ++--- packages/composer-common/test/acl/test.acl | 57 +++- .../test/businessnetworkdefinition.js | 2 +- .../test-archive-dotfolders/permissions.acl | 13 +- .../test/data/zip/test-archive.zip | Bin 5683 -> 5520 bytes .../data/zip/test-archive/permissions.acl | 13 +- packages/composer-common/test/modelutil.js | 79 +++++ .../composer-connector-embedded/package.json | 8 +- packages/composer-connector-hlf/package.json | 6 +- .../composer-connector-hlfv1/package.json | 6 +- .../composer-connector-proxy/package.json | 4 +- .../composer-connector-server/package.json | 10 +- packages/composer-connector-web/package.json | 8 +- .../features/basic-sample-network.bna | Bin 5671 -> 6481 bytes .../features/basic-sample-network/README.md | 10 +- .../lib/{logic.js => sample.js} | 27 +- .../{org.acme.sample.cto => sample.cto} | 2 +- .../basic-sample-network/package.json | 2 +- .../basic-sample-network/permissions.acl | 9 +- .../features/events.feature | 12 +- packages/composer-cucumber-steps/package.json | 10 +- packages/composer-playground-api/package.json | 6 +- packages/composer-playground/package.json | 18 +- packages/composer-rest-server/package.json | 12 +- .../test/bond-network.bna | Bin 5143 -> 5105 bytes .../composer-runtime-embedded/package.json | 6 +- packages/composer-runtime-hlf/package.json | 4 +- packages/composer-runtime-hlfv1/package.json | 4 +- packages/composer-runtime-web/package.json | 6 +- .../composer-runtime/lib/accesscontroller.js | 37 +-- packages/composer-runtime/package.json | 4 +- .../composer-runtime/test/accesscontroller.js | 69 +++- packages/composer-systests/package.json | 16 +- .../jekylldocs/reference/acl_language.md | 27 +- packages/composer-website/package.json | 10 +- .../package.json | 10 +- .../loopback-connector-composer/package.json | 10 +- .../test/bond-network.bna | Bin 5143 -> 5105 bytes 52 files changed, 706 insertions(+), 386 deletions(-) rename packages/composer-cucumber-steps/features/basic-sample-network/lib/{logic.js => sample.js} (61%) rename packages/composer-cucumber-steps/features/basic-sample-network/models/{org.acme.sample.cto => sample.cto} (95%) diff --git a/lerna.json b/lerna.json index c9823084df..5823eae946 100644 --- a/lerna.json +++ b/lerna.json @@ -3,6 +3,6 @@ "packages": [ "packages/*" ], - "version": "0.8.1", + "version": "0.9.0", "hoist": true } diff --git a/package.json b/package.json index f0567533bf..f7b53fd322 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "name": "composer", "description": "You must install [Lerna](https://lernajs.io) to build this multi-package repository.", - "version": "0.8.1", + "version": "0.9.0", "main": "index.js", "private": true, "scripts": { diff --git a/packages/composer-admin/package.json b/packages/composer-admin/package.json index 31b4481c24..80feb08824 100644 --- a/packages/composer-admin/package.json +++ b/packages/composer-admin/package.json @@ -1,6 +1,6 @@ { "name": "composer-admin", - "version": "0.8.1", + "version": "0.9.0", "description": "Hyperledger Composer Admin, code that manages business networks deployed to Hyperledger Fabric", "engines": { "node": ">=6", @@ -42,9 +42,9 @@ "sinon-as-promised": "^4.0.2" }, "dependencies": { - "composer-common": "^0.8.1", - "composer-connector-hlf": "^0.8.1", - "composer-connector-hlfv1": "^0.8.1" + "composer-common": "^0.9.0", + "composer-connector-hlf": "^0.9.0", + "composer-connector-hlfv1": "^0.9.0" }, "license-check-config": { "src": [ diff --git a/packages/composer-cli/package.json b/packages/composer-cli/package.json index 8a32bf520f..e68304f948 100644 --- a/packages/composer-cli/package.json +++ b/packages/composer-cli/package.json @@ -1,6 +1,6 @@ { "name": "composer-cli", - "version": "0.8.1", + "version": "0.9.0", "description": "Hyperledger Composer command line interfaces (CLIs)", "engines": { "node": ">=6", @@ -42,10 +42,10 @@ "dependencies": { "chalk": "^1.1.3", "cli-table": "^0.3.1", - "composer-admin": "^0.8.1", - "composer-client": "^0.8.1", - "composer-common": "^0.8.1", - "composer-rest-server": "^0.8.1", + "composer-admin": "^0.9.0", + "composer-client": "^0.9.0", + "composer-common": "^0.9.0", + "composer-rest-server": "^0.9.0", "homedir": "^0.6.0", "npm-paths": "^0.1.3", "nunjucks": "^3.0.0", diff --git a/packages/composer-client/package.json b/packages/composer-client/package.json index 4d75077d14..c867c57835 100644 --- a/packages/composer-client/package.json +++ b/packages/composer-client/package.json @@ -1,6 +1,6 @@ { "name": "composer-client", - "version": "0.8.1", + "version": "0.9.0", "description": "The node.js client library for Hyperledger Composer, a development framework for Hyperledger Fabric", "engines": { "node": ">=6", @@ -42,9 +42,9 @@ "logError": true }, "dependencies": { - "composer-common": "^0.8.1", - "composer-connector-hlf": "^0.8.1", - "composer-connector-hlfv1": "^0.8.1", + "composer-common": "^0.9.0", + "composer-connector-hlf": "^0.9.0", + "composer-connector-hlfv1": "^0.9.0", "uuid": "^3.0.1" }, "devDependencies": { diff --git a/packages/composer-common/index.js b/packages/composer-common/index.js index 79081dc5c5..04b43b7cd4 100644 --- a/packages/composer-common/index.js +++ b/packages/composer-common/index.js @@ -94,6 +94,7 @@ module.exports.Sort = require('./lib/query/sort'); module.exports.TransactionDeclaration = require('./lib/introspect/transactiondeclaration'); module.exports.TypescriptVisitor = require('./lib/codegen/fromcto/typescript/typescriptvisitor'); module.exports.Util = require('./lib/util'); +module.exports.ModelUtil = require('./lib/modelutil'); module.exports.Wallet = require('./lib/wallet'); module.exports.Where = require('./lib/query/where'); module.exports.Writer = require('./lib/codegen/writer.js'); diff --git a/packages/composer-common/lib/acl/aclrule.js b/packages/composer-common/lib/acl/aclrule.js index 4eacd6fc82..45b5d4cffe 100644 --- a/packages/composer-common/lib/acl/aclrule.js +++ b/packages/composer-common/lib/acl/aclrule.js @@ -112,17 +112,27 @@ class AclRule { const foundVerbs = {}; this.verbs.forEach((verb) => { if (foundVerbs[verb]) { - throw new Error(`The verb '${verb}' has been specified more than once in the ACL rule '${this.name}'`); + throw new IllegalModelException(`The verb '${verb}' has been specified more than once in the ACL rule '${this.name}'`); } foundVerbs[verb] = true; }); if(this.participant) { this.participant.validate(); + + let participantClassDeclaration = this.participant.getClassDeclaration(); + if (participantClassDeclaration && participantClassDeclaration.constructor.name !== 'ParticipantDeclaration') { + throw new IllegalModelException(`The participant '${participantClassDeclaration.getName()}' must be a participant`); + } } if(this.transaction) { this.transaction.validate(); + + let transactionClassDeclaration = this.transaction.getClassDeclaration(); + if (transactionClassDeclaration && transactionClassDeclaration.constructor.name !== 'TransactionDeclaration') { + throw new IllegalModelException(`The transaction '${transactionClassDeclaration.getName()}' must be a transaction`); + } } if(this.predicate) { diff --git a/packages/composer-common/lib/acl/modelbinding.js b/packages/composer-common/lib/acl/modelbinding.js index ccc070c2d4..13b68a80d0 100644 --- a/packages/composer-common/lib/acl/modelbinding.js +++ b/packages/composer-common/lib/acl/modelbinding.js @@ -142,70 +142,43 @@ class ModelBinding { /** * Semantic validation of the structure of this ModelBinding. - *

- * Algorithm: - *

- * - *
-     * We assume we have ns.class.property and try to resolve the class in ns
-     * - On success
-     * -- Check that the property exists
-     * - On failure
-     * -- Try to resolve ns.class
-     * -- On failure
-     * --- If instanceId and variableName are null
-     * --- Try to resolve ns
-     * 
+ * * @throws {IllegalModelException} * @private */ validate() { const mm = this.getAclRule().getAclFile().getModelManager(); - // assume qualifiedName is ns.class.property - const nsDotClass = ModelUtil.getNamespace(this.qualifiedName); - const ns = ModelUtil.getNamespace(nsDotClass); - const className = ModelUtil.getShortName(nsDotClass); - const propertyName = ModelUtil.getShortName(this.qualifiedName); - const modelFile = mm.getModelFile(ns); + const ns = ModelUtil.getNamespace(this.qualifiedName); + if (ModelUtil.isRecursiveWildcardName(this.qualifiedName)) { + const namespaces = mm.getNamespaces(); - if(modelFile) { - const classDeclaration = modelFile.getLocalType(className); - if(!classDeclaration) { - throw new Error('Failed to find class ' + nsDotClass); - } - this.classDeclaration = classDeclaration; - const property = classDeclaration.getProperty(propertyName); - if(!property) { - throw new Error('Failed to find property ' + this.qualifiedName); + if (namespaces.findIndex(function (element, index, array) { + return (ns === element || element.startsWith(ns + '.')); + })=== -1) { + throw new IllegalModelException('Failed to find namespace ' + this.qualifiedName); } - } - else { - // assume qualifiedName is ns.class - const ns = ModelUtil.getNamespace(this.qualifiedName); - const className = ModelUtil.getShortName(this.qualifiedName); + } else if (ModelUtil.isWildcardName(this.qualifiedName)) { const modelFile = mm.getModelFile(ns); - if(modelFile) { - const classDeclaration = modelFile.getLocalType(className); - if(!classDeclaration) { - throw new Error('Failed to find class ' + this.qualifiedName); - } - this.classDeclaration = classDeclaration; + if(!modelFile) { + throw new IllegalModelException('Failed to find namespace ' + this.qualifiedName); } - else if(this.instanceId === null && this.variableName === null) { - // assume namespace - const modelFile = mm.getModelFile(this.qualifiedName); - if(!modelFile) { - throw new Error('Failed to find namespace ' + this.qualifiedName); - } + } else { + const modelFile = mm.getModelFile(ns); + + if(!modelFile) { + throw new IllegalModelException('Failed to find namespace ' + ns); } - else { - throw new Error('Failed to resolve ' + this.qualifiedName); + + const className = ModelUtil.getShortName(this.qualifiedName); + const classDeclaration = modelFile.getLocalType(className); + + if(!classDeclaration) { + throw new IllegalModelException('Failed to find class ' + this.qualifiedName); } + + this.classDeclaration = classDeclaration; } } diff --git a/packages/composer-common/lib/acl/parser.pegjs b/packages/composer-common/lib/acl/parser.pegjs index 7188268e95..a72df4bfc5 100644 --- a/packages/composer-common/lib/acl/parser.pegjs +++ b/packages/composer-common/lib/acl/parser.pegjs @@ -1424,8 +1424,12 @@ InstanceId return id; } +Glob + = '.**' + / '.*' + Binding - = qualifiedName:QualifiedName instanceId:InstanceId? + = qualifiedName:QualifiedName instanceId:InstanceId { return { type: "Binding", @@ -1436,17 +1440,18 @@ Binding } BindingNoInstance - = qualifiedName:QualifiedName + = qualifiedName:(QualifiedName Glob?) { return { type: "BindingNoInstance", - qualifiedName: qualifiedName, + qualifiedName: qualifiedName.join(''), location: location() }; } Noun = Binding + / BindingNoInstance NounNoInstance = BindingNoInstance @@ -1486,8 +1491,9 @@ AllVerb = 'ALL' Verbs = AllVerb / BasicVerbList Participant - = 'ANY' / - Binding + = 'ANY' + / Binding + / BindingNoInstance Predicate = "(" __ test:$Expression __ ")" __ diff --git a/packages/composer-common/lib/modelutil.js b/packages/composer-common/lib/modelutil.js index 9c02a645b5..c4dbda366d 100644 --- a/packages/composer-common/lib/modelutil.js +++ b/packages/composer-common/lib/modelutil.js @@ -44,15 +44,51 @@ class ModelUtil { } /** - * Returns true if the specified name is a wildcard. + * Returns true if the specified name is a wildcard * @param {string} fqn - the source string - * @return {boolean} true if the specified name is a wildcard. + * @return {boolean} true if the specified name is a wildcard * @private */ static isWildcardName(fqn) { return ModelUtil.getShortName(fqn) === '*'; } + /** + * Returns true if the specified name is a recusive wildcard + * @param {string} fqn - the source string + * @return {boolean} true if the specified name is a recusive wildcard + * @private + */ + static isRecursiveWildcardName(fqn) { + return ModelUtil.getShortName(fqn) === '**'; + } + + /** + * Returns true if a type matches the required fully qualified name. The required + * name may be a wildcard or recursive wildcard + * @param {Typed} type - the type to test + * @param {string} fqn - required fully qualified name + * @return {boolean} true if the specified type and namespace match + * @private + */ + static isMatchingType(type, fqn) { + let ns = ModelUtil.getNamespace(fqn); + let typeNS = type.getNamespace(); + + if (type.instanceOf(fqn)) { + // matching type or subtype + } else if (ModelUtil.isWildcardName(fqn) && typeNS === ns) { + // matching namespace + } else if (ModelUtil.isRecursiveWildcardName(fqn) && (typeNS + '.').startsWith(ns + '.')) { + // matching recursive namespace + } else { + // does not match + return false; + } + + return true; + } + /** * Returns the namespace for a the fully qualified name of a type * @param {string} fqn - the fully qualified identifier of a type @@ -76,13 +112,13 @@ class ModelUtil { /** * Returns true if the type is a primitive type - * @param {string} type - the name of the type + * @param {string} typeName - the name of the type * @return {boolean} - true if the type is a primitive * @private */ - static isPrimitiveType(type) { + static isPrimitiveType(typeName) { const primitiveTypes = ['Boolean', 'String', 'DateTime', 'Double', 'Integer', 'Long']; - return (primitiveTypes.indexOf(type) >= 0); + return (primitiveTypes.indexOf(typeName) >= 0); } /** diff --git a/packages/composer-common/messages/en.json b/packages/composer-common/messages/en.json index d0065c9aab..534800e1e7 100644 --- a/packages/composer-common/messages/en.json +++ b/packages/composer-common/messages/en.json @@ -56,7 +56,6 @@ "modelutil-getnamespace-nofnq": "FQN is invalid.", - "resourcevalidator-notresourceorconcept": "Model violation in instance {resourceId} class {classFQN} has value {invalidValue} expected a Resource or a Concept.", "resourcevalidator-notrelationship": "Model violation in instance {resourceId} class {classFQN} has value {invalidValue} expected a Relationship.", "resourcevalidator-fieldtypeviolation": "Model violation in instance {resourceId} field {propertyName} has value {value} ({typeOfValue}) expected type {fieldType}", diff --git a/packages/composer-common/package.json b/packages/composer-common/package.json index c7adea4538..27ae5eca48 100644 --- a/packages/composer-common/package.json +++ b/packages/composer-common/package.json @@ -1,6 +1,6 @@ { "name": "composer-common", - "version": "0.8.1", + "version": "0.9.0", "description": "Hyperledger Composer Common, code that is common across client, admin and runtime.", "engines": { "node": ">=6", diff --git a/packages/composer-common/test/acl/aclfile.js b/packages/composer-common/test/acl/aclfile.js index 290a1b9f32..00c1bf93f3 100644 --- a/packages/composer-common/test/acl/aclfile.js +++ b/packages/composer-common/test/acl/aclfile.js @@ -17,6 +17,8 @@ const AclFile = require('../../lib/acl/aclfile'); const parser = require('../../lib/acl/parser'); const ModelManager = require('../../lib/modelmanager'); +const IllegalModelException = require('../../lib/introspect/illegalmodelexception'); +const ParseException = require('../../lib/introspect/parseexception'); const fs = require('fs'); const path = require('path'); @@ -81,7 +83,7 @@ describe('AclFile', () => { describe('#constructor', () => { - it('should parse a rule correctly', () => { + it('should parse R1 rule correctly', () => { const aclContents = `rule R1 { description: "Fred can DELETE the car ABC123" participant: "org.acme.Driver#Fred" @@ -105,7 +107,7 @@ describe('AclFile', () => { r1.getDescription().should.equal('Fred can DELETE the car ABC123'); }); - it('should parse a rule correctly', () => { + it('should parse R2 rule correctly', () => { const aclContents = `rule R2 { description: "regulator with ID Bill can not update a Car if they own it" participant(r): "org.acme.Regulator#Bill" @@ -126,13 +128,12 @@ describe('AclFile', () => { r2.getParticipant().getVariableName().should.equal('r'); }); - it('should parse a rule correctly', () => { + it('should parse R3 rule correctly', () => { const aclContents = `rule R3 { - description: "Driver can change the ownership of a car that they own" - participant(d): "org.acme.Driver" - operation: UPDATE - resource(o): "org.acme.Car.owner" - condition: (o == d) + description: "regulators can perform all operations on Cars" + participant: "org.acme.Regulator" + operation: ALL + resource: "org.acme.Car" action: ALLOW }`; const aclFile = new AclFile('test.acl', modelManager, aclContents); @@ -140,20 +141,21 @@ describe('AclFile', () => { aclFile.getDefinitions().should.equal(aclContents); const r3 = aclFile.getAclRules()[0]; r3.getName().should.equal('R3'); - r3.getNoun().getFullyQualifiedName().should.equal('org.acme.Car.owner'); - r3.getVerbs().should.deep.equal(['UPDATE']); - r3.getParticipant().getFullyQualifiedName().should.equal('org.acme.Driver'); - r3.getParticipant().getVariableName().should.equal('d'); + r3.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); + r3.getVerbs().should.deep.equal(['ALL']); + r3.getParticipant().getFullyQualifiedName().should.equal('org.acme.Regulator'); + (r3.getParticipant().getInstanceIdentifier() === null).should.be.true; + (r3.getParticipant().getVariableName() === null).should.be.true; (r3.getTransaction() === null).should.be.true; - r3.getPredicate().getExpression().should.equal('o == d'); + r3.getPredicate().getExpression().should.equal('true'); r3.getAction().should.equal('ALLOW'); - r3.getDescription().should.equal('Driver can change the ownership of a car that they own'); + r3.getDescription().should.equal('regulators can perform all operations on Cars'); }); - it('should parse a rule correctly', () => { + it('should parse R4 rule correctly', () => { const aclContents = `rule R4 { - description: "regulators can perform all operations on Cars" - participant: "org.acme.Regulator" + description: "anyone in the org.acme namespace can perform all operations on Cars" + participant: "org.acme.*" operation: ALL resource: "org.acme.Car" action: ALLOW @@ -165,21 +167,21 @@ describe('AclFile', () => { r4.getName().should.equal('R4'); r4.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); r4.getVerbs().should.deep.equal(['ALL']); - r4.getParticipant().getFullyQualifiedName().should.equal('org.acme.Regulator'); + r4.getParticipant().getFullyQualifiedName().should.equal('org.acme.*'); (r4.getParticipant().getInstanceIdentifier() === null).should.be.true; (r4.getParticipant().getVariableName() === null).should.be.true; (r4.getTransaction() === null).should.be.true; r4.getPredicate().getExpression().should.equal('true'); r4.getAction().should.equal('ALLOW'); - r4.getDescription().should.equal('regulators can perform all operations on Cars'); + r4.getDescription().should.equal('anyone in the org.acme namespace can perform all operations on Cars'); }); - it('should parse a rule correctly', () => { + it('should parse R5 rule correctly', () => { const aclContents = `rule R5 { - description: "Everyone can read all resources in the org.acme namespace" - participant: "ANY" - operation: READ - resource: "org.acme" + description: "anyone under the org.acme namespace can perform all operations on Cars" + participant: "org.acme.**" + operation: ALL + resource: "org.acme.Car" action: ALLOW }`; const aclFile = new AclFile('test.acl', modelManager, aclContents); @@ -187,22 +189,23 @@ describe('AclFile', () => { aclFile.getDefinitions().should.equal(aclContents); const r5 = aclFile.getAclRules()[0]; r5.getName().should.equal('R5'); - r5.getNoun().getFullyQualifiedName().should.equal('org.acme'); - r5.getVerbs().should.deep.equal(['READ']); - (r5.getParticipant() === null).should.be.true; + r5.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); + r5.getVerbs().should.deep.equal(['ALL']); + r5.getParticipant().getFullyQualifiedName().should.equal('org.acme.**'); + (r5.getParticipant().getInstanceIdentifier() === null).should.be.true; + (r5.getParticipant().getVariableName() === null).should.be.true; (r5.getTransaction() === null).should.be.true; r5.getPredicate().getExpression().should.equal('true'); r5.getAction().should.equal('ALLOW'); - r5.getDescription().should.equal('Everyone can read all resources in the org.acme namespace'); + r5.getDescription().should.equal('anyone under the org.acme namespace can perform all operations on Cars'); }); - it('should parse a rule correctly', () => { + it('should parse R6 rule correctly', () => { const aclContents = `rule R6 { - description: "Drivers can do something in a org.acme.Transaction transaction" + description: "Everyone can read all resources in the org.acme namespace" participant: "ANY" - operation: ALL - resource: "org.acme.Car" - transaction: "org.acme.Transaction" + operation: READ + resource: "org.acme.*" action: ALLOW }`; const aclFile = new AclFile('test.acl', modelManager, aclContents); @@ -210,24 +213,21 @@ describe('AclFile', () => { aclFile.getDefinitions().should.equal(aclContents); const r6 = aclFile.getAclRules()[0]; r6.getName().should.equal('R6'); - r6.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); - r6.getVerbs().should.deep.equal(['ALL']); + r6.getNoun().getFullyQualifiedName().should.equal('org.acme.*'); + r6.getVerbs().should.deep.equal(['READ']); (r6.getParticipant() === null).should.be.true; - r6.getTransaction().getFullyQualifiedName().should.equal('org.acme.Transaction'); - (r6.getTransaction().getVariableName() === null).should.be.true; + (r6.getTransaction() === null).should.be.true; r6.getPredicate().getExpression().should.equal('true'); r6.getAction().should.equal('ALLOW'); - r6.getDescription().should.equal('Drivers can do something in a org.acme.Transaction transaction'); + r6.getDescription().should.equal('Everyone can read all resources in the org.acme namespace'); }); - it('should parse a rule correctly', () => { + it('should parse R7 rule correctly', () => { const aclContents = `rule R7 { - description: "Regulators can do something in a org.acme.Transaction transaction" + description: "Everyone can read all resources under the org.acme namespace" participant: "ANY" - operation: ALL - resource: "org.acme.Car" - transaction(tx): "org.acme.Transaction" - condition: (tx.asset.colour === 'blue') + operation: READ + resource: "org.acme.**" action: ALLOW }`; const aclFile = new AclFile('test.acl', modelManager, aclContents); @@ -235,19 +235,114 @@ describe('AclFile', () => { aclFile.getDefinitions().should.equal(aclContents); const r7 = aclFile.getAclRules()[0]; r7.getName().should.equal('R7'); - r7.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); - r7.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); - r7.getVerbs().should.deep.equal(['ALL']); + r7.getNoun().getFullyQualifiedName().should.equal('org.acme.**'); + r7.getVerbs().should.deep.equal(['READ']); (r7.getParticipant() === null).should.be.true; - r7.getTransaction().getFullyQualifiedName().should.equal('org.acme.Transaction'); - r7.getTransaction().getVariableName().should.equal('tx'); - r7.getPredicate().getExpression().should.equal('tx.asset.colour === \'blue\''); + (r7.getTransaction() === null).should.be.true; + r7.getPredicate().getExpression().should.equal('true'); r7.getAction().should.equal('ALLOW'); - r7.getDescription().should.equal('Regulators can do something in a org.acme.Transaction transaction'); + r7.getDescription().should.equal('Everyone can read all resources under the org.acme namespace'); }); - it('should parse a rule correctly', () => { + it('should parse R8 rule correctly', () => { const aclContents = `rule R8 { + description: "Drivers can do something in a org.acme.Transaction transaction" + participant: "ANY" + operation: ALL + resource: "org.acme.Car" + transaction: "org.acme.Transaction" + action: ALLOW + }`; + const aclFile = new AclFile('test.acl', modelManager, aclContents); + aclFile.getAclRules().length.should.equal(1); + aclFile.getDefinitions().should.equal(aclContents); + const r8 = aclFile.getAclRules()[0]; + r8.getName().should.equal('R8'); + r8.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); + r8.getVerbs().should.deep.equal(['ALL']); + (r8.getParticipant() === null).should.be.true; + r8.getTransaction().getFullyQualifiedName().should.equal('org.acme.Transaction'); + (r8.getTransaction().getVariableName() === null).should.be.true; + r8.getPredicate().getExpression().should.equal('true'); + r8.getAction().should.equal('ALLOW'); + r8.getDescription().should.equal('Drivers can do something in a org.acme.Transaction transaction'); + }); + + it('should parse R9 rule correctly', () => { + const aclContents = `rule R9 { + description: "Drivers can do something with transactions in the org.acme namespace" + participant: "ANY" + operation: ALL + resource: "org.acme.Car" + transaction: "org.acme.*" + action: ALLOW + }`; + const aclFile = new AclFile('test.acl', modelManager, aclContents); + aclFile.getAclRules().length.should.equal(1); + aclFile.getDefinitions().should.equal(aclContents); + const r9 = aclFile.getAclRules()[0]; + r9.getName().should.equal('R9'); + r9.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); + r9.getVerbs().should.deep.equal(['ALL']); + (r9.getParticipant() === null).should.be.true; + r9.getTransaction().getFullyQualifiedName().should.equal('org.acme.*'); + (r9.getTransaction().getVariableName() === null).should.be.true; + r9.getPredicate().getExpression().should.equal('true'); + r9.getAction().should.equal('ALLOW'); + r9.getDescription().should.equal('Drivers can do something with transactions in the org.acme namespace'); + }); + + it('should parse R10 rule correctly', () => { + const aclContents = `rule R10 { + description: "Drivers can do something with transactions under the org.acme namespace" + participant: "ANY" + operation: ALL + resource: "org.acme.Car" + transaction: "org.acme.**" + action: ALLOW + }`; + const aclFile = new AclFile('test.acl', modelManager, aclContents); + aclFile.getAclRules().length.should.equal(1); + aclFile.getDefinitions().should.equal(aclContents); + const r10 = aclFile.getAclRules()[0]; + r10.getName().should.equal('R10'); + r10.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); + r10.getVerbs().should.deep.equal(['ALL']); + (r10.getParticipant() === null).should.be.true; + r10.getTransaction().getFullyQualifiedName().should.equal('org.acme.**'); + (r10.getTransaction().getVariableName() === null).should.be.true; + r10.getPredicate().getExpression().should.equal('true'); + r10.getAction().should.equal('ALLOW'); + r10.getDescription().should.equal('Drivers can do something with transactions under the org.acme namespace'); + }); + + it('should parse R11 rule correctly', () => { + const aclContents = `rule R11 { + description: "Regulators can do something in a org.acme.Transaction transaction" + participant: "ANY" + operation: ALL + resource: "org.acme.Car" + transaction(tx): "org.acme.Transaction" + condition: (tx.asset.colour === 'blue') + action: ALLOW + }`; + const aclFile = new AclFile('test.acl', modelManager, aclContents); + aclFile.getAclRules().length.should.equal(1); + aclFile.getDefinitions().should.equal(aclContents); + const r11 = aclFile.getAclRules()[0]; + r11.getName().should.equal('R11'); + r11.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); + r11.getVerbs().should.deep.equal(['ALL']); + (r11.getParticipant() === null).should.be.true; + r11.getTransaction().getFullyQualifiedName().should.equal('org.acme.Transaction'); + r11.getTransaction().getVariableName().should.equal('tx'); + r11.getPredicate().getExpression().should.equal('tx.asset.colour === \'blue\''); + r11.getAction().should.equal('ALLOW'); + r11.getDescription().should.equal('Regulators can do something in a org.acme.Transaction transaction'); + }); + + it('should parse R12 rule correctly', () => { + const aclContents = `rule R12 { description: "Fred can CREATE, READ, and UPDATE the car ABC123" participant: "org.acme.Driver#Fred" operation: CREATE, READ, UPDATE @@ -257,12 +352,20 @@ describe('AclFile', () => { const aclFile = new AclFile('test.acl', modelManager, aclContents); aclFile.getAclRules().length.should.equal(1); aclFile.getDefinitions().should.equal(aclContents); - const r8 = aclFile.getAclRules()[0]; - r8.getVerbs().should.deep.equal(['CREATE', 'READ', 'UPDATE']); + const r12 = aclFile.getAclRules()[0]; + r12.getName().should.equal('R12'); + r12.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); + r12.getNoun().getInstanceIdentifier().should.equal('ABC123'); + r12.getVerbs().should.deep.equal(['CREATE', 'READ', 'UPDATE']); + r12.getParticipant().getFullyQualifiedName().should.equal('org.acme.Driver'); + r12.getParticipant().getInstanceIdentifier().should.equal('Fred'); + (r12.getParticipant().getVariableName() === null).should.be.true; + r12.getAction().should.equal('ALLOW'); + r12.getDescription().should.equal('Fred can CREATE, READ, and UPDATE the car ABC123'); }); - it('should parse a rule correctly', () => { - const aclContents = `rule R9 { + it('should parse R13 rule correctly', () => { + const aclContents = `rule R13 { description: "regulator with ID Bill can not update or delete a Car if they own it" participant(r): "org.acme.Regulator#Bill" operation: UPDATE, DELETE @@ -273,12 +376,18 @@ describe('AclFile', () => { const aclFile = new AclFile('test.acl', modelManager, aclContents); aclFile.getAclRules().length.should.equal(1); aclFile.getDefinitions().should.equal(aclContents); - const r9 = aclFile.getAclRules()[0]; - r9.getVerbs().should.deep.equal(['UPDATE', 'DELETE']); + const r13 = aclFile.getAclRules()[0]; + r13.getName().should.equal('R13'); + r13.getNoun().getFullyQualifiedName().should.equal('org.acme.Car'); + (r13.getNoun().getInstanceIdentifier() === null).should.be.true; + r13.getParticipant().getFullyQualifiedName().should.equal('org.acme.Regulator'); + r13.getParticipant().getInstanceIdentifier().should.equal('Bill'); + r13.getParticipant().getVariableName().should.equal('r'); + r13.getVerbs().should.deep.equal(['UPDATE', 'DELETE']); }); it('should fail to parse a rule with ALL and another verb', () => { - const aclContents = `rule R9 { + const aclContents = `rule A { description: "regulator with ID Bill can not update or delete a Car if they own it" participant(r): "org.acme.Regulator#Bill" operation: ALL, DELETE @@ -288,31 +397,45 @@ describe('AclFile', () => { }`; (() => { new AclFile('test.acl', modelManager, aclContents); - }).should.throw(/Expected.*but.*found/); + }).should.throw(ParseException, /Expected.*but.*found/); }); - it('should fail to parse a rule with duplicate verbs', () => { - const aclContents = `rule R9 { + it('should fail to parse a rule with a glob and instance ID', () => { + const aclContents = `rule B { description: "regulator with ID Bill can not update or delete a Car if they own it" - participant(r): "org.acme.Regulator#Bill" - operation: DELETE, READ, DELETE + participant(r): "org.acme.Regulator.*#Bill" + operation: UPDATE, DELETE resource(c): "org.acme.Car" condition: (c.owner == r) action: DENY }`; - const aclFile = new AclFile('test.acl', modelManager, aclContents); (() => { - aclFile.validate(); - }).should.throw(/has been specified more than once/); + new AclFile('test.acl', modelManager, aclContents); + }).should.throw(ParseException, /Expected.*but.*found/); + }); + + it('should fail to parse a rule with a recursive glob and instance ID', () => { + const aclContents = `rule C { + description: "regulator with ID Bill can not update or delete a Car if they own it" + participant(r): "org.acme.Regulator.**#Bill" + operation: UPDATE, DELETE + resource(c): "org.acme.Car" + condition: (c.owner == r) + action: DENY + }`; + (() => { + new AclFile('test.acl', modelManager, aclContents); + }).should.throw(ParseException, /Expected.*but.*found/); }); + it('should parse correctly and preserve order', () => { const aclFile = new AclFile('test.acl', modelManager, testAcl); - aclFile.getAclRules().length.should.equal(9); + aclFile.getAclRules().length.should.equal(13); aclFile.getDefinitions().should.equal(testAcl); aclFile.getAclRules().map((aclRule) => { return aclRule.getName(); - }).should.deep.equal(['R1', 'R2', 'R3', 'R4', 'R5', 'R6', 'R7', 'R8' , 'R9']); + }).should.deep.equal(['R1', 'R2', 'R3', 'R4', 'R5', 'R6', 'R7', 'R8' , 'R9', 'R10', 'R11', 'R12', 'R13']); }); }); @@ -329,7 +452,7 @@ describe('AclFile', () => { description: "some rule" participant: "ANY" operation: ALL - resource: "org.acme" + resource: "org.acme.*" action: ALLOW } @@ -337,7 +460,7 @@ describe('AclFile', () => { description: "some rule" participant: "ANY" operation: ALL - resource: "org.acme" + resource: "org.acme.*" action: ALLOW }`; const aclFile = new AclFile('test.acl', modelManager, aclContents); @@ -346,6 +469,50 @@ describe('AclFile', () => { }).should.throw(/Found two or more ACL rules with the name/); }); + it('should fail to validate a rule with duplicate verbs', () => { + const aclContents = `rule A { + description: "regulator with ID Bill can not update or delete a Car if they own it" + participant(r): "org.acme.Regulator#Bill" + operation: DELETE, READ, DELETE + resource(c): "org.acme.Car" + condition: (c.owner == r) + action: DENY + }`; + const aclFile = new AclFile('test.acl', modelManager, aclContents); + (() => { + aclFile.validate(); + }).should.throw(/has been specified more than once/); + }); + + it('should fail to validate a rule when participant is not a participant', () => { + const aclContents = `rule B { + description: "org.acme.Car is an asset, not a particpant" + participant: "org.acme.Car" + operation: READ + resource: "org.acme.Car" + action: ALLOW + }`; + const aclFile = new AclFile('test.acl', modelManager, aclContents); + (() => { + aclFile.validate(); + }).should.throw(IllegalModelException, /The participant.*must be a participant/); + }); + + it('should fail to validate a rule when transaction is not a transaction', () => { + const aclContents = `rule D { + description: "org.acme.Car is an asset, not a transaction" + participant: "ANY" + operation: ALL + resource: "org.acme.*" + transaction: "org.acme.Car" + action: ALLOW + }`; + const aclFile = new AclFile('test.acl', modelManager, aclContents); + (() => { + aclFile.validate(); + }).should.throw(IllegalModelException, /The transaction.*must be a transaction/); + }); + }); describe('#accept', () => { diff --git a/packages/composer-common/test/acl/aclrule.js b/packages/composer-common/test/acl/aclrule.js index a513fde2f5..6a4208ebae 100644 --- a/packages/composer-common/test/acl/aclrule.js +++ b/packages/composer-common/test/acl/aclrule.js @@ -18,6 +18,8 @@ const AclRule = require('../../lib/acl/aclrule'); const AclFile = require('../../lib/acl/aclfile'); const ModelManager = require('../../lib/modelmanager'); const ModelFile = require('../../lib/introspect/modelfile'); +const AssetDeclaration = require('../../lib/introspect/assetdeclaration'); +const ParticipantDeclaration = require('../../lib/introspect/participantdeclaration'); const should = require('chai').should(); const sinon = require('sinon'); @@ -28,6 +30,8 @@ describe('AclRule', () => { let aclFile; let mockModelManager; let mockModelFile; + let mockAssetDeclaration; + let mockParticipantDeclaration; let sandbox; const ast = { @@ -65,9 +69,11 @@ describe('AclRule', () => { mockModelManager = sinon.createStubInstance(ModelManager); aclFile.getModelManager.returns(mockModelManager); mockModelFile = sinon.createStubInstance(ModelFile); + mockAssetDeclaration = sinon.createStubInstance(AssetDeclaration); + mockParticipantDeclaration = sinon.createStubInstance(ParticipantDeclaration); mockModelManager.getModelFile.withArgs('org.acme').returns(mockModelFile); - mockModelFile.getLocalType.withArgs('Car').returns('fake'); - mockModelFile.getLocalType.withArgs('Driver').returns('fake'); + mockModelFile.getLocalType.withArgs('Car').returns(mockAssetDeclaration); + mockModelFile.getLocalType.withArgs('Driver').returns(mockParticipantDeclaration); sandbox = sinon.sandbox.create(); }); diff --git a/packages/composer-common/test/acl/modelbinding.js b/packages/composer-common/test/acl/modelbinding.js index 53944f11ce..b0127f9a5c 100644 --- a/packages/composer-common/test/acl/modelbinding.js +++ b/packages/composer-common/test/acl/modelbinding.js @@ -30,18 +30,16 @@ describe('ModelBinding', () => { let modelManager; let sandbox; - const namespaceAst = {'type':'Binding','qualifiedName':'org.acme'}; + const namespaceAst = {'type':'Binding','qualifiedName':'org.acme.*'}; + const recursiveNamespaceAst = {'type':'Binding','qualifiedName':'org.**'}; const classAst = {'type':'Binding','qualifiedName':'org.acme.Car'}; const classWithIdentifierAst = {'type':'Binding','qualifiedName':'org.acme.Car','instanceId':'ABC123'}; - const propertyAst = {'type':'Binding','qualifiedName':'org.acme.Car.assetId'}; - const propertyWithIdentifierAst = {'type':'Binding','qualifiedName':'org.acme.Car.assetId','instanceId':'ABC123'}; const variableAst = {'type':'Identifier','name':'dan'}; const missingClass = {'type':'Binding','qualifiedName':'org.acme.Missing','instanceId':'ABC123'}; - const missingNamespace = {'type':'Binding','qualifiedName':'org.missing.Missing'}; - const missingClassWithProperty = {'type':'Binding','qualifiedName':'org.acme.Missing.missing','instanceId':'ABC123'}; - const missingProperty = {'type':'Binding','qualifiedName':'org.acme.Car.missing','instanceId':'ABC123'}; - const missing = {'type':'Binding','qualifiedName':'org.missing.Missing','instanceId':'ABC123'}; + const missingNamespace = {'type':'Binding','qualifiedName':'org.missing.Missing.*'}; + const missingRecursiveNamespace = {'type':'Binding','qualifiedName':'org.missing.Missing.**'}; + const missing = {'type':'Binding','qualifiedName':'org.missing.Missing.*','instanceId':'ABC123'}; beforeEach(() => { aclFile = sinon.createStubInstance(AclFile); @@ -91,7 +89,13 @@ describe('ModelBinding', () => { it('should validate correct contents for a namespace reference', () => { modelBinding = new ModelBinding( aclRule, namespaceAst ); modelBinding.validate(); - modelBinding.toString().should.equal('ModelBinding org.acme'); + modelBinding.toString().should.equal('ModelBinding org.acme.*'); + }); + + it('should validate correct contents for a recursive namespace reference', () => { + modelBinding = new ModelBinding( aclRule, recursiveNamespaceAst ); + modelBinding.validate(); + modelBinding.toString().should.equal('ModelBinding org.**'); }); it('should validate correct contents for a class reference', () => { @@ -106,18 +110,6 @@ describe('ModelBinding', () => { modelBinding.toString().should.equal('ModelBinding org.acme.Car#ABC123'); }); - it('should validate correct contents for a property reference', () => { - modelBinding = new ModelBinding( aclRule, propertyAst ); - modelBinding.validate(); - modelBinding.toString().should.equal('ModelBinding org.acme.Car.assetId'); - }); - - it('should validate correct contents for a property reference with an identifier', () => { - modelBinding = new ModelBinding( aclRule, propertyWithIdentifierAst ); - modelBinding.validate(); - modelBinding.toString().should.equal('ModelBinding org.acme.Car.assetId#ABC123'); - }); - it('should validate correct contents for a class reference with a variable binding', () => { modelBinding = new ModelBinding( aclRule, classAst, variableAst ); modelBinding.validate(); @@ -130,18 +122,6 @@ describe('ModelBinding', () => { modelBinding.toString().should.equal('ModelBinding org.acme.Car#ABC123:dan'); }); - it('should validate correct contents for a property reference with a variable binding', () => { - modelBinding = new ModelBinding( aclRule, propertyAst, variableAst ); - modelBinding.validate(); - modelBinding.toString().should.equal('ModelBinding org.acme.Car.assetId:dan'); - }); - - it('should validate correct contents for a property reference with an identifier and with a variable binding', () => { - modelBinding = new ModelBinding( aclRule, propertyWithIdentifierAst, variableAst ); - modelBinding.validate(); - modelBinding.toString().should.equal('ModelBinding org.acme.Car.assetId#ABC123:dan'); - }); - it('should detect reference to missing class', () => { (() => { modelBinding = new ModelBinding( aclRule, missingClass ); @@ -156,25 +136,18 @@ describe('ModelBinding', () => { }).should.throw(/Failed to find namespace org.missing.Missing/); }); - it('should detect reference to missing namespace with variable name', () => { - (() => { - modelBinding = new ModelBinding( aclRule, missing ); - modelBinding.validate(); - }).should.throw(/Failed to resolve org.missing.Missing/); - }); - - it('should detect reference to missing property', () => { + it('should detect reference to missing recursive namespace', () => { (() => { - modelBinding = new ModelBinding( aclRule, missingProperty ); + modelBinding = new ModelBinding( aclRule, missingRecursiveNamespace ); modelBinding.validate(); - }).should.throw(/Failed to find property org.acme.Car.missing/); + }).should.throw(/Failed to find namespace org.missing.Missing/); }); - it('should detect reference to missing class with property', () => { + it('should detect reference to missing namespace with variable name', () => { (() => { - modelBinding = new ModelBinding( aclRule, missingClassWithProperty ); + modelBinding = new ModelBinding( aclRule, missing ); modelBinding.validate(); - }).should.throw(/Failed to find class org.acme.Missing/); + }).should.throw(/Failed to find namespace org.missing.Missing/); }); }); @@ -200,14 +173,14 @@ describe('ModelBinding', () => { should.equal(modelBinding.getClassDeclaration(), null); }); - it('should return the class declaration for a class binding', () => { - modelBinding = new ModelBinding( aclRule, classAst ); + it('should return null for a recursive namespace binding', () => { + modelBinding = new ModelBinding( aclRule, recursiveNamespaceAst ); modelBinding.validate(); - modelBinding.getClassDeclaration().getFullyQualifiedName().should.equal('org.acme.Car'); + should.equal(modelBinding.getClassDeclaration(), null); }); - it('should return the class declaration for a property binding', () => { - modelBinding = new ModelBinding( aclRule, propertyAst ); + it('should return the class declaration for a class binding', () => { + modelBinding = new ModelBinding( aclRule, classAst ); modelBinding.validate(); modelBinding.getClassDeclaration().getFullyQualifiedName().should.equal('org.acme.Car'); }); diff --git a/packages/composer-common/test/acl/test.acl b/packages/composer-common/test/acl/test.acl index 330e056ae9..b0e86b091f 100644 --- a/packages/composer-common/test/acl/test.acl +++ b/packages/composer-common/test/acl/test.acl @@ -16,31 +16,46 @@ rule R2 { } rule R3 { - description: "Driver can change the ownership of a car that they own" - participant(d): "org.acme.Driver" - operation: UPDATE - resource(o): "org.acme.Car.owner" - condition: (o == d) + description: "regulators can perform all operations on Cars" + participant: "org.acme.Regulator" + operation: ALL + resource: "org.acme.Car" action: ALLOW } rule R4 { - description: "regulators can perform all operations on Cars" - participant: "org.acme.Regulator" + description: "anyone in the org.acme namespace can perform all operations on Cars" + participant: "org.acme.*" operation: ALL resource: "org.acme.Car" action: ALLOW } rule R5 { + description: "anyone under the org.acme namespace can perform all operations on Cars" + participant: "org.acme.**" + operation: ALL + resource: "org.acme.Car" + action: ALLOW +} + +rule R6 { description: "Everyone can read all resources in the org.acme namespace" participant: "ANY" operation: READ - resource: "org.acme" + resource: "org.acme.*" action: ALLOW } -rule R6 { +rule R7 { + description: "Everyone can read all resources under the org.acme namespace" + participant: "ANY" + operation: READ + resource: "org.acme.**" + action: ALLOW +} + +rule R8 { description: "Drivers can do something in a org.acme.Transaction transaction" participant: "ANY" operation: ALL @@ -49,7 +64,25 @@ rule R6 { action: ALLOW } -rule R7 { +rule R9 { + description: "Drivers can do something with transactions in the org.acme namespace" + participant: "ANY" + operation: ALL + resource: "org.acme.Car" + transaction: "org.acme.*" + action: ALLOW +} + +rule R10 { + description: "Drivers can do something with transactions under the org.acme namespace" + participant: "ANY" + operation: ALL + resource: "org.acme.Car" + transaction: "org.acme.**" + action: ALLOW +} + +rule R11 { description: "Regulators can do something in a org.acme.Transaction transaction" participant: "ANY" operation: ALL @@ -59,7 +92,7 @@ rule R7 { action: ALLOW } -rule R8 { +rule R12 { description: "Fred can CREATE, READ, and UPDATE the car ABC123" participant: "org.acme.Driver#Fred" operation: CREATE, READ, UPDATE @@ -67,7 +100,7 @@ rule R8 { action: ALLOW } -rule R9 { +rule R13 { description: "regulator with ID Bill can not update or delete a Car if they own it" participant(r): "org.acme.Regulator#Bill" operation: UPDATE, DELETE diff --git a/packages/composer-common/test/businessnetworkdefinition.js b/packages/composer-common/test/businessnetworkdefinition.js index 6ea59e80dc..7fff0e5c6a 100644 --- a/packages/composer-common/test/businessnetworkdefinition.js +++ b/packages/composer-common/test/businessnetworkdefinition.js @@ -100,7 +100,7 @@ describe('BusinessNetworkDefinition', () => { businessNetwork.getMetadata().getPackageJson().customKey.should.equal('custom value'); Object.keys(businessNetwork.modelManager.modelFiles).should.have.length(3); Object.keys(businessNetwork.scriptManager.scripts).should.have.length(2); - businessNetwork.aclManager.getAclRules().should.have.length(5); + businessNetwork.aclManager.getAclRules().should.have.length(4); const intro = businessNetwork.getIntrospector(); intro.getClassDeclarations().length.should.equal(25); diff --git a/packages/composer-common/test/data/zip/test-archive-dotfolders/permissions.acl b/packages/composer-common/test/data/zip/test-archive-dotfolders/permissions.acl index 3ba57239ef..25893994a9 100644 --- a/packages/composer-common/test/data/zip/test-archive-dotfolders/permissions.acl +++ b/packages/composer-common/test/data/zip/test-archive-dotfolders/permissions.acl @@ -17,15 +17,6 @@ rule R2 { } rule R3 { - description: "Driver can change the ownership of a car that they own" - participant(d): "concerto.Participant" - operation: UPDATE - resource(o): "org.acme.Vehicle.owner" - condition: (o == d) - action: ALLOW -} - -rule R4 { description: "regulators can perform all operations on Cars" participant: "org.acme.Regulator" operation: ALL @@ -33,10 +24,10 @@ rule R4 { action: ALLOW } -rule R5 { +rule R4 { description: "Everyone can read all resources in the org.acme namespace" participant: "ANY" operation: READ - resource: "org.acme" + resource: "org.acme.*" action: ALLOW } \ No newline at end of file diff --git a/packages/composer-common/test/data/zip/test-archive.zip b/packages/composer-common/test/data/zip/test-archive.zip index 0c18da48ed783bf195ae848c53ec449cae75bbf0..4b4b44949c4725830aed131ca2d2bc09fc0ebbbb 100644 GIT binary patch delta 2148 zcmdn2GeNsPz?+$civa}QhwShIGoS`}XW z`d)i&r~6-a{w;5O>%sNPXQqVne@+9=3neT zI_rFNU*xCHuAe5PhbxIj`Gl(mtA3trwR5}V8hhTivxQyl7WeNJ?7g*MZSsZa$TZHJ z+@*b6>wCRwi+cYo?yQ@4<>f~UCd*rPdnKhq9*1!pTeoKJo(3-OTWT?HV;(ME5_i4v zWu~F=^QW2jm(O2w=05j@SDUJ@I)6M->0B?4VsU;pe&oeVHEaqWAimNkC z&M2`fiHNJo%Y}vcps{4XZb}3&k~gnpnZZ=g&XK@iU@?`6fnhTn#4JckK{tyXXjX21 zN@`9qJh4E{!Q%j7gr?-gqMX#k;#5$I0VNk}U~&N(zyfy38TK#Ce(W4#w=Qh>2=vth zPJF&XHZ(skIkgCw1W^qInK_w@=co@m2UkVmxzI;6OG}WkWei(DMLIEJ#ewPE1b)=8yb5m|=@uuYg^bD3A^e`lNFw zn^SE939hN7oC~Ka zY_p1-*Z+8#TFn~mB%Y@WFU>cSjNPm-{rVo?fH#_zi|2}{9-Y28pe4gg^b=AvFL5o!e z?vp2U!Wb60v`)J6NC;>MJBQKyssnt$*ahJLZ$>6jW=Qq|7OV9rk<7vX%*qhp@YWH; zfRxJ|@NyZV9Z@PHmjQxEx|cK>L3Jb2GPsQ9fS1u=qtuuoW`S}#av30rVphQ9xx%vb z5fHtg9FOb^%t8X$6E^G&uv`@iGyqh;APhj*jhyR+QS2^+8BkxWk6M0kz{(G(a}cH> zXLwZ8Hgm(BlZ;Ys;5H1F*Xu!<9W{)P0ey<4$iQwEC=ns&bsiLNN(wNbl@$R~<<13>l6eY-xw2*-&0|FQCV{UGl8_OG`-zCAO`y2?%KoArOCjT`L0 z*hgrbyZq+W%{B7JCQX^TVE)=VhVJ6k)eIkdzVJOSY4A!ey=%Sf>VCN@WmQUDkotGC=C8{a))p^1k^W_)`JzDSEu}nF z+NX6I*RH*?aa&;8O8<2e>rF16pKN6-=dR#>d%1aXbJiWMgEyvboh%-Z=y|(k^<3?m zDOX-w8QAKkJ=Hmgl*7YO*kzxWAj|mJ07E_rR7&fyp zFh~MpB9}!P921~u#Egl}(^zIO#lX@5`&4I?bifWw2f6tvsX4`vq|keA3jU-3GC=Is zg$*BpN#Ow}1A{Qo0Uv}#!43dv#^QjD>|dCrVMz((0j`R|b%%fn%oCW>K>9CmL-b?! zz~s-|M?H|W^WF71$O5$f0tYxH!rY&mUzJ!?qL*Bf4^Pxs9K*uP&g={`ZAVg$SJJtY z%_%^~)B`aOFfLj;;YF)0_pY0=4|m+-CGFs5Hkw{13$=| z)S}$X;^NHwykfn?6~W*XUVbK8E;^owofynX76>X$i1@IF*#`Yp1;vhx;)>iY%d{t*o6LYr#l z**{J2+OnSIWr>+gZ?#^au~Ad^EYD=&7|*_xkWDtS-)|fKOAb{ww7=6|vb-hA^vK0$ zqParHw$Ip;pjpxS_w#Cr?-xt#>T7!K7N6MM-}I(s_Oc{5!8a|M4;7nNuFy(Xp4zxf zZ$`aig286Hq`Rx$I=)rmxcpHnpr&t|mCw8F!q!RWPwV_uHG5Ea;H>&vdF>W|zKSHy z2?<7wUG*C2KOenKvEh?SSGkwF!}N*jWa*OHot?WxOjm5Zn)}*V)!6;vx?I6{jZg8b zw3fWuyX(-ZP~At_Z|}wAhg-*YZwcGNnkH@cDKlVG{o3$$v8}h$j-PA#vvpqh_fKXX z^KSQj^IG&+Ev2>D?#@C-kNDf)eHCtsm7cX|)46msn!S4J@|&vf6yA$Nibh~AVgMxq zqxn?__<(E><^)DwkgKDMud7~e3chFysn5tPRsfjWcf#Ue-w`ke zvs6JgW*;{LsEonyJBWio2BDTO$OfGS)_u^@61lX&7ibV;!39k{N=buku8aT!TDb!> z5>)Qs2|AFGsAUeaiN`=DqLn~3pa{WfDi^S($Ceq8O>GcjK+YPtjAI2>ObiTMK { + ModelUtil.isWildcardName('org.acme.baz.**').should.be.false; + }); + + }); + + describe('#isRecursiveWildcardName', () => { + + it('should return false for a name without a recursive wildcard', () => { + ModelUtil.isRecursiveWildcardName('Foo').should.be.false; + }); + + it('should return true for a name with a recursive wildcard', () => { + ModelUtil.isRecursiveWildcardName('**').should.be.true; + }); + + it('should return false for a fully qualified name without a recursive wildcard', () => { + ModelUtil.isRecursiveWildcardName('org.acme.baz.Foo').should.be.false; + }); + + it('should return true for a fully qualified name with a recursive wildcard', () => { + ModelUtil.isRecursiveWildcardName('org.acme.baz.**').should.be.true; + }); + + it('should return false for a fully qualified name with a wildcard', () => { + ModelUtil.isRecursiveWildcardName('org.acme.baz.*').should.be.false; + }); + + }); + + describe('#isMatchingType', () => { + + let mockModelManager; + let mockModelFile; + let mockClassDeclaration; + let type; + + beforeEach(() => { + mockModelManager = sinon.createStubInstance(ModelManager); + mockModelFile = sinon.createStubInstance(ModelFile); + mockClassDeclaration = sinon.createStubInstance(ClassDeclaration); + + mockModelManager.getModelFile.returns(mockModelFile); + mockModelFile.getType.returns(mockClassDeclaration); + mockClassDeclaration.getFullyQualifiedName.returns('org.acme.baz.Foo'); + mockClassDeclaration.getAllSuperTypeDeclarations.returns([]); + + type = new Typed(mockModelManager, 'org.acme.baz', 'Foo' ); + }); + + it('should return true for an exact match', () => { + ModelUtil.isMatchingType(type, 'org.acme.baz.Foo').should.be.true; + }); + + it('should return false for a non-match', () => { + ModelUtil.isMatchingType(type, 'org.acme.baz.Bar').should.be.false; + }); + + it('should return true for a wildcard namespace match', () => { + ModelUtil.isMatchingType(type, 'org.acme.baz.*').should.be.true; + }); + + it('should return false for a non-matching wildcard namespace', () => { + ModelUtil.isMatchingType(type, 'org.doge.baz.*').should.be.false; + }); + + it('should return false for an ancestor namespace wildcard match', () => { + ModelUtil.isMatchingType(type, 'org.acme.*').should.be.false; + }); + + it('should return true for a recursive wildcard match', () => { + ModelUtil.isMatchingType(type, 'org.acme.**').should.be.true; + }); + + it('should return false for a non-matching recursive wildcard', () => { + ModelUtil.isMatchingType(type, 'org.ac.**').should.be.false; + }); }); describe('#getNamespace', function() { diff --git a/packages/composer-connector-embedded/package.json b/packages/composer-connector-embedded/package.json index d4438fe961..650fccbb27 100644 --- a/packages/composer-connector-embedded/package.json +++ b/packages/composer-connector-embedded/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-embedded", - "version": "0.8.1", + "version": "0.9.0", "description": "The embedded client connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -52,9 +52,9 @@ "watchify": "^3.7.0" }, "dependencies": { - "composer-common": "^0.8.1", - "composer-runtime": "^0.8.1", - "composer-runtime-embedded": "^0.8.1" + "composer-common": "^0.9.0", + "composer-runtime": "^0.9.0", + "composer-runtime-embedded": "^0.9.0" }, "nyc": { "exclude": [ diff --git a/packages/composer-connector-hlf/package.json b/packages/composer-connector-hlf/package.json index f020e66db2..70dd6fed9b 100644 --- a/packages/composer-connector-hlf/package.json +++ b/packages/composer-connector-hlf/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-hlf", - "version": "0.8.1", + "version": "0.9.0", "description": "The Hyperledger Fabric Client connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -40,8 +40,8 @@ "logError": true }, "dependencies": { - "composer-common": "^0.8.1", - "composer-runtime-hlf": "^0.8.1", + "composer-common": "^0.9.0", + "composer-runtime-hlf": "^0.9.0", "fs-extra": "^1.0.0", "hfc": "^0.6.5", "semver": "^5.3.0", diff --git a/packages/composer-connector-hlfv1/package.json b/packages/composer-connector-hlfv1/package.json index 25f25005f7..512926747d 100644 --- a/packages/composer-connector-hlfv1/package.json +++ b/packages/composer-connector-hlfv1/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-hlfv1", - "version": "0.8.1", + "version": "0.9.0", "description": "The Hyperledger Fabric v1.x Client connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -40,8 +40,8 @@ "logError": true }, "dependencies": { - "composer-common": "^0.8.1", - "composer-runtime-hlfv1": "^0.8.1", + "composer-common": "^0.9.0", + "composer-runtime-hlfv1": "^0.9.0", "fabric-ca-client": "1.0.0-beta", "fabric-client": "1.0.0-beta", "fs-extra": "^1.0.0", diff --git a/packages/composer-connector-proxy/package.json b/packages/composer-connector-proxy/package.json index 7a573a2022..faa4a61622 100644 --- a/packages/composer-connector-proxy/package.json +++ b/packages/composer-connector-proxy/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-proxy", - "version": "0.8.1", + "version": "0.9.0", "description": "The proxying client connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -54,7 +54,7 @@ "watchify": "^3.7.0" }, "dependencies": { - "composer-common": "^0.8.1", + "composer-common": "^0.9.0", "socket.io-client": "^1.7.3" }, "nyc": { diff --git a/packages/composer-connector-server/package.json b/packages/composer-connector-server/package.json index a5f3343bb7..3b633a98c9 100644 --- a/packages/composer-connector-server/package.json +++ b/packages/composer-connector-server/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-server", - "version": "0.8.1", + "version": "0.9.0", "description": "The remote connector server for Hyperledger Composer", "engines": { "node": ">=6", @@ -44,10 +44,10 @@ "logError": true }, "dependencies": { - "composer-common": "^0.8.1", - "composer-connector-embedded": "^0.8.1", - "composer-connector-hlf": "^0.8.1", - "composer-connector-hlfv1": "^0.8.1", + "composer-common": "^0.9.0", + "composer-connector-embedded": "^0.9.0", + "composer-connector-hlf": "^0.9.0", + "composer-connector-hlfv1": "^0.9.0", "serializerr": "^1.0.3", "socket.io": "^1.7.3", "uuid": "^3.0.1", diff --git a/packages/composer-connector-web/package.json b/packages/composer-connector-web/package.json index e4876524e5..1eb056d00b 100644 --- a/packages/composer-connector-web/package.json +++ b/packages/composer-connector-web/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-web", - "version": "0.8.1", + "version": "0.9.0", "description": "The web client connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -62,9 +62,9 @@ "watchify": "^3.7.0" }, "dependencies": { - "composer-common": "^0.8.1", - "composer-runtime": "^0.8.1", - "composer-runtime-web": "^0.8.1", + "composer-common": "^0.9.0", + "composer-runtime": "^0.9.0", + "composer-runtime-web": "^0.9.0", "uuid": "^3.0.1" } } diff --git a/packages/composer-cucumber-steps/features/basic-sample-network.bna b/packages/composer-cucumber-steps/features/basic-sample-network.bna index cf9701b0115a29815056e432aa43fc9047f112f7..8d7797b7bd3615e3f863e2621258b018f959ddbb 100644 GIT binary patch delta 1787 zcmZuxO>7%Q6kgkLQm-2VO-hQO(sTuFY}xB15+J1nB^3>Y9EfO&gsLEq*JJO5{nO5@ zYZF;UNC=4wpsB)vOF1DV4pg}yai9_sQjZ8YaB2nDN(l9YxbS9n*Rf5z-kf9qmZz8 z=SMt#keTGuUQ9jYvi9q66J1_qk?Ue8q+;0Ib)*Y+9Ak}#5dm&CvEGd@3%BNrTSIu1`fF_kHmxg;Q;O01SO}tz=2_vw~mNQIRlW#z9U1unC+3Djw0@#~43vr_8SYg>0N z|R!|plSnVi3JChZ+9y=$N=1LpVQ4L-4GX?(+ zZPdoww;4=-aTnSrzq5%)T-?#8bB$~PZ3H;PE;Zpu1Ml7(e>8YSl968eJCEhKe16@b3YBh8f!^o$NR1Hi!i`f9K2H;_Ja40+dmTWLu z2Ue!KDN_Yp9D-d7nxJz9r|jqk<+j=mLR90GH zTHIe)R8B*^gXZVPoDPd(@$}ijq$X#EPLIP=9%2$HPcoWR{4h={9|t8vX>ortJaKo_ uzZ`^k$)txcK=D|N-@)Wyq%oTIT@sO`+h;r5`DtmMfzKxyP5Wk1?*0R0cn`_| delta 993 zcmca;v|L9bz?+$civa{?tlR6grsC+WMivH!MwW>ZcFYEPmK!508I6^!lydWvGZGba zb&FDyON)v#%Tkqelu}X)a`G#Il6v~Z$wiq3CB^y>A-&>^$+k>iCi^lMPyWu_GP#>& zmt-xfMNB>XM|yx3^{`ALPvMFd&&f~EOxDXPPSE4x za>>tAFHuO#FG^3H%)>o}7v$E&7t!_V%nS_blh<+wP5!{EGuf13v9SPB>dx%nxnImP-w zTl5l>b5r#;`}4Ci3FIpTmlS2@r7I*B7pInZrcBNjG_?n52ZtJz0D6TXCo@SO)t86# zvgbYrN`WvBR0Y^un;!@!G1cpHrIqF-mt^MWDdgt`C*~I9q=pnF<`pM`#Waf1MKl$v zxfFn)EU`!-KPM$DF{d7t@%$S1d!tGX<+r5a+;x&p_Nw5W}d+72+xyAise?t)?|sEmwdy zBa;XNwzN6)zQV*RWCOM-6VL*0j3^)6m4;wlRYJ5 z!HFbOLON*^Fw&rj0^JPsw1Y6_IzkgXB|-eFmt2yMX3pfh64DXwKu16l52}yRlMlj< aOsEc69trSfWdq5w1L1T=28JpT5Dx$=8Y5T$ diff --git a/packages/composer-cucumber-steps/features/basic-sample-network/README.md b/packages/composer-cucumber-steps/features/basic-sample-network/README.md index 976af21b8c..2d9b2af245 100644 --- a/packages/composer-cucumber-steps/features/basic-sample-network/README.md +++ b/packages/composer-cucumber-steps/features/basic-sample-network/README.md @@ -2,16 +2,16 @@ This is the "Hello World" of Hyperledger Composer samples. -This sample defines a business network composed of a single asset type (`SampleAsset`), a single participant type (`SampleParticipant`), and a single transaction type (`SampleTransaction`). +This sample defines a business network composed of a single asset type (`SampleAsset`), a single participant type (`SampleParticipant`), a single transaction type (`SampleTransaction`), and a single event type (`SampleEvent`). -`SampleAssets` are owned by a `SampleParticipant`, and the value property on a `SampleAsset` can be modified by submitting a `SampleTransaction`. +`SampleAssets` are owned by a `SampleParticipant`, and the value property on a `SampleAsset` can be modified by submitting a `SampleTransaction`. The `SampleTransaction` emits a `SampleEvent` that notifies applications of the old and new values for each modified `SampleAsset`. To get started inside Hyperledger Composer you can click the Test tab and create instances of `SampleAsset` and `SampleParticipant`. Make sure that the owner property on the `SampleAsset` refers to a `SampleParticipant` that you have created. -You can then submit a `SampleTransaction`, making sure that the asset property refers to an asset that you created earlier. After the transaction has been processed you should see that the value property on the asset has been modified. +You can then submit a `SampleTransaction`, making sure that the asset property refers to an asset that you created earlier. After the transaction has been processed you should see that the value property on the asset has been modified, and that a `SampleEvent` has been emitted. -The logic for updating the asset when a `SampleTransaction` is processed is written in `logic.js`. +The logic for updating the asset when a `SampleTransaction` is processed is written in `sample.js`. -Don't forget you can import more advanced samples into Hyperledger Composer using the Import/Replace button. +Don't forget that you can import more advanced samples into Hyperledger Composer using the Import/Replace button. Have fun learning Hyperledger Composer! \ No newline at end of file diff --git a/packages/composer-cucumber-steps/features/basic-sample-network/lib/logic.js b/packages/composer-cucumber-steps/features/basic-sample-network/lib/sample.js similarity index 61% rename from packages/composer-cucumber-steps/features/basic-sample-network/lib/logic.js rename to packages/composer-cucumber-steps/features/basic-sample-network/lib/sample.js index 1e72a8e59f..18fbf97610 100644 --- a/packages/composer-cucumber-steps/features/basic-sample-network/lib/logic.js +++ b/packages/composer-cucumber-steps/features/basic-sample-network/lib/sample.js @@ -14,19 +14,34 @@ /** * Sample transaction processor function. + * @param {org.acme.sample.SampleTransaction} tx The sample transaction instance. + * @transaction */ -function onSampleTransaction(sampleTransaction) { - var oldValue = sampleTransaction.asset.value; - sampleTransaction.asset.value = sampleTransaction.newValue; +function sampleTransaction(tx) { + + // Save the old value of the asset. + var oldValue = tx.asset.value; + + // Update the asset with the new value. + tx.asset.value = tx.newValue; + + // Get the asset registry for the asset. return getAssetRegistry('org.acme.sample.SampleAsset') .then(function (assetRegistry) { - return assetRegistry.update(sampleTransaction.asset); + + // Update the asset in the asset registry. + return assetRegistry.update(tx.asset); + }) .then(function () { + + // Emit an event for the modified asset. var event = getFactory().newEvent('org.acme.sample', 'SampleEvent'); - event.assetId = sampleTransaction.asset.assetId; + event.asset = tx.asset; event.oldValue = oldValue; - event.newValue = sampleTransaction.newValue; + event.newValue = tx.newValue; emit(event); + }); + } diff --git a/packages/composer-cucumber-steps/features/basic-sample-network/models/org.acme.sample.cto b/packages/composer-cucumber-steps/features/basic-sample-network/models/sample.cto similarity index 95% rename from packages/composer-cucumber-steps/features/basic-sample-network/models/org.acme.sample.cto rename to packages/composer-cucumber-steps/features/basic-sample-network/models/sample.cto index c71560223d..c5231468df 100644 --- a/packages/composer-cucumber-steps/features/basic-sample-network/models/org.acme.sample.cto +++ b/packages/composer-cucumber-steps/features/basic-sample-network/models/sample.cto @@ -23,7 +23,7 @@ transaction SampleTransaction identified by transactionId { event SampleEvent identified by eventId { o String eventId - o String assetId + --> SampleAsset asset o String oldValue o String newValue } diff --git a/packages/composer-cucumber-steps/features/basic-sample-network/package.json b/packages/composer-cucumber-steps/features/basic-sample-network/package.json index c05059a267..5b05d11886 100644 --- a/packages/composer-cucumber-steps/features/basic-sample-network/package.json +++ b/packages/composer-cucumber-steps/features/basic-sample-network/package.json @@ -1 +1 @@ -{"name":"basic-sample-network","version":"0.0.9","description":"The Hello World of Hyperledger Composer samples","scripts":{"prepublish":"mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/basic-sample-network.bna","pretest":"npm run lint","lint":"eslint .","postlint":"npm run licchk","licchk":"license-check","postlicchk":"npm run doc","doc":"jsdoc --pedantic --recurse -c jsdoc.conf","test":"mocha --recursive","deploy":"./scripts/deploy.sh"},"repository":{"type":"git","url":"https://github.com/hyperledger/composer-sample-networks.git"},"keywords":["sample","network"],"author":"Hyperledger Composer","license":"Apache-2.0","devDependencies":{"browserfs":"^1.2.0","chai":"^3.5.0","composer-admin":"latest","composer-cli":"latest","composer-client":"latest","composer-connector-embedded":"latest","eslint":"^3.6.1","istanbul":"^0.4.5","jsdoc":"^3.4.1","license-check":"^1.1.5","mkdirp":"^0.5.1","mocha":"^3.2.0","moment":"^2.17.1"},"license-check-config":{"src":["**/*.js","!./coverage/**/*","!./node_modules/**/*","!./out/**/*","!./scripts/**/*"],"path":"header.txt","blocking":true,"logInfo":false,"logError":true}} \ No newline at end of file +{"name":"basic-sample-network","version":"0.1.0","description":"The Hello World of Hyperledger Composer samples","scripts":{"prepublish":"mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/basic-sample-network.bna","pretest":"npm run lint","lint":"eslint .","postlint":"npm run licchk","licchk":"license-check","postlicchk":"npm run doc","doc":"jsdoc --pedantic --recurse -c jsdoc.conf","test-inner":"mocha --recursive && cucumber-js","test-cover":"nyc npm run test-inner","test":"npm run test-inner"},"repository":{"type":"git","url":"https://github.com/hyperledger/composer-sample-networks.git"},"keywords":["sample","network"],"author":"Hyperledger Composer","license":"Apache-2.0","devDependencies":{"browserfs":"^1.2.0","chai":"^3.5.0","chai-as-promised":"^6.0.0","composer-admin":"latest","composer-cli":"latest","composer-client":"latest","composer-connector-embedded":"latest","composer-cucumber-steps":"latest","cucumber":"^2.2.0","eslint":"^3.6.1","istanbul":"^0.4.5","jsdoc":"^3.4.1","license-check":"^1.1.5","mkdirp":"^0.5.1","mocha":"^3.2.0","moment":"^2.17.1","nyc":"^11.0.2"},"license-check-config":{"src":["**/*.js","!./coverage/**/*","!./node_modules/**/*","!./out/**/*","!./scripts/**/*"],"path":"header.txt","blocking":true,"logInfo":false,"logError":true},"nyc":{"exclude":["coverage/**","features/**","out/**","test/**"],"reporter":["text-summary","html"],"all":true,"check-coverage":true,"statements":100,"branches":100,"functions":100,"lines":100}} \ No newline at end of file diff --git a/packages/composer-cucumber-steps/features/basic-sample-network/permissions.acl b/packages/composer-cucumber-steps/features/basic-sample-network/permissions.acl index 0b87760325..8496969d23 100644 --- a/packages/composer-cucumber-steps/features/basic-sample-network/permissions.acl +++ b/packages/composer-cucumber-steps/features/basic-sample-network/permissions.acl @@ -1,13 +1,14 @@ /** * Sample access control list. */ -rule EverybodyCanRead { +rule EverybodyCanReadEverything { description: "Allow all participants read access to all resources" participant: "org.acme.sample.SampleParticipant" operation: READ - resource: "org.acme.sample" + resource: "org.acme.sample.*" action: ALLOW } + rule EverybodyCanSubmitTransactions { description: "Allow all participants to submit transactions" participant: "org.acme.sample.SampleParticipant" @@ -16,8 +17,8 @@ rule EverybodyCanSubmitTransactions { action: ALLOW } -rule OwnerHasFullAccess { - description: "Allow participants full access to their resources" +rule OwnerHasFullAccessToTheirAssets { + description: "Allow all participants full access to their assets" participant(p): "org.acme.sample.SampleParticipant" operation: ALL resource(r): "org.acme.sample.SampleAsset" diff --git a/packages/composer-cucumber-steps/features/events.feature b/packages/composer-cucumber-steps/features/events.feature index ab42d81fea..5840f13df1 100644 --- a/packages/composer-cucumber-steps/features/events.feature +++ b/packages/composer-cucumber-steps/features/events.feature @@ -14,7 +14,7 @@ Feature: Event steps | asset | newValue | | 1 | 100 | Then I should have received the following event of type org.acme.sample.SampleEvent - | assetId | oldValue | newValue | + | asset | oldValue | newValue | | 1 | 10 | 100 | Scenario: then I should have received the following events @@ -23,7 +23,7 @@ Feature: Event steps | 1 | 100 | Then I should have received the following event """ - {"$class":"org.acme.sample.SampleEvent", "assetId":"1", "oldValue":"10", "newValue":"100"} + {"$class":"org.acme.sample.SampleEvent", "asset":"1", "oldValue":"10", "newValue":"100"} """ Scenario: then I should have received the following events of type (1) @@ -32,7 +32,7 @@ Feature: Event steps | 1 | 20 | | 1 | 100 | Then I should have received the following event of type org.acme.sample.SampleEvent - | assetId | oldValue | newValue | + | asset | oldValue | newValue | | 1 | 10 | 20 | | 1 | 20 | 100 | @@ -44,13 +44,13 @@ Feature: Event steps Then I should have received the following events """ [ - {"$class":"org.acme.sample.SampleEvent", "assetId":"1", "oldValue":"10", "newValue":"20"}, - {"$class":"org.acme.sample.SampleEvent", "assetId":"1", "oldValue":"20", "newValue":"100"} + {"$class":"org.acme.sample.SampleEvent", "asset":"1", "oldValue":"10", "newValue":"20"}, + {"$class":"org.acme.sample.SampleEvent", "asset":"1", "oldValue":"20", "newValue":"100"} ] """ Scenario: I should get an error when I do not receive an event of type (1) Then I should have received the following event of type org.acme.sample.SampleEvent - | assetId | oldValue | newValue | + | asset | oldValue | newValue | | 1 | 10 | 20 | And I should get an error matching /failed to find expected event/ diff --git a/packages/composer-cucumber-steps/package.json b/packages/composer-cucumber-steps/package.json index 48664f7ebc..1bbf3a5b65 100644 --- a/packages/composer-cucumber-steps/package.json +++ b/packages/composer-cucumber-steps/package.json @@ -1,6 +1,6 @@ { "name": "composer-cucumber-steps", - "version": "0.8.1", + "version": "0.9.0", "description": "A library of Cucumber steps for testing Hyperledger Composer", "main": "index.js", "scripts": { @@ -70,10 +70,10 @@ "dependencies": { "browserfs": "^1.1.0", "chai": "^3.5.0", - "composer-admin": "^0.8.1", - "composer-client": "^0.8.1", - "composer-common": "^0.8.1", - "composer-connector-embedded": "^0.8.1", + "composer-admin": "^0.9.0", + "composer-client": "^0.9.0", + "composer-common": "^0.9.0", + "composer-connector-embedded": "^0.9.0", "thenify-all": "^1.6.0" } } diff --git a/packages/composer-playground-api/package.json b/packages/composer-playground-api/package.json index c077d8c7d0..f6a3e7f1b2 100644 --- a/packages/composer-playground-api/package.json +++ b/packages/composer-playground-api/package.json @@ -1,6 +1,6 @@ { "name": "composer-playground-api", - "version": "0.8.1", + "version": "0.9.0", "description": "The REST API for the Hyperledger Composer Playground", "engines": { "node": ">=6", @@ -57,8 +57,8 @@ }, "dependencies": { "body-parser": "^1.17.0", - "composer-common": "^0.8.1", - "composer-connector-server": "^0.8.1", + "composer-common": "^0.9.0", + "composer-connector-server": "^0.9.0", "dotenv": "^4.0.0", "express": "^4.15.2", "http-status": "^1.0.1", diff --git a/packages/composer-playground/package.json b/packages/composer-playground/package.json index 318edf7a2c..ebd36bca25 100644 --- a/packages/composer-playground/package.json +++ b/packages/composer-playground/package.json @@ -1,6 +1,6 @@ { "name": "composer-playground", - "version": "0.8.1", + "version": "0.9.0", "description": "A test harness/UI for the web runtime container for Hyperledger Composer", "engines": { "node": ">=6", @@ -78,8 +78,8 @@ "dependencies": { "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.24", "cheerio": "^0.22.0", - "composer-common": "^0.8.1", - "composer-playground-api": "^0.8.1", + "composer-common": "^0.9.0", + "composer-playground-api": "^0.9.0", "express": "^4.15.2", "fast-json-patch": "^1.1.8", "file-saver": "^1.3.3", @@ -134,12 +134,12 @@ "chai": "^3.5.0", "codelyzer": "^2.0.1", "codemirror": "5.26.0", - "composer-admin": "^0.8.1", - "composer-client": "^0.8.1", - "composer-connector-proxy": "^0.8.1", - "composer-connector-web": "^0.8.1", - "composer-runtime": "^0.8.1", - "composer-runtime-web": "^0.8.1", + "composer-admin": "^0.9.0", + "composer-client": "^0.9.0", + "composer-connector-proxy": "^0.9.0", + "composer-connector-web": "^0.9.0", + "composer-runtime": "^0.9.0", + "composer-runtime-web": "^0.9.0", "copy-webpack-plugin": "^4.0.1", "core-js": "^2.4.1", "css-loader": "^0.26.1", diff --git a/packages/composer-rest-server/package.json b/packages/composer-rest-server/package.json index 3e0ce332c1..676bc18032 100644 --- a/packages/composer-rest-server/package.json +++ b/packages/composer-rest-server/package.json @@ -1,6 +1,6 @@ { "name": "composer-rest-server", - "version": "0.8.1", + "version": "0.9.0", "description": "Hyperledger Composer REST server that uses the Hyperledger Composer LoopBack connector", "engines": { "node": ">=6", @@ -36,7 +36,7 @@ "chalk": "^1.1.3", "clear": "0.0.1", "clui": "^0.3.1", - "composer-common": "^0.8.1", + "composer-common": "^0.9.0", "compression": "^1.0.3", "connect-ensure-login": "^0.1.1", "cookie-parser": "^1.4.3", @@ -52,7 +52,7 @@ "loopback-boot": "^2.23.0", "loopback-component-explorer": "^4.1.1", "loopback-component-passport": "^3.2.0", - "loopback-connector-composer": "^0.8.1", + "loopback-connector-composer": "^0.9.0", "passport-local": "^1.0.0", "serve-favicon": "^2.0.1", "strong-error-handler": "^1.0.1", @@ -64,9 +64,9 @@ "chai-as-promised": "^6.0.0", "chai-http": "^3.0.0", "clone": "^2.1.1", - "composer-admin": "^0.8.1", - "composer-client": "^0.8.1", - "composer-connector-embedded": "^0.8.1", + "composer-admin": "^0.9.0", + "composer-client": "^0.9.0", + "composer-connector-embedded": "^0.9.0", "eslint": "^3.17.1", "jsdoc": "^3.4.3", "license-check": "^1.1.5", diff --git a/packages/composer-rest-server/test/bond-network.bna b/packages/composer-rest-server/test/bond-network.bna index 68edd904eabe1d430bb529686a4f8cb135c72bef..2b8c6e6bda80880d169a85cb241b5115920ac59d 100644 GIT binary patch delta 615 zcmbQP@ljnoz?+$civa{&8ZUcA=X`E#VPRlsnJ8|~Y^Z0jF))KsL`g@fB(=Ci$x10V zKRF{&LAOM~VDcx%WYLVug4Cj%)RgqpBK_q2+=Be#)FR!Xl8W-n zfs$#(KttjT^^EikfQACi$^`L^^-Mwh$sd?X`BRH?GV?%I0Y%O93^&hVwqn!*x$Jw} zZtkOu3=AO533P3ctD}prt6px3vI3H2n}4%#FfwLLW@g*T3)1zp#H{)}P{;YrC)s8* zGU;hePT?>FQByhOnSlx>Z|6{w1}W^DI(g|5pvOU&A834TeoAUivHs@29NSpH3b*ls zHEh1YyMP63G(4y#w+iXXV{;-XP$6L5!?z6=B3~gr=nYycE6Ul6-W{2*nHxK(9i90FdtCWnjq3OwtGI@J6!l s>msI^%m#W!8v`>KEfsW26!i4-@>5dd zbMsS5b5e`-GxM@_Q}UCGONtT;^h;863vv=mQWf-+bd*X`i%XQOlydWvGZH7CWlR=G zOH3-tOx8`#&n?I=PA$@(?8+3t<^~p=ynxAU@^7XRbD-Kxpr$xuJySgcpdqQnIhlDN zV}K%NdWJv|WD7y!hI+<&2Ak`ctr%57?ruzTU9+E&fdPa$fqn^cb#(D{)yqv$R)AQw z`4I~T8yqk9c3)pmcAWqH@(hbArJWy~$ zz!{+NaK{FCGct)VAYudVOrTTwVLHI{rpd2`3+gg`a`0NymIbN~PV diff --git a/packages/composer-runtime-embedded/package.json b/packages/composer-runtime-embedded/package.json index 2697e2880e..9f772f2301 100644 --- a/packages/composer-runtime-embedded/package.json +++ b/packages/composer-runtime-embedded/package.json @@ -1,6 +1,6 @@ { "name": "composer-runtime-embedded", - "version": "0.8.1", + "version": "0.9.0", "description": "The embedded runtime container for Hyperledger Composer", "engines": { "node": ">=6", @@ -52,8 +52,8 @@ "logError": true }, "dependencies": { - "composer-common": "^0.8.1", - "composer-runtime": "^0.8.1", + "composer-common": "^0.9.0", + "composer-runtime": "^0.9.0", "debug": "^2.6.2", "dexie": "^1.5.1", "fake-indexeddb": "^1.0.8", diff --git a/packages/composer-runtime-hlf/package.json b/packages/composer-runtime-hlf/package.json index e511759b1e..d840edfd4d 100644 --- a/packages/composer-runtime-hlf/package.json +++ b/packages/composer-runtime-hlf/package.json @@ -1,6 +1,6 @@ { "name": "composer-runtime-hlf", - "version": "0.8.1", + "version": "0.9.0", "description": "The Hyperledger Fabric runtime container for Hyperledger Composer", "engines": { "node": ">=6", @@ -28,7 +28,7 @@ "babelify": "^7.3.0", "browserify": "^13.3.0", "browserify-replace": "^0.9.0", - "composer-runtime": "^0.8.1", + "composer-runtime": "^0.9.0", "exorcist": "^0.4.0", "fs-extra": "^1.0.0", "uglify-js": "2.7.5" diff --git a/packages/composer-runtime-hlfv1/package.json b/packages/composer-runtime-hlfv1/package.json index 0baae9570a..7b3829c63c 100644 --- a/packages/composer-runtime-hlfv1/package.json +++ b/packages/composer-runtime-hlfv1/package.json @@ -1,6 +1,6 @@ { "name": "composer-runtime-hlfv1", - "version": "0.8.1", + "version": "0.9.0", "description": "The Hyperledger Fabric v1.x runtime container for Hyperledger Composer", "engines": { "node": ">=6", @@ -28,7 +28,7 @@ "babelify": "^7.3.0", "browserify": "^13.3.0", "browserify-replace": "^0.9.0", - "composer-runtime": "^0.8.1", + "composer-runtime": "^0.9.0", "exorcist": "^0.4.0", "fs-extra": "^1.0.0", "uglify-js": "2.7.5" diff --git a/packages/composer-runtime-web/package.json b/packages/composer-runtime-web/package.json index ce5932919c..f476df0ab6 100644 --- a/packages/composer-runtime-web/package.json +++ b/packages/composer-runtime-web/package.json @@ -1,6 +1,6 @@ { "name": "composer-runtime-web", - "version": "0.8.1", + "version": "0.9.0", "description": "The web runtime container for Hyperledger Composer", "engines": { "node": ">=6", @@ -62,8 +62,8 @@ "logError": true }, "dependencies": { - "composer-common": "^0.8.1", - "composer-runtime": "^0.8.1", + "composer-common": "^0.9.0", + "composer-runtime": "^0.9.0", "dexie": "^1.5.1", "uuid": "^3.0.1", "xhr": "^2.4.0" diff --git a/packages/composer-runtime/lib/accesscontroller.js b/packages/composer-runtime/lib/accesscontroller.js index 3c5f408133..5f8762dd4c 100644 --- a/packages/composer-runtime/lib/accesscontroller.js +++ b/packages/composer-runtime/lib/accesscontroller.js @@ -16,6 +16,7 @@ const AccessException = require('./accessexception'); const Logger = require('composer-common').Logger; +const ModelUtil = require('composer-common').ModelUtil; const LOG = Logger.getLog('AccessController'); @@ -208,22 +209,16 @@ class AccessController { const method = 'matchNoun'; LOG.entry(method, resource.getFullyQualifiedIdentifier(), aclRule); - // Determine the input fully qualified name and ID. - let fqn = resource.getFullyQualifiedType(); - let ns = resource.getNamespace(); + // Determine the input ID. let id = resource.getIdentifier(); - // Check the namespace and type of the ACL rule. + // Check to see if the resource is an instance of the + // required resource type, or is in the required + // namespace. let noun = aclRule.getNoun(); - - // Check to see if the fully qualified name matches. let reqFQN = noun.getFullyQualifiedName(); - if (fqn === reqFQN) { - // Noun is matching fully qualified type. - } else if (ns === reqFQN) { - // Noun is matching namespace. - } else { - // Noun does not match. + + if (!ModelUtil.isMatchingType(resource, reqFQN)) { LOG.exit(method, false); return false; } @@ -292,14 +287,9 @@ class AccessController { // Check to see if the participant is an instance of the // required participant type, or is in the required // namespace. - let ns = participant.getNamespace(); let reqFQN = reqParticipant.getFullyQualifiedName(); - if (participant.instanceOf(reqFQN)) { - // Participant is matching type or subtype. - } else if (ns === reqFQN) { - // Participant is matching namespace. - } else { - // Participant does not match. + + if (!ModelUtil.isMatchingType(participant, reqFQN)) { LOG.exit(method, false); return false; } @@ -352,14 +342,9 @@ class AccessController { // Check to see if the participant is an instance of the // required participant type, or is in the required // namespace. - let ns = transaction.getNamespace(); let reqFQN = reqTransaction.getFullyQualifiedName(); - if (transaction.instanceOf(reqFQN)) { - // Transaction is matching type or subtype. - } else if (ns === reqFQN) { - // Transaction is matching namespace. - } else { - // Participant does not match. + + if (!ModelUtil.isMatchingType(transaction, reqFQN)) { LOG.exit(method, false); return false; } diff --git a/packages/composer-runtime/package.json b/packages/composer-runtime/package.json index d453ff71ff..5d821e513b 100644 --- a/packages/composer-runtime/package.json +++ b/packages/composer-runtime/package.json @@ -1,6 +1,6 @@ { "name": "composer-runtime", - "version": "0.8.1", + "version": "0.9.0", "description": "The runtime execution environment for Hyperledger Composer", "engines": { "node": ">=6", @@ -62,7 +62,7 @@ "logError": true }, "dependencies": { - "composer-common": "^0.8.1", + "composer-common": "^0.9.0", "debug": "^2.6.2", "fast-json-patch": "^1.1.8", "jsonata": "^1.2.2", diff --git a/packages/composer-runtime/test/accesscontroller.js b/packages/composer-runtime/test/accesscontroller.js index e39c948c04..04dba48651 100644 --- a/packages/composer-runtime/test/accesscontroller.js +++ b/packages/composer-runtime/test/accesscontroller.js @@ -30,6 +30,7 @@ describe('AccessController', () => { let aclManager; let factory; let asset; + let asset2; let participant; let participant2; let transaction; @@ -57,7 +58,10 @@ describe('AccessController', () => { asset TestAsset identified by assetId extends BaseAsset { o String assetId } - asset TestAsset2 identified by assetId extends BaseAsset { + asset TestAsset2 extends TestAsset { + + } + asset TestAsset3 identified by assetId extends BaseAsset { o String assetId } participant TestParticipant identified by participantId extends BaseParticipant { @@ -94,6 +98,7 @@ describe('AccessController', () => { aclManager = new AclManager(modelManager); factory = new Factory(modelManager); asset = factory.newResource('org.acme.test', 'TestAsset', 'A1234'); + asset2 = factory.newResource('org.acme.test', 'TestAsset2', 'A4321'); participant = factory.newResource('org.acme.test', 'TestParticipant', 'P5678'); participant2 = factory.newResource('org.acme.test', 'TestParticipant2', 'P7890'); transaction = factory.newResource('org.acme.test', 'TestTransaction', 'T9012'); @@ -325,7 +330,13 @@ describe('AccessController', () => { }); it('should return true if the ACL rule specifies a matching namespace', () => { - setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test.TestParticipant#P5678" operation: READ resource: "org.acme.test" action: ALLOW}'); + setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test.TestParticipant#P5678" operation: READ resource: "org.acme.test.*" action: ALLOW}'); + controller.matchNoun(asset, aclManager.getAclRules()[0]) + .should.be.true; + }); + + it('should return true if the ACL rule specifies a matching recursive namespace', () => { + setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test.TestParticipant#P5678" operation: READ resource: "org.acme.**" action: ALLOW}'); controller.matchNoun(asset, aclManager.getAclRules()[0]) .should.be.true; }); @@ -343,7 +354,27 @@ describe('AccessController', () => { }); it('should return false if the ACL rule specifies a non-matching namespace', () => { - setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test.TestParticipant#P5678" operation: READ resource: "org.acme.test2" action: ALLOW}'); + setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test.TestParticipant#P5678" operation: READ resource: "org.acme.test2.*" action: ALLOW}'); + controller.matchNoun(asset, aclManager.getAclRules()[0]) + .should.be.false; + }); + + it('should return false if the ACL rule specifies a non-matching recursive namespace', () => { + setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test.TestParticipant#P5678" operation: READ resource: "org.acme.test2.**" action: ALLOW}'); + controller.matchNoun(asset, aclManager.getAclRules()[0]) + .should.be.false; + }); + + it('should return true if the ACL rule specifies a fully qualified name of a nested supertype', () => { + // Test with TestAsset2 which extends TestAsset which extends BaseAsset. + setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test.TestParticipant#P5678" operation: READ resource: "org.acme.base.BaseAsset" action: ALLOW}'); + controller.matchNoun(asset2, aclManager.getAclRules()[0]) + .should.be.true; + }); + + it('should return false if the ACL rule specifies a fully qualified name of a subtype', () => { + // Test with TestAsset which is extended by TestAsset3. + setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test.TestParticipant#P5678" operation: READ resource: "org.acme.test.TestAsset3" action: ALLOW}'); controller.matchNoun(asset, aclManager.getAclRules()[0]) .should.be.false; }); @@ -404,7 +435,13 @@ describe('AccessController', () => { }); it('should return true if the ACL rule specifies a matching namespace', () => { - setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test" operation: READ resource: "org.acme.test.TestAsset#A1234" action: ALLOW}'); + setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test.*" operation: READ resource: "org.acme.test.TestAsset#A1234" action: ALLOW}'); + controller.matchParticipant(participant, aclManager.getAclRules()[0]) + .should.be.true; + }); + + it('should return true if the ACL rule specifies a matching recursive namespace', () => { + setAclFile('rule R1 {description: "Test R1" participant: "org.acme.**" operation: READ resource: "org.acme.test.TestAsset#A1234" action: ALLOW}'); controller.matchParticipant(participant, aclManager.getAclRules()[0]) .should.be.true; }); @@ -422,7 +459,13 @@ describe('AccessController', () => { }); it('should return false if the ACL rule specifies a non-matching namespace', () => { - setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test2" operation: READ resource: "org.acme.test.TestAsset#A1234" action: ALLOW}'); + setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test2.*" operation: READ resource: "org.acme.test.TestAsset#A1234" action: ALLOW}'); + controller.matchParticipant(participant, aclManager.getAclRules()[0]) + .should.be.false; + }); + + it('should return false if the ACL rule specifies a non-matching recursive namespace', () => { + setAclFile('rule R1 {description: "Test R1" participant: "org.acme.test2.**" operation: READ resource: "org.acme.test.TestAsset#A1234" action: ALLOW}'); controller.matchParticipant(participant, aclManager.getAclRules()[0]) .should.be.false; }); @@ -472,7 +515,13 @@ describe('AccessController', () => { }); it('should return true if the ACL rule specifies a matching namespace', () => { - setAclFile('rule R1 {description: "Test R1" participant: "ANY" operation: READ resource: "org.acme.test.TestAsset#A1234" transaction: "org.acme.test" action: ALLOW}'); + setAclFile('rule R1 {description: "Test R1" participant: "ANY" operation: READ resource: "org.acme.test.TestAsset#A1234" transaction: "org.acme.test.*" action: ALLOW}'); + controller.matchTransaction(transaction, aclManager.getAclRules()[0]) + .should.be.true; + }); + + it('should return true if the ACL rule specifies a matching recusive namespace', () => { + setAclFile('rule R1 {description: "Test R1" participant: "ANY" operation: READ resource: "org.acme.test.TestAsset#A1234" transaction: "org.acme.test.**" action: ALLOW}'); controller.matchTransaction(transaction, aclManager.getAclRules()[0]) .should.be.true; }); @@ -484,7 +533,13 @@ describe('AccessController', () => { }); it('should return false if the ACL rule specifies a non-matching namespace', () => { - setAclFile('rule R1 {description: "Test R1" participant: "ANY" operation: READ resource: "org.acme.test.TestAsset#A1234" transaction: "org.acme.test2" action: ALLOW}'); + setAclFile('rule R1 {description: "Test R1" participant: "ANY" operation: READ resource: "org.acme.test.TestAsset#A1234" transaction: "org.acme.test2.*" action: ALLOW}'); + controller.matchTransaction(transaction, aclManager.getAclRules()[0]) + .should.be.false; + }); + + it('should return false if the ACL rule specifies a non-matching recursive namespace', () => { + setAclFile('rule R1 {description: "Test R1" participant: "ANY" operation: READ resource: "org.acme.test.TestAsset#A1234" transaction: "org.acme.test2.**" action: ALLOW}'); controller.matchTransaction(transaction, aclManager.getAclRules()[0]) .should.be.false; }); diff --git a/packages/composer-systests/package.json b/packages/composer-systests/package.json index 3aa583de18..261fc9a2c7 100644 --- a/packages/composer-systests/package.json +++ b/packages/composer-systests/package.json @@ -1,6 +1,6 @@ { "name": "composer-systests", - "version": "0.8.1", + "version": "0.9.0", "private": true, "description": "System tests and automation for Hyperledger Composer", "engines": { @@ -42,13 +42,13 @@ "chai": "^3.5.0", "chai-as-promised": "^6.0.0", "chai-subset": "^1.3.0", - "composer-admin": "^0.8.1", - "composer-client": "^0.8.1", - "composer-common": "^0.8.1", - "composer-connector-embedded": "^0.8.1", - "composer-connector-proxy": "^0.8.1", - "composer-connector-server": "^0.8.1", - "composer-connector-web": "^0.8.1", + "composer-admin": "^0.9.0", + "composer-client": "^0.9.0", + "composer-common": "^0.9.0", + "composer-connector-embedded": "^0.9.0", + "composer-connector-proxy": "^0.9.0", + "composer-connector-server": "^0.9.0", + "composer-connector-web": "^0.9.0", "eslint": "^3.17.1", "homedir": "^0.6.0", "karma": "^1.3.0", diff --git a/packages/composer-website/jekylldocs/reference/acl_language.md b/packages/composer-website/jekylldocs/reference/acl_language.md index 2c358547d7..d844c880f2 100644 --- a/packages/composer-website/jekylldocs/reference/acl_language.md +++ b/packages/composer-website/jekylldocs/reference/acl_language.md @@ -51,12 +51,12 @@ rule SampleConditionalRule { Multiple ACL rules may be defined that conceptually define a decision table. The actions of the decision tree define access control decisions (ALLOW or DENY). If the decision table fails to match then by default access is denied. -**Resource** defines the things that the ACL rule applies to. This can be a property on a class, an entire class or all classes within a namespace. It can also be an instance of a class. +**Resource** defines the things that the ACL rule applies to. This can be a class, all classes within a namespace, or all classes under a namespace. It can also be an instance of a class. Resource Examples: -- Namespace: org.example +- Namespace: org.example.* +- Namespace (recursive): org.example.** - Class in namespace: org.example.Car -- Property on class: org.example.Car.owner - Instance of a class: org.example.Car#ABC123 **Operation** identifies the action that the rule governs. It must be one of: CREATE, READ, UPDATE, DELETE or ALL. @@ -90,15 +90,6 @@ rule R2 { } rule R3 { - description: "Driver can change the ownership of a car that they own" - participant(d): "org.example.Driver" - operation: UPDATE - resource(o): "org.example.Car" - condition: (o == d) - action: ALLOW -} - -rule R4 { description: "regulators can perform all operations on Cars" participant: "org.example.Regulator" operation: ALL @@ -106,11 +97,19 @@ rule R4 { action: ALLOW } -rule R5 { +rule R4 { description: "Everyone can read all resources in the org.example namespace" participant: "ANY" operation: READ - resource: "org.example" + resource: "org.example.*" + action: ALLOW +} + +rule R5 { + description: "Everyone can read all resources under the org.example namespace" + participant: "ANY" + operation: READ + resource: "org.example.**" action: ALLOW } ``` diff --git a/packages/composer-website/package.json b/packages/composer-website/package.json index 79f71a7fe9..6711850d5b 100644 --- a/packages/composer-website/package.json +++ b/packages/composer-website/package.json @@ -1,6 +1,6 @@ { "name": "composer-website", - "version": "0.8.1", + "version": "0.9.0", "private": true, "description": "Hyperledger Composer is a blockchain development framework for Hyperledger Fabric: a library of assets/functions for creating blockchain-based applications.", "engines": { @@ -28,10 +28,10 @@ "author": "Hyperledger Composer", "license": "Apache-2.0", "devDependencies": { - "composer-admin": "^0.8.1", - "composer-client": "^0.8.1", - "composer-common": "^0.8.1", - "composer-runtime": "^0.8.1", + "composer-admin": "^0.9.0", + "composer-client": "^0.9.0", + "composer-common": "^0.9.0", + "composer-runtime": "^0.9.0", "jsdoc": "^3.4.3", "node-plantuml": "^0.5.0", "sanitize-html": "^1.14.1" diff --git a/packages/generator-hyperledger-composer/package.json b/packages/generator-hyperledger-composer/package.json index c07c83f0df..88c0dc53ce 100755 --- a/packages/generator-hyperledger-composer/package.json +++ b/packages/generator-hyperledger-composer/package.json @@ -1,6 +1,6 @@ { "name": "generator-hyperledger-composer", - "version": "0.8.1", + "version": "0.9.0", "description": "Generates skeleton projects from Hyperledger Composer business network definitions", "engines": { "node": ">=6", @@ -15,8 +15,8 @@ "liveNetworkTest": "mocha -t 0 test/angular-network.js" }, "dependencies": { - "composer-client": "^0.8.1", - "composer-common": "^0.8.1", + "composer-client": "^0.9.0", + "composer-common": "^0.9.0", "fs": "0.0.1-security", "shelljs": "^0.7.7", "underscore.string": "^3.3.4", @@ -30,8 +30,8 @@ "license": "Apache-2.0", "devDependencies": { "@angular/cli": "^1.0.0-rc.0", - "composer-admin": "^0.8.1", - "composer-connector-embedded": "^0.8.1", + "composer-admin": "^0.9.0", + "composer-connector-embedded": "^0.9.0", "typings": "^2.1.0", "yeoman-test": "^1.6.0" } diff --git a/packages/loopback-connector-composer/package.json b/packages/loopback-connector-composer/package.json index d356e8d32a..11c990a892 100644 --- a/packages/loopback-connector-composer/package.json +++ b/packages/loopback-connector-composer/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-composer", - "version": "0.8.1", + "version": "0.9.0", "description": "A Loopback connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -26,8 +26,8 @@ "author": "Hyperledger Composer", "license": "Apache-2.0", "dependencies": { - "composer-client": "^0.8.1", - "composer-common": "^0.8.1", + "composer-client": "^0.9.0", + "composer-common": "^0.9.0", "loopback": "^3.4.0", "loopback-connector": "^4.0.0", "node-cache": "^4.1.1" @@ -35,8 +35,8 @@ "devDependencies": { "chai": "^3.5.0", "chai-as-promised": "^6.0.0", - "composer-admin": "^0.8.1", - "composer-connector-embedded": "^0.8.1", + "composer-admin": "^0.9.0", + "composer-connector-embedded": "^0.9.0", "eslint": "^3.17.1", "jsdoc": "^3.4.3", "license-check": "^1.1.5", diff --git a/packages/loopback-connector-composer/test/bond-network.bna b/packages/loopback-connector-composer/test/bond-network.bna index 68edd904eabe1d430bb529686a4f8cb135c72bef..2b8c6e6bda80880d169a85cb241b5115920ac59d 100644 GIT binary patch delta 615 zcmbQP@ljnoz?+$civa{&8ZUcA=X`E#VPRlsnJ8|~Y^Z0jF))KsL`g@fB(=Ci$x10V zKRF{&LAOM~VDcx%WYLVug4Cj%)RgqpBK_q2+=Be#)FR!Xl8W-n zfs$#(KttjT^^EikfQACi$^`L^^-Mwh$sd?X`BRH?GV?%I0Y%O93^&hVwqn!*x$Jw} zZtkOu3=AO533P3ctD}prt6px3vI3H2n}4%#FfwLLW@g*T3)1zp#H{)}P{;YrC)s8* zGU;hePT?>FQByhOnSlx>Z|6{w1}W^DI(g|5pvOU&A834TeoAUivHs@29NSpH3b*ls zHEh1YyMP63G(4y#w+iXXV{;-XP$6L5!?z6=B3~gr=nYycE6Ul6-W{2*nHxK(9i90FdtCWnjq3OwtGI@J6!l s>msI^%m#W!8v`>KEfsW26!i4-@>5dd zbMsS5b5e`-GxM@_Q}UCGONtT;^h;863vv=mQWf-+bd*X`i%XQOlydWvGZH7CWlR=G zOH3-tOx8`#&n?I=PA$@(?8+3t<^~p=ynxAU@^7XRbD-Kxpr$xuJySgcpdqQnIhlDN zV}K%NdWJv|WD7y!hI+<&2Ak`ctr%57?ruzTU9+E&fdPa$fqn^cb#(D{)yqv$R)AQw z`4I~T8yqk9c3)pmcAWqH@(hbArJWy~$ zz!{+NaK{FCGct)VAYudVOrTTwVLHI{rpd2`3+gg`a`0NymIbN~PV From cd7ba0888adc7479e06fb62b9e826718434530fc Mon Sep 17 00:00:00 2001 From: James Taylor Date: Tue, 20 Jun 2017 17:01:36 +0100 Subject: [PATCH 2/3] Revert "drop in the detection script for live testing (#1286)" (#1342) This reverts commit 19e949acd626ebe2c9e037f6b9e8441c5a61e1ec. # Conflicts: # .travis/before-install.sh --- .travis/before-install.sh | 47 ++------------------------------------- 1 file changed, 2 insertions(+), 45 deletions(-) diff --git a/.travis/before-install.sh b/.travis/before-install.sh index d67271b28e..8f9f56ca19 100755 --- a/.travis/before-install.sh +++ b/.travis/before-install.sh @@ -10,7 +10,7 @@ sudo rm /usr/local/bin/docker-compose curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose chmod +x docker-compose sudo mv docker-compose /usr/local/bin -echo "Docker-compose version: " +echo "Docker-compose version: " docker-compose --version # Update docker @@ -22,7 +22,7 @@ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" sudo apt-get update sudo apt-get install docker-ce -echo "Docker version: " +echo "Docker version: " docker --version # Grab the parent (root) directory. @@ -51,49 +51,6 @@ echo "->- Build cfg being used" cat ${DIR}/build.cfg echo "-<-" - -###### -# checking the changes that are in this file -echo "Travis commit range $TRAVIS_COMMIT_RANGE" -echo "Travis commit $TRAVIS_COMMIT" -echo "Travis event type $TRAVIS_EVENT_TYPE" - - -if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - echo -e "Build Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" -elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then - echo -e 'Build Branch with Snapshot => Branch ['$TRAVIS_BRANCH']' -elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then - echo -e 'Build Branch for Release => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG']' -else - echo -e 'WARN: Should not be here => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG'] Pull Request ['$TRAVIS_PULL_REQUEST']' -fi - - -cd $TRAVIS_BUILD_DIR -git diff --name-only $(echo $TRAVIS_COMMIT_RANGE | sed 's/\.//') - -if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - git show --pretty=format: --name-only "$TRAVIS_COMMIT_RANGE"|sort|uniq > changedfiles.log -elif [ -n "$TRAVIS_PULL_REQUEST" ]; then - git diff --name-only "$TRAVIS_COMMIT" "$TRAVIS_BRANCH" > changedfiles.log -fi - -RESULT=$(cat changedfiles | sed '/^\s*$/d' | awk '!/composer-website/ { print "MORE" }') -if [ "${RESULT}" == "" ]; -then - echo "Only docs changes" -else - echo "More than docs changes" -fi -rm changedfiles.log - -cd - > /dev/null -###### - - - - # Check of the task current executing if [ "${FC_TASK}" = "docs" ]; then echo Doing Docs - no requirement for installations of other software From fcc9402308a5d26b846fda4c485dbca051798d1d Mon Sep 17 00:00:00 2001 From: James Taylor Date: Wed, 21 Jun 2017 10:58:38 +0100 Subject: [PATCH 3/3] Fixed examples of old ACL namespace syntax (#1343) Signed-off-by: James Taylor --- .../jekylldocs/assets/img/tutorials/developer/Tutorial2.md | 2 +- .../composer-website/jekylldocs/tutorials/developer-guide.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/composer-website/jekylldocs/assets/img/tutorials/developer/Tutorial2.md b/packages/composer-website/jekylldocs/assets/img/tutorials/developer/Tutorial2.md index b51fc2c57f..c7e1540a21 100644 --- a/packages/composer-website/jekylldocs/assets/img/tutorials/developer/Tutorial2.md +++ b/packages/composer-website/jekylldocs/assets/img/tutorials/developer/Tutorial2.md @@ -138,7 +138,7 @@ rule Default { description: "Allow all participants access to all resources" participant: "ANY" operation: ALL -resource: "org.example.mynetwork" +resource: "org.example.mynetwork.*" action: ALLOW } ``` diff --git a/packages/composer-website/jekylldocs/tutorials/developer-guide.md b/packages/composer-website/jekylldocs/tutorials/developer-guide.md index d20e22d670..c06d9ede1f 100644 --- a/packages/composer-website/jekylldocs/tutorials/developer-guide.md +++ b/packages/composer-website/jekylldocs/tutorials/developer-guide.md @@ -179,7 +179,7 @@ rule Default { description: "Allow all participants access to all resources" participant: "ANY" operation: ALL - resource: "org.acme.mynetwork" + resource: "org.acme.mynetwork.*" action: ALLOW } ```