├── .gitignore ├── features ├── support │ └── index.js └── sample.feature ├── package.json ├── models └── org.example.blockchainbank.cto ├── .eslintrc.yml ├── lib └── logic.js ├── permissions.acl ├── .github └── workflows │ └── codeql-analysis.yml ├── README.md ├── LICENSE └── test └── logic.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.bna 4 | *.card 5 | -------------------------------------------------------------------------------- /features/support/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 'use strict'; 16 | 17 | const composerSteps = require('composer-cucumber-steps'); 18 | const cucumber = require('cucumber'); 19 | 20 | module.exports = function () { 21 | composerSteps.call(this); 22 | }; 23 | 24 | if (cucumber.defineSupportCode) { 25 | cucumber.defineSupportCode((context) => { 26 | module.exports.call(context); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "composer": "^0.19.6" 4 | }, 5 | "name": "blockchain-bank", 6 | "version": "0.0.1", 7 | "description": "Blockchain Bank", 8 | "scripts": { 9 | "prepublish": "composer archive create --sourceType dir --sourceName . -a ./blockchain-bank.bna", 10 | "pretest": "npm run lint", 11 | "lint": "eslint .", 12 | "test": "nyc mocha -t 0 test/*.js && cucumber-js" 13 | }, 14 | "keywords": [ 15 | "composer", 16 | "composer-network" 17 | ], 18 | "author": "Filipe Corrêa", 19 | "email": "filipecorrea@me.com", 20 | "license": "Apache-2.0", 21 | "devDependencies": { 22 | "chai": "latest", 23 | "chai-as-promised": "latest", 24 | "composer-admin": "^0.20.5", 25 | "composer-cli": "^0.20.5", 26 | "composer-client": "^0.20.5", 27 | "composer-common": "^0.20.5", 28 | "composer-connector-embedded": "^0.20.5", 29 | "composer-cucumber-steps": "^0.20.5", 30 | "cucumber": "^2.2.0", 31 | "eslint": "latest", 32 | "mkdirp": "latest", 33 | "mocha": "latest", 34 | "nyc": "latest" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /models/org.example.blockchainbank.cto: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | namespace org.example.blockchainbank 16 | 17 | participant Customer identified by customerId { 18 | o String customerId 19 | o String firstName 20 | o String lastName 21 | } 22 | 23 | asset Account identified by accountId { 24 | o String accountId 25 | --> Customer owner 26 | o Double balance 27 | } 28 | 29 | transaction AccountTransfer { 30 | --> Account from 31 | --> Account to 32 | o Double amount 33 | } 34 | 35 | transaction AddFunds { 36 | --> Account account 37 | o Double amount 38 | } 39 | 40 | transaction Withdrawal { 41 | --> Account account 42 | o Double amount 43 | } 44 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | mocha: true 4 | extends: 'eslint:recommended' 5 | parserOptions: 6 | ecmaVersion: 8 7 | sourceType: 8 | - script 9 | globals: 10 | getFactory: true 11 | getSerializer: true 12 | getAssetRegistry: true 13 | getParticipantRegistry: true 14 | getCurrentParticipant: true 15 | post: true 16 | emit: true 17 | rules: 18 | indent: 19 | - error 20 | - 4 21 | linebreak-style: 22 | - error 23 | - unix 24 | quotes: 25 | - error 26 | - single 27 | semi: 28 | - error 29 | - always 30 | no-unused-vars: 31 | - 0 32 | - args: none 33 | no-console: off 34 | curly: error 35 | eqeqeq: error 36 | no-throw-literal: error 37 | strict: error 38 | dot-notation: error 39 | no-tabs: error 40 | no-trailing-spaces: error 41 | no-useless-call: error 42 | no-with: error 43 | operator-linebreak: error 44 | require-jsdoc: 45 | - error 46 | - require: 47 | ClassDeclaration: true 48 | MethodDefinition: true 49 | FunctionDeclaration: true 50 | yoda: error 51 | no-confusing-arrow: 2 52 | no-constant-condition: 2 53 | -------------------------------------------------------------------------------- /lib/logic.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 'use strict' 16 | 17 | /** 18 | * Account transfer transaction 19 | * @param {org.example.blockchainbank.AccountTransfer} accountTransfer 20 | * @transaction 21 | */ 22 | async function accountTransfer(accountTransfer) { 23 | 24 | if (accountTransfer.from.balance < accountTransfer.amount) { 25 | throw new Error('Insufficient funds!') 26 | } 27 | 28 | accountTransfer.from.balance -= accountTransfer.amount 29 | accountTransfer.to.balance += accountTransfer.amount 30 | 31 | return getAssetRegistry('org.example.blockchainbank.Account') 32 | .then(function (assetRegistry) { 33 | return assetRegistry.update(accountTransfer.from) 34 | }) 35 | .then(function () { 36 | return getAssetRegistry('org.example.blockchainbank.Account') 37 | }) 38 | .then(function (assetRegistry) { 39 | return assetRegistry.update(accountTransfer.to) 40 | }) 41 | } 42 | 43 | /** 44 | * Add funds transaction 45 | * @param {org.example.blockchainbank.AddFunds} addFunds 46 | * @transaction 47 | */ 48 | async function addFunds(addFunds) { 49 | 50 | addFunds.account.balance += addFunds.amount 51 | 52 | return getAssetRegistry('org.example.blockchainbank.Account') 53 | .then(function (assetRegistry) { 54 | return assetRegistry.update(addFunds.account) 55 | }) 56 | } 57 | 58 | /** 59 | * Withdrawal transaction 60 | * @param {org.example.blockchainbank.Withdrawal} withdrawal 61 | * @transaction 62 | */ 63 | async function withdrawal(withdrawal) { 64 | 65 | withdrawal.account.balance -= withdrawal.amount 66 | 67 | return getAssetRegistry('org.example.blockchainbank.Account') 68 | .then(function (assetRegistry) { 69 | return assetRegistry.update(withdrawal.account) 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /permissions.acl: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | rule EverybodyCanReadEverything { 16 | description: "Allow all participants read access to all resources" 17 | participant: "org.example.blockchainbank.Customer" 18 | operation: READ 19 | resource: "org.example.blockchainbank.*" 20 | action: ALLOW 21 | } 22 | 23 | rule EverybodyCanSubmitTransactions { 24 | description: "Allow all participants to submit transactions" 25 | participant: "org.example.blockchainbank.Customer" 26 | operation: CREATE 27 | resource: "org.example.blockchainbank.AccountTransfer" 28 | action: ALLOW 29 | } 30 | 31 | rule OwnerHasFullAccessToTheirAssets { 32 | description: "Allow all participants full access to their assets" 33 | participant(p): "org.example.blockchainbank.Customer" 34 | operation: ALL 35 | resource(r): "org.example.blockchainbank.Account" 36 | condition: (r.owner.getIdentifier() === p.getIdentifier()) 37 | action: ALLOW 38 | } 39 | 40 | rule SystemACL { 41 | description: "System ACL to permit all access" 42 | participant: "org.hyperledger.composer.system.Participant" 43 | operation: ALL 44 | resource: "org.hyperledger.composer.system.**" 45 | action: ALLOW 46 | } 47 | 48 | rule NetworkAdminUser { 49 | description: "Grant business network administrators full access to user resources" 50 | participant: "org.hyperledger.composer.system.NetworkAdmin" 51 | operation: ALL 52 | resource: "**" 53 | action: ALLOW 54 | } 55 | 56 | rule NetworkAdminSystem { 57 | description: "Grant business network administrators full access to system resources" 58 | participant: "org.hyperledger.composer.system.NetworkAdmin" 59 | operation: ALL 60 | resource: "org.hyperledger.composer.system.**" 61 | action: ALLOW 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # ******** NOTE ******** 12 | 13 | name: "CodeQL" 14 | 15 | on: 16 | push: 17 | branches: [ master ] 18 | pull_request: 19 | # The branches below must be a subset of the branches above 20 | branches: [ master ] 21 | schedule: 22 | - cron: '40 13 * * 3' 23 | 24 | jobs: 25 | analyze: 26 | name: Analyze 27 | runs-on: ubuntu-latest 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | language: [ 'javascript' ] 33 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 34 | # Learn more... 35 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 36 | 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v2 40 | 41 | # Initializes the CodeQL tools for scanning. 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@v1 44 | with: 45 | languages: ${{ matrix.language }} 46 | # If you wish to specify custom queries, you can do so here or in a config file. 47 | # By default, queries listed here will override any specified in a config file. 48 | # Prefix the list here with "+" to use these queries and those in the config file. 49 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 50 | 51 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 52 | # If this step fails, then you should remove it and run the build manually (see below) 53 | - name: Autobuild 54 | uses: github/codeql-action/autobuild@v1 55 | 56 | # ℹ️ Command-line programs to run using the OS shell. 57 | # 📚 https://git.io/JvXDl 58 | 59 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 60 | # and modify them (or add more) to build your code if your project 61 | # uses a compiled language 62 | 63 | #- run: | 64 | # make bootstrap 65 | # make release 66 | 67 | - name: Perform CodeQL Analysis 68 | uses: github/codeql-action/analyze@v1 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blockchain Bank 2 | 3 | Digital bank transactions using Hyperledger Composer Blockchain. 4 | 5 | ## Pre-requisites 6 | 7 | - [Docker](https://www.docker.com) 8 | - [Node.js](https://nodejs.org) 9 | 10 | ## Setup 11 | 12 | ### Install Hyperledger Composer tools 13 | 14 | Install Hyperledger Composer essential CLI tools: 15 | 16 | ``` 17 | npm install -g composer-cli 18 | ``` 19 | 20 | Install Hyperledger Composer Playground: 21 | 22 | ``` 23 | npm install -g composer-playground 24 | ``` 25 | 26 | Install Hyperledger Composer utility for running a REST Server on your machine 27 | to expose your business networks as RESTful APIs: 28 | 29 | ``` 30 | npm install -g composer-rest-server 31 | ``` 32 | 33 | ### Install Hyperledger Fabric 34 | 35 | In a directory of your choice (we will assume `~/fabric-dev-servers`), get the 36 | `.tar.gz` file that contains the tools to install Hyperledger Fabric: 37 | 38 | ``` 39 | mkdir ~/fabric-dev-servers && cd ~/fabric-dev-servers 40 | 41 | curl -O https://raw.githubusercontent.com/hyperledger/composer-tools/master/packages/fabric-dev-servers/fabric-dev-servers.tar.gz 42 | tar -xvf fabric-dev-servers.tar.gz 43 | ``` 44 | 45 | ## Run 46 | 47 | ### Start Hyperledger Fabric 48 | 49 | You control your runtime using a set of scripts which you'll find in 50 | `~/fabric-dev-servers` if you followed the suggested defaults. 51 | 52 | Start Hyperledger Fabric: 53 | 54 | ``` 55 | ~/fabric-dev-servers/startFabric.sh 56 | ``` 57 | 58 | The first time you start up a new runtime, you'll need to generate a PeerAdmin 59 | card: 60 | 61 | ``` 62 | ~/fabric-dev-servers/createPeerAdminCard.sh 63 | ``` 64 | 65 | You can start and stop your runtime using `~/fabric-dev-servers/stopFabric.sh`, 66 | and start it again with `~/fabric-dev-servers/startFabric.sh`. 67 | 68 | At the end of your development session, you run 69 | `~/fabric-dev-servers/stopFabric.sh` and then 70 | `~/fabric-dev-servers/teardownFabric.sh`. Note that if you've run the teardown 71 | script, the next time you start the runtime, you'll need to create a new 72 | PeerAdmin card just like you did on first time startup. 73 | 74 | If you've previously used an older version of Hyperledger Composer and are now 75 | setting up a new install, you may want to kill and remove all previous Docker 76 | containers, which you can do with these commands: 77 | 78 | ``` 79 | docker kill $(docker ps -q) 80 | docker rm $(docker ps -aq) 81 | docker rmi $(docker images dev-* -q) 82 | ``` 83 | 84 | ### Start Playground web app 85 | 86 | To start the web app, run: 87 | 88 | ``` 89 | composer-playground 90 | ``` 91 | 92 | ## Deploy 93 | 94 | ### Generate a business network archive 95 | 96 | Package the application into a deployable business network archive (`.bna`) 97 | file: 98 | 99 | ``` 100 | npm run prepublish 101 | ``` 102 | 103 | ### Deploying the business network 104 | 105 | After creating the `.bna` file, the business network can be deployed to the 106 | Hyperledger Fabric's instance. 107 | 108 | To install the business network, run the following command: 109 | 110 | ``` 111 | composer network install --card PeerAdmin@hlfv1 --archiveFile dist/blockchain-bank.bna 112 | ``` 113 | 114 | To start the business network, run the following command: 115 | 116 | ``` 117 | composer network start --networkName blockchain-bank --networkVersion 0.0.1 --networkAdmin admin --networkAdminEnrollSecret adminpw --card PeerAdmin@hlfv1 --file networkadmin.card 118 | ``` 119 | 120 | To import the network administrator identity as a usable business network card, 121 | run the following command: 122 | 123 | ``` 124 | composer card import --file networkadmin.card 125 | ``` 126 | 127 | To check that the business network has been deployed successfully, run the 128 | following command to ping the network: 129 | 130 | ``` 131 | composer network ping --card admin@blockchain-bank 132 | ``` 133 | 134 | ### Generating a REST server 135 | 136 | Hyperledger Composer can generate a bespoke REST API based on a business 137 | network. For developing a web application, the REST API provides a useful layer 138 | of language-neutral abstraction. 139 | 140 | To create the REST API, navigate to the `blockchain-bank` directory and run the 141 | following command (enter `admin@blockchain-bank` as the card name): 142 | 143 | ``` 144 | composer-rest-server 145 | ``` 146 | -------------------------------------------------------------------------------- /features/sample.feature: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | 15 | Feature: Sample 16 | 17 | Background: 18 | Given I have deployed the business network definition .. 19 | And I have added the following participants of type org.example.blockchainbank.SampleParticipant 20 | | participantId | firstName | lastName | 21 | | alice@email.com | Alice | A | 22 | | bob@email.com | Bob | B | 23 | And I have added the following assets of type org.example.blockchainbank.SampleAsset 24 | | assetId | owner | value | 25 | | 1 | alice@email.com | 10 | 26 | | 2 | bob@email.com | 20 | 27 | And I have issued the participant org.example.blockchainbank.SampleParticipant#alice@email.com with the identity alice1 28 | And I have issued the participant org.example.blockchainbank.SampleParticipant#bob@email.com with the identity bob1 29 | 30 | Scenario: Alice can read all of the assets 31 | When I use the identity alice1 32 | Then I should have the following assets of type org.example.blockchainbank.SampleAsset 33 | | assetId | owner | value | 34 | | 1 | alice@email.com | 10 | 35 | | 2 | bob@email.com | 20 | 36 | 37 | Scenario: Bob can read all of the assets 38 | When I use the identity alice1 39 | Then I should have the following assets of type org.example.blockchainbank.SampleAsset 40 | | assetId | owner | value | 41 | | 1 | alice@email.com | 10 | 42 | | 2 | bob@email.com | 20 | 43 | 44 | Scenario: Alice can add assets that she owns 45 | When I use the identity alice1 46 | And I add the following asset of type org.example.blockchainbank.SampleAsset 47 | | assetId | owner | value | 48 | | 3 | alice@email.com | 30 | 49 | Then I should have the following assets of type org.example.blockchainbank.SampleAsset 50 | | assetId | owner | value | 51 | | 3 | alice@email.com | 30 | 52 | 53 | Scenario: Alice cannot add assets that Bob owns 54 | When I use the identity alice1 55 | And I add the following asset of type org.example.blockchainbank.SampleAsset 56 | | assetId | owner | value | 57 | | 3 | bob@email.com | 30 | 58 | Then I should get an error matching /does not have .* access to resource/ 59 | 60 | Scenario: Bob can add assets that he owns 61 | When I use the identity bob1 62 | And I add the following asset of type org.example.blockchainbank.SampleAsset 63 | | assetId | owner | value | 64 | | 4 | bob@email.com | 40 | 65 | Then I should have the following assets of type org.example.blockchainbank.SampleAsset 66 | | assetId | owner | value | 67 | | 4 | bob@email.com | 40 | 68 | 69 | Scenario: Bob cannot add assets that Alice owns 70 | When I use the identity bob1 71 | And I add the following asset of type org.example.blockchainbank.SampleAsset 72 | | assetId | owner | value | 73 | | 4 | alice@email.com | 40 | 74 | Then I should get an error matching /does not have .* access to resource/ 75 | 76 | Scenario: Alice can update her assets 77 | When I use the identity alice1 78 | And I update the following asset of type org.example.blockchainbank.SampleAsset 79 | | assetId | owner | value | 80 | | 1 | alice@email.com | 50 | 81 | Then I should have the following assets of type org.example.blockchainbank.SampleAsset 82 | | assetId | owner | value | 83 | | 1 | alice@email.com | 50 | 84 | 85 | Scenario: Alice cannot update Bob's assets 86 | When I use the identity alice1 87 | And I update the following asset of type org.example.blockchainbank.SampleAsset 88 | | assetId | owner | value | 89 | | 2 | bob@email.com | 50 | 90 | Then I should get an error matching /does not have .* access to resource/ 91 | 92 | Scenario: Bob can update his assets 93 | When I use the identity bob1 94 | And I update the following asset of type org.example.blockchainbank.SampleAsset 95 | | assetId | owner | value | 96 | | 2 | bob@email.com | 60 | 97 | Then I should have the following assets of type org.example.blockchainbank.SampleAsset 98 | | assetId | owner | value | 99 | | 2 | bob@email.com | 60 | 100 | 101 | Scenario: Bob cannot update Alice's assets 102 | When I use the identity bob1 103 | And I update the following asset of type org.example.blockchainbank.SampleAsset 104 | | assetId | owner | value | 105 | | 1 | alice@email.com | 60 | 106 | Then I should get an error matching /does not have .* access to resource/ 107 | 108 | Scenario: Alice can remove her assets 109 | When I use the identity alice1 110 | And I remove the following asset of type org.example.blockchainbank.SampleAsset 111 | | assetId | 112 | | 1 | 113 | Then I should not have the following assets of type org.example.blockchainbank.SampleAsset 114 | | assetId | 115 | | 1 | 116 | 117 | Scenario: Alice cannot remove Bob's assets 118 | When I use the identity alice1 119 | And I remove the following asset of type org.example.blockchainbank.SampleAsset 120 | | assetId | 121 | | 2 | 122 | Then I should get an error matching /does not have .* access to resource/ 123 | 124 | Scenario: Bob can remove his assets 125 | When I use the identity bob1 126 | And I remove the following asset of type org.example.blockchainbank.SampleAsset 127 | | assetId | 128 | | 2 | 129 | Then I should not have the following assets of type org.example.blockchainbank.SampleAsset 130 | | assetId | 131 | | 2 | 132 | 133 | Scenario: Bob cannot remove Alice's assets 134 | When I use the identity bob1 135 | And I remove the following asset of type org.example.blockchainbank.SampleAsset 136 | | assetId | 137 | | 1 | 138 | Then I should get an error matching /does not have .* access to resource/ 139 | 140 | Scenario: Alice can submit a transaction for her assets 141 | When I use the identity alice1 142 | And I submit the following transaction of type org.example.blockchainbank.SampleTransaction 143 | | asset | newValue | 144 | | 1 | 50 | 145 | Then I should have the following assets of type org.example.blockchainbank.SampleAsset 146 | | assetId | owner | value | 147 | | 1 | alice@email.com | 50 | 148 | And I should have received the following event of type org.example.blockchainbank.SampleEvent 149 | | asset | oldValue | newValue | 150 | | 1 | 10 | 50 | 151 | 152 | Scenario: Alice cannot submit a transaction for Bob's assets 153 | When I use the identity alice1 154 | And I submit the following transaction of type org.example.blockchainbank.SampleTransaction 155 | | asset | newValue | 156 | | 2 | 50 | 157 | Then I should get an error matching /does not have .* access to resource/ 158 | 159 | Scenario: Bob can submit a transaction for his assets 160 | When I use the identity bob1 161 | And I submit the following transaction of type org.example.blockchainbank.SampleTransaction 162 | | asset | newValue | 163 | | 2 | 60 | 164 | Then I should have the following assets of type org.example.blockchainbank.SampleAsset 165 | | assetId | owner | value | 166 | | 2 | bob@email.com | 60 | 167 | And I should have received the following event of type org.example.blockchainbank.SampleEvent 168 | | asset | oldValue | newValue | 169 | | 2 | 20 | 60 | 170 | 171 | Scenario: Bob cannot submit a transaction for Alice's assets 172 | When I use the identity bob1 173 | And I submit the following transaction of type org.example.blockchainbank.SampleTransaction 174 | | asset | newValue | 175 | | 1 | 60 | 176 | Then I should get an error matching /does not have .* access to resource/ 177 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/logic.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 'use strict'; 16 | 17 | const AdminConnection = require('composer-admin').AdminConnection; 18 | const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection; 19 | const { BusinessNetworkDefinition, CertificateUtil, IdCard } = require('composer-common'); 20 | const path = require('path'); 21 | 22 | const chai = require('chai'); 23 | chai.should(); 24 | chai.use(require('chai-as-promised')); 25 | 26 | const namespace = 'org.example.blockchainbank'; 27 | const assetType = 'SampleAsset'; 28 | const assetNS = namespace + '.' + assetType; 29 | const participantType = 'SampleParticipant'; 30 | const participantNS = namespace + '.' + participantType; 31 | 32 | describe('#' + namespace, () => { 33 | // In-memory card store for testing so cards are not persisted to the file system 34 | const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore( { type: 'composer-wallet-inmemory' } ); 35 | 36 | // Embedded connection used for local testing 37 | const connectionProfile = { 38 | name: 'embedded', 39 | 'x-type': 'embedded' 40 | }; 41 | 42 | // Name of the business network card containing the administrative identity for the business network 43 | const adminCardName = 'admin'; 44 | 45 | // Admin connection to the blockchain, used to deploy the business network 46 | let adminConnection; 47 | 48 | // This is the business network connection the tests will use. 49 | let businessNetworkConnection; 50 | 51 | // This is the factory for creating instances of types. 52 | let factory; 53 | 54 | // These are the identities for Alice and Bob. 55 | const aliceCardName = 'alice'; 56 | const bobCardName = 'bob'; 57 | 58 | // These are a list of receieved events. 59 | let events; 60 | 61 | let businessNetworkName; 62 | 63 | before(async () => { 64 | // Generate certificates for use with the embedded connection 65 | const credentials = CertificateUtil.generate({ commonName: 'admin' }); 66 | 67 | // Identity used with the admin connection to deploy business networks 68 | const deployerMetadata = { 69 | version: 1, 70 | userName: 'PeerAdmin', 71 | roles: [ 'PeerAdmin', 'ChannelAdmin' ] 72 | }; 73 | const deployerCard = new IdCard(deployerMetadata, connectionProfile); 74 | deployerCard.setCredentials(credentials); 75 | const deployerCardName = 'PeerAdmin'; 76 | 77 | adminConnection = new AdminConnection({ cardStore: cardStore }); 78 | 79 | await adminConnection.importCard(deployerCardName, deployerCard); 80 | await adminConnection.connect(deployerCardName); 81 | }); 82 | 83 | /** 84 | * 85 | * @param {String} cardName The card name to use for this identity 86 | * @param {Object} identity The identity details 87 | */ 88 | async function importCardForIdentity(cardName, identity) { 89 | const metadata = { 90 | userName: identity.userID, 91 | version: 1, 92 | enrollmentSecret: identity.userSecret, 93 | businessNetwork: businessNetworkName 94 | }; 95 | const card = new IdCard(metadata, connectionProfile); 96 | await adminConnection.importCard(cardName, card); 97 | } 98 | 99 | // This is called before each test is executed. 100 | beforeEach(async () => { 101 | // Generate a business network definition from the project directory. 102 | let businessNetworkDefinition = await BusinessNetworkDefinition.fromDirectory(path.resolve(__dirname, '..')); 103 | businessNetworkName = businessNetworkDefinition.getName(); 104 | await adminConnection.install(businessNetworkDefinition); 105 | const startOptions = { 106 | networkAdmins: [ 107 | { 108 | userName: 'admin', 109 | enrollmentSecret: 'adminpw' 110 | } 111 | ] 112 | }; 113 | const adminCards = await adminConnection.start(businessNetworkName, businessNetworkDefinition.getVersion(), startOptions); 114 | await adminConnection.importCard(adminCardName, adminCards.get('admin')); 115 | 116 | // Create and establish a business network connection 117 | businessNetworkConnection = new BusinessNetworkConnection({ cardStore: cardStore }); 118 | events = []; 119 | businessNetworkConnection.on('event', event => { 120 | events.push(event); 121 | }); 122 | await businessNetworkConnection.connect(adminCardName); 123 | 124 | // Get the factory for the business network. 125 | factory = businessNetworkConnection.getBusinessNetwork().getFactory(); 126 | 127 | const participantRegistry = await businessNetworkConnection.getParticipantRegistry(participantNS); 128 | // Create the participants. 129 | const alice = factory.newResource(namespace, participantType, 'alice@email.com'); 130 | alice.firstName = 'Alice'; 131 | alice.lastName = 'A'; 132 | 133 | const bob = factory.newResource(namespace, participantType, 'bob@email.com'); 134 | bob.firstName = 'Bob'; 135 | bob.lastName = 'B'; 136 | 137 | participantRegistry.addAll([alice, bob]); 138 | 139 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 140 | // Create the assets. 141 | const asset1 = factory.newResource(namespace, assetType, '1'); 142 | asset1.owner = factory.newRelationship(namespace, participantType, 'alice@email.com'); 143 | asset1.value = '10'; 144 | 145 | const asset2 = factory.newResource(namespace, assetType, '2'); 146 | asset2.owner = factory.newRelationship(namespace, participantType, 'bob@email.com'); 147 | asset2.value = '20'; 148 | 149 | assetRegistry.addAll([asset1, asset2]); 150 | 151 | // Issue the identities. 152 | let identity = await businessNetworkConnection.issueIdentity(participantNS + '#alice@email.com', 'alice1'); 153 | await importCardForIdentity(aliceCardName, identity); 154 | identity = await businessNetworkConnection.issueIdentity(participantNS + '#bob@email.com', 'bob1'); 155 | await importCardForIdentity(bobCardName, identity); 156 | }); 157 | 158 | /** 159 | * Reconnect using a different identity. 160 | * @param {String} cardName The name of the card for the identity to use 161 | */ 162 | async function useIdentity(cardName) { 163 | await businessNetworkConnection.disconnect(); 164 | businessNetworkConnection = new BusinessNetworkConnection({ cardStore: cardStore }); 165 | events = []; 166 | businessNetworkConnection.on('event', (event) => { 167 | events.push(event); 168 | }); 169 | await businessNetworkConnection.connect(cardName); 170 | factory = businessNetworkConnection.getBusinessNetwork().getFactory(); 171 | } 172 | 173 | it('Alice can read all of the assets', async () => { 174 | // Use the identity for Alice. 175 | await useIdentity(aliceCardName); 176 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 177 | const assets = await assetRegistry.getAll(); 178 | 179 | // Validate the assets. 180 | assets.should.have.lengthOf(2); 181 | const asset1 = assets[0]; 182 | asset1.owner.getFullyQualifiedIdentifier().should.equal(participantNS + '#alice@email.com'); 183 | asset1.value.should.equal('10'); 184 | const asset2 = assets[1]; 185 | asset2.owner.getFullyQualifiedIdentifier().should.equal(participantNS + '#bob@email.com'); 186 | asset2.value.should.equal('20'); 187 | }); 188 | 189 | it('Bob can read all of the assets', async () => { 190 | // Use the identity for Bob. 191 | await useIdentity(bobCardName); 192 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 193 | const assets = await assetRegistry.getAll(); 194 | 195 | // Validate the assets. 196 | assets.should.have.lengthOf(2); 197 | const asset1 = assets[0]; 198 | asset1.owner.getFullyQualifiedIdentifier().should.equal(participantNS + '#alice@email.com'); 199 | asset1.value.should.equal('10'); 200 | const asset2 = assets[1]; 201 | asset2.owner.getFullyQualifiedIdentifier().should.equal(participantNS + '#bob@email.com'); 202 | asset2.value.should.equal('20'); 203 | }); 204 | 205 | it('Alice can add assets that she owns', async () => { 206 | // Use the identity for Alice. 207 | await useIdentity(aliceCardName); 208 | 209 | // Create the asset. 210 | let asset3 = factory.newResource(namespace, assetType, '3'); 211 | asset3.owner = factory.newRelationship(namespace, participantType, 'alice@email.com'); 212 | asset3.value = '30'; 213 | 214 | // Add the asset, then get the asset. 215 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 216 | await assetRegistry.add(asset3); 217 | 218 | // Validate the asset. 219 | asset3 = await assetRegistry.get('3'); 220 | asset3.owner.getFullyQualifiedIdentifier().should.equal(participantNS + '#alice@email.com'); 221 | asset3.value.should.equal('30'); 222 | }); 223 | 224 | it('Alice cannot add assets that Bob owns', async () => { 225 | // Use the identity for Alice. 226 | await useIdentity(aliceCardName); 227 | 228 | // Create the asset. 229 | const asset3 = factory.newResource(namespace, assetType, '3'); 230 | asset3.owner = factory.newRelationship(namespace, participantType, 'bob@email.com'); 231 | asset3.value = '30'; 232 | 233 | // Try to add the asset, should fail. 234 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 235 | assetRegistry.add(asset3).should.be.rejectedWith(/does not have .* access to resource/); 236 | }); 237 | 238 | it('Bob can add assets that he owns', async () => { 239 | // Use the identity for Bob. 240 | await useIdentity(bobCardName); 241 | 242 | // Create the asset. 243 | let asset4 = factory.newResource(namespace, assetType, '4'); 244 | asset4.owner = factory.newRelationship(namespace, participantType, 'bob@email.com'); 245 | asset4.value = '40'; 246 | 247 | // Add the asset, then get the asset. 248 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 249 | await assetRegistry.add(asset4); 250 | 251 | // Validate the asset. 252 | asset4 = await assetRegistry.get('4'); 253 | asset4.owner.getFullyQualifiedIdentifier().should.equal(participantNS + '#bob@email.com'); 254 | asset4.value.should.equal('40'); 255 | }); 256 | 257 | it('Bob cannot add assets that Alice owns', async () => { 258 | // Use the identity for Bob. 259 | await useIdentity(bobCardName); 260 | 261 | // Create the asset. 262 | const asset4 = factory.newResource(namespace, assetType, '4'); 263 | asset4.owner = factory.newRelationship(namespace, participantType, 'alice@email.com'); 264 | asset4.value = '40'; 265 | 266 | // Try to add the asset, should fail. 267 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 268 | assetRegistry.add(asset4).should.be.rejectedWith(/does not have .* access to resource/); 269 | 270 | }); 271 | 272 | it('Alice can update her assets', async () => { 273 | // Use the identity for Alice. 274 | await useIdentity(aliceCardName); 275 | 276 | // Create the asset. 277 | let asset1 = factory.newResource(namespace, assetType, '1'); 278 | asset1.owner = factory.newRelationship(namespace, participantType, 'alice@email.com'); 279 | asset1.value = '50'; 280 | 281 | // Update the asset, then get the asset. 282 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 283 | await assetRegistry.update(asset1); 284 | 285 | // Validate the asset. 286 | asset1 = await assetRegistry.get('1'); 287 | asset1.owner.getFullyQualifiedIdentifier().should.equal(participantNS + '#alice@email.com'); 288 | asset1.value.should.equal('50'); 289 | }); 290 | 291 | it('Alice cannot update Bob\'s assets', async () => { 292 | // Use the identity for Alice. 293 | await useIdentity(aliceCardName); 294 | 295 | // Create the asset. 296 | const asset2 = factory.newResource(namespace, assetType, '2'); 297 | asset2.owner = factory.newRelationship(namespace, participantType, 'bob@email.com'); 298 | asset2.value = '50'; 299 | 300 | // Try to update the asset, should fail. 301 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 302 | assetRegistry.update(asset2).should.be.rejectedWith(/does not have .* access to resource/); 303 | }); 304 | 305 | it('Bob can update his assets', async () => { 306 | // Use the identity for Bob. 307 | await useIdentity(bobCardName); 308 | 309 | // Create the asset. 310 | let asset2 = factory.newResource(namespace, assetType, '2'); 311 | asset2.owner = factory.newRelationship(namespace, participantType, 'bob@email.com'); 312 | asset2.value = '60'; 313 | 314 | // Update the asset, then get the asset. 315 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 316 | await assetRegistry.update(asset2); 317 | 318 | // Validate the asset. 319 | asset2 = await assetRegistry.get('2'); 320 | asset2.owner.getFullyQualifiedIdentifier().should.equal(participantNS + '#bob@email.com'); 321 | asset2.value.should.equal('60'); 322 | }); 323 | 324 | it('Bob cannot update Alice\'s assets', async () => { 325 | // Use the identity for Bob. 326 | await useIdentity(bobCardName); 327 | 328 | // Create the asset. 329 | const asset1 = factory.newResource(namespace, assetType, '1'); 330 | asset1.owner = factory.newRelationship(namespace, participantType, 'alice@email.com'); 331 | asset1.value = '60'; 332 | 333 | // Update the asset, then get the asset. 334 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 335 | assetRegistry.update(asset1).should.be.rejectedWith(/does not have .* access to resource/); 336 | 337 | }); 338 | 339 | it('Alice can remove her assets', async () => { 340 | // Use the identity for Alice. 341 | await useIdentity(aliceCardName); 342 | 343 | // Remove the asset, then test the asset exists. 344 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 345 | await assetRegistry.remove('1'); 346 | const exists = await assetRegistry.exists('1'); 347 | exists.should.be.false; 348 | }); 349 | 350 | it('Alice cannot remove Bob\'s assets', async () => { 351 | // Use the identity for Alice. 352 | await useIdentity(aliceCardName); 353 | 354 | // Remove the asset, then test the asset exists. 355 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 356 | assetRegistry.remove('2').should.be.rejectedWith(/does not have .* access to resource/); 357 | }); 358 | 359 | it('Bob can remove his assets', async () => { 360 | // Use the identity for Bob. 361 | await useIdentity(bobCardName); 362 | 363 | // Remove the asset, then test the asset exists. 364 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 365 | await assetRegistry.remove('2'); 366 | const exists = await assetRegistry.exists('2'); 367 | exists.should.be.false; 368 | }); 369 | 370 | it('Bob cannot remove Alice\'s assets', async () => { 371 | // Use the identity for Bob. 372 | await useIdentity(bobCardName); 373 | 374 | // Remove the asset, then test the asset exists. 375 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 376 | assetRegistry.remove('1').should.be.rejectedWith(/does not have .* access to resource/); 377 | }); 378 | 379 | it('Alice can submit a transaction for her assets', async () => { 380 | // Use the identity for Alice. 381 | await useIdentity(aliceCardName); 382 | 383 | // Submit the transaction. 384 | const transaction = factory.newTransaction(namespace, 'SampleTransaction'); 385 | transaction.asset = factory.newRelationship(namespace, assetType, '1'); 386 | transaction.newValue = '50'; 387 | await businessNetworkConnection.submitTransaction(transaction); 388 | 389 | // Get the asset. 390 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 391 | const asset1 = await assetRegistry.get('1'); 392 | 393 | // Validate the asset. 394 | asset1.owner.getFullyQualifiedIdentifier().should.equal(participantNS + '#alice@email.com'); 395 | asset1.value.should.equal('50'); 396 | 397 | // Validate the events. 398 | events.should.have.lengthOf(1); 399 | const event = events[0]; 400 | event.eventId.should.be.a('string'); 401 | event.timestamp.should.be.an.instanceOf(Date); 402 | event.asset.getFullyQualifiedIdentifier().should.equal(assetNS + '#1'); 403 | event.oldValue.should.equal('10'); 404 | event.newValue.should.equal('50'); 405 | }); 406 | 407 | it('Alice cannot submit a transaction for Bob\'s assets', async () => { 408 | // Use the identity for Alice. 409 | await useIdentity(aliceCardName); 410 | 411 | // Submit the transaction. 412 | const transaction = factory.newTransaction(namespace, 'SampleTransaction'); 413 | transaction.asset = factory.newRelationship(namespace, assetType, '2'); 414 | transaction.newValue = '50'; 415 | businessNetworkConnection.submitTransaction(transaction).should.be.rejectedWith(/does not have .* access to resource/); 416 | }); 417 | 418 | it('Bob can submit a transaction for his assets', async () => { 419 | // Use the identity for Bob. 420 | await useIdentity(bobCardName); 421 | 422 | // Submit the transaction. 423 | const transaction = factory.newTransaction(namespace, 'SampleTransaction'); 424 | transaction.asset = factory.newRelationship(namespace, assetType, '2'); 425 | transaction.newValue = '60'; 426 | await businessNetworkConnection.submitTransaction(transaction); 427 | 428 | // Get the asset. 429 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 430 | const asset2 = await assetRegistry.get('2'); 431 | 432 | // Validate the asset. 433 | asset2.owner.getFullyQualifiedIdentifier().should.equal(participantNS + '#bob@email.com'); 434 | asset2.value.should.equal('60'); 435 | 436 | // Validate the events. 437 | events.should.have.lengthOf(1); 438 | const event = events[0]; 439 | event.eventId.should.be.a('string'); 440 | event.timestamp.should.be.an.instanceOf(Date); 441 | event.asset.getFullyQualifiedIdentifier().should.equal(assetNS + '#2'); 442 | event.oldValue.should.equal('20'); 443 | event.newValue.should.equal('60'); 444 | }); 445 | 446 | it('Bob cannot submit a transaction for Alice\'s assets', async () => { 447 | // Use the identity for Bob. 448 | await useIdentity(bobCardName); 449 | 450 | // Submit the transaction. 451 | const transaction = factory.newTransaction(namespace, 'SampleTransaction'); 452 | transaction.asset = factory.newRelationship(namespace, assetType, '1'); 453 | transaction.newValue = '60'; 454 | businessNetworkConnection.submitTransaction(transaction).should.be.rejectedWith(/does not have .* access to resource/); 455 | }); 456 | 457 | }); 458 | --------------------------------------------------------------------------------