├── resources └── maimi.png ├── .gitignore ├── features ├── support │ └── index.js └── sample.feature ├── package.json ├── .eslintrc.yml ├── startNetwork.sh ├── FHIR_resources_notes.txt ├── permissions.acl ├── README.md ├── lib └── logic.js ├── models └── org.cc.patientdatanetwork.cto └── test └── logic.js /resources/maimi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micklynch/patient-data-network/HEAD/resources/maimi.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | node_modules 3 | 4 | # Output files 5 | dist 6 | 7 | # Details of my specific connection 8 | DevServer_connection.json 9 | 10 | # Don't include any cards or bna files 11 | *.card 12 | *.bna 13 | 14 | # Don't include any of my VSCode specifics 15 | .vscode 16 | 17 | # Don't include any testing output 18 | .nyc_output 19 | 20 | # Don't include my hack for cleaning my system (it might make other people's machines explode) 21 | removeDockerImages.sh 22 | -------------------------------------------------------------------------------- /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.20.0" 4 | }, 5 | "name": "patient-data-network", 6 | "version": "0.0.2", 7 | "description": "A secure and transparent network for sharing health data", 8 | "scripts": { 9 | "prepublish": "mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/patient-data-network.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": "Mick Lynch", 19 | "license": "Apache-2.0", 20 | "devDependencies": { 21 | "composer-admin": "^0.20.0", 22 | "composer-cli": "^0.20.0", 23 | "composer-client": "^0.20.0", 24 | "composer-common": "^0.20.0", 25 | "composer-connector-embedded": "^0.20.0", 26 | "composer-cucumber-steps": "^0.20.0", 27 | "chai": "latest", 28 | "chai-as-promised": "latest", 29 | "cucumber": "^2.2.0", 30 | "eslint": "latest", 31 | "nyc": "latest", 32 | "mkdirp": "latest", 33 | "mocha": "latest", 34 | "ajv": "^6.5.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /startNetwork.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # (c) Mick Lynch (https://mlynch.redbrick.dcu.ie) 4 | # Refresh my Hyperledger Fabrick network 5 | # Needs to be run inside the project folder 6 | NETWORK_NAME=$(basename "$PWD") 7 | 8 | NETWORK_VERSION=($1) 9 | if [ $# -eq 0 ] 10 | then 11 | echo "Please specify network version number" 12 | exit 0 13 | fi 14 | echo 'Running Network: ' $NETWORK_NAME ': Version ' $NETWORK_VERSION 15 | 16 | # Create a new BNA file 17 | composer archive create -t dir -n . 18 | 19 | # shut down fabric 20 | $FABRIC_DEV_SERVER/./teardownFabric.sh 21 | $FABRIC_DEV_SERVER/./teardownAllDocker.sh 22 | 23 | # Remove all the previous cards 24 | rm -Rf ~/.composer 25 | 26 | # Create initial Peer card 27 | if [ "${FABRIC_DEV_SERVER}" = "" ]; then 28 | echo "Need to define the location of your Fabric Server Scripts" 29 | fi 30 | $FABRIC_DEV_SERVER/./startFabric.sh 31 | $FABRIC_DEV_SERVER/./createPeerAdminCard.sh 32 | 33 | # Install peer card onto my network 34 | composer network install --card PeerAdmin@hlfv1 --archiveFile $NETWORK_NAME\@$NETWORK_VERSION.bna 35 | 36 | # Start the network with the PeerAdmin card 37 | composer network start --networkName $NETWORK_NAME --networkVersion $NETWORK_VERSION --networkAdmin admin --networkAdminEnrollSecret adminpw --card PeerAdmin@hlfv1 --file networkadmin.card 38 | # Import the network admin card to the network 39 | composer card import --file networkadmin.card 40 | 41 | # Ping the card to activate it 42 | composer network ping --card admin@$NETWORK_NAME 43 | # or Upgrade network 44 | # composer network upgrade --networkName $NETWORK_NAME -V $NETWORK_VERSION --card admin@$NETWORK_NAME 45 | 46 | 47 | # composer network ping --card admin@$NETWORK_NAME 48 | 49 | # Start-up composer playground 50 | composer-playground 51 | 52 | # Create REST server locally 53 | #composer-rest-server -c admin@$NETWORK_NAME -n never -w true 54 | -------------------------------------------------------------------------------- /FHIR_resources_notes.txt: -------------------------------------------------------------------------------- 1 | Based on http://hapi.fhir.org/# list 2 | 3 | Participants 4 | ============ 5 | Patient 6 | Practitioner 7 | Person 8 | Organization 9 | ResearchSubject 10 | 11 | Events 12 | ======= 13 | AuditEvent 14 | 15 | Assets 16 | ======== 17 | Medication 18 | Procedure 19 | Observation 20 | Encounter 21 | Immunization 22 | Appointment 23 | MedicationStatement 24 | Condition 25 | MedicationRequest 26 | Claim 27 | DiagnosticReport 28 | CarePlan 29 | List 30 | HealthcareService 31 | MedicationAdministration 32 | Goal 33 | Bundle 34 | AllergyIntolerance 35 | Provenance 36 | StructureDefinition 37 | SearchParameter 38 | QuestionnaireResponse 39 | Questionnaire 40 | ProcedureRequest 41 | DocumentReference 42 | ImagingStudy 43 | Media 44 | MedicationDispense 45 | EpisodeOfCare 46 | Composition 47 | DeviceMetric 48 | Communication 49 | FamilyMemberHistory 50 | AppointmentResponse 51 | AdverseEvent 52 | Basic 53 | Coverage 54 | Specimen 55 | CommunicationRequest 56 | Consent 57 | ReferralRequest 58 | Task 59 | ActivityDefinition 60 | ClinicalImpression 61 | OperationDefinition 62 | Schedule 63 | Subscription 64 | DocumentManifest 65 | ImplementationGuide 66 | RiskAssessment 67 | Substance 68 | ExplanationOfBenefit 69 | CapabilityStatement 70 | Group 71 | BodySite 72 | ClaimResponse 73 | PlanDefinition 74 | Account 75 | DataElement 76 | VisionPrescription 77 | ChargeItem 78 | DetectedIssue 79 | DeviceUseStatement 80 | Library 81 | NutritionOrder 82 | ProcessRequest 83 | ProcessResponse 84 | RequestGroup 85 | StructureMap 86 | CompartmentDefinition 87 | EligibilityRequest 88 | ImmunizationRecommendation 89 | SupplyDelivery 90 | DeviceRequest 91 | ImagingManifest 92 | SupplyRequest 93 | Contract 94 | ExpansionProfile 95 | Measure 96 | EligibilityResponse 97 | EnrollmentRequest 98 | EnrollmentResponse 99 | GraphDefinition 100 | GuidanceResponse 101 | MeasureReport 102 | MessageDefinition 103 | PaymentNotice 104 | PaymentReconciliation 105 | ServiceDefinition 106 | OperationOutcome 107 | 108 | Other 109 | ===== 110 | PractitionerRole 111 | Location 112 | CareTeam 113 | RelatedPerson 114 | ResearchStudy 115 | Device 116 | Binary 117 | TestReport 118 | TestScript 119 | MessageHeader 120 | ValueSet 121 | Parameters 122 | Sequence 123 | ConceptMap 124 | Flag 125 | Linkage 126 | Endpoint 127 | NamingSystem 128 | Slot 129 | DeviceComponent 130 | CodeSystem 131 | -------------------------------------------------------------------------------- /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 | /** 16 | * Rules for data access 17 | */ 18 | rule PatientsHaveAccessToTheirData { 19 | description: "Allow patients to see all of their data" 20 | participant(p): "org.cc.patientdatanetwork.Patient" 21 | operation: ALL 22 | resource(r): "org.cc.patientdatanetwork.*" 23 | condition: (r.owner.getIdentifier() == p.getIdentifier()) 24 | action: ALLOW 25 | } 26 | 27 | rule PractitionersCanSeeDataSharedWithThem { 28 | description: "Allow practitioners to see data that is shared with them" 29 | participant(p): "org.cc.patientdatanetwork.Practitioner" 30 | operation: READ, UPDATE 31 | resource(r): "org.cc.patientdatanetwork.*" 32 | condition: (r.practitionersWithAccess.indexOf(p.getIdentifier()) > -1) 33 | action: ALLOW 34 | } 35 | 36 | rule OrgsCanSeeTheirData { 37 | description: "Allow organizations to see data where they are the custodian" 38 | participant(p): "org.cc.patientdatanetwork.Organization" 39 | operation: READ 40 | resource(r): "org.cc.patientdatanetwork.*" 41 | condition: (r.orgsWithAccess.indexOf(p.getIdentifier()) > -1) 42 | action: ALLOW 43 | } 44 | 45 | rule EveryoneCanShareTheirOwnAssetsWithAPractitioner { 46 | description: "Allow all participants to submit transactions" 47 | participant: "org.cc.patientdatanetwork.Patient" 48 | operation: CREATE 49 | resource: "org.cc.patientdatanetwork.ShareWithPractitioner" 50 | action: ALLOW 51 | } 52 | 53 | 54 | rule EveryoneCanShareTheirOwnAssetsWithAnOrg { 55 | description: "Allow all participants to submit transactions" 56 | participant: "org.cc.patientdatanetwork.Patient" 57 | operation: CREATE 58 | resource: "org.cc.patientdatanetwork.ShareWithOrg" 59 | action: ALLOW 60 | } 61 | 62 | rule SystemACL { 63 | description: "System ACL to permit all access" 64 | participant: "org.hyperledger.composer.system.Participant" 65 | operation: ALL 66 | resource: "org.hyperledger.composer.system.**" 67 | action: ALLOW 68 | } 69 | 70 | rule NetworkAdminUser { 71 | description: "Grant business network administrators full access to user resources" 72 | participant: "org.hyperledger.composer.system.NetworkAdmin" 73 | operation: ALL 74 | resource: "**" 75 | action: ALLOW 76 | } 77 | 78 | rule NetworkAdminSystem { 79 | description: "Grant business network administrators full access to system resources" 80 | participant: "org.hyperledger.composer.system.NetworkAdmin" 81 | operation: ALL 82 | resource: "org.hyperledger.composer.system.**" 83 | action: ALLOW 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Patient Data Network 2 | 3 | 4 | 5 | _A secure and transparent network for sharing health data_ 6 | 7 | The Patient Data Network is a project that enables people to access and share their health data between people, healthcare professionals and healthcare practitioners. It is built with the _[Hyperledger Blockchain Composer](https://www.hyperledger.org)_ and uses _[HL7 FHIR](https://www.hl7.org/fhir/)_ resources. 8 | 9 | A detailed description of the project can be found on [Medium](https://medium.com/@micklynch_6905/the-patient-data-network-project-ef84a3d13781). 10 | 11 | The network consists of three categories; 12 | 13 | ### Participants 14 | * Patients 15 | * Individual Healthcare Practitioners (doctors, dentists, physiotherapists, chiropractors etc.) 16 | * Healthcare Organizations (Hospitals, research organizations, insurers) 17 | 18 | ### Assets 19 | * Procedure 20 | * Imaging Study 21 | * Medication 22 | * ... 23 | * ... 24 | 25 | ### Transaction 26 | * Consent to share data assets 27 | 28 | 29 | In FHIR, we have an Audit resource, but that is implicit in the blockchain transactions. The data doesn't live on the blockchain, but instead the blockchain holds a link to the resource which is stored at the healthcare organization. We also store a hash of the data ensuring that the data is not tampered with. 30 | 31 | Please check our project Wiki page for details on how to contribute... 32 | 33 | *** 34 | 35 | ## Building the network 36 | 37 | To build the project, you must follow the installation instructions given on the [Hyperledger Composer Installation guide](https://hyperledger.github.io/composer/v0.19/installing/installing-index). 38 | 39 | Please follow *all* the steps to install the Development Environment of Hyperledger Composer for your OS. 40 | 41 | Here are the specific versions of software that were used to build this project: 42 | 43 | ### Node 44 | Node installation was performed using [NVM](https://github.com/creationix/nvm) (Node Version Manager) 45 | ``` 46 | $ nvm --version 47 | 0.33.0 48 | ``` 49 | We are using the LTS version of Node 50 | ``` 51 | $ nvm current 52 | v8.12.0 53 | ``` 54 | ### Docker 55 | 56 | ``` 57 | $ docker --version 58 | Docker version 18.06.1-ce, build e68fc7a215 59 | 60 | ``` 61 | ### Microsoft Visual Code 62 | ``` 63 | $ code --version 64 | 1.27.2 65 | 66 | ``` 67 | After installation, we installed the Hypledger CTO plugin. 68 | 69 | ### Hyperledger Composer 70 | 71 | ``` 72 | $ composer --version 73 | v0.20.0 74 | ``` 75 | ## Running the application 76 | 77 | * Ensure that docker is running. `docker ps` 78 | * Set the environmental variables 79 | * The `$FABRIC_VERSION`, which I am using hlfv12 80 | * The location of the Fabric Dev Servers with the variable `$FABRIC_DEV_SERVERS` 81 | * Run the script file to start the network 82 | ``` 83 | $./startNetwork.sh 84 | ``` 85 | The `````` must be the version number that appears in the [`package.json`](package.json) file 86 | * Run `composer-playground` from the terminal 87 | * Open [http://localhost:8080](http://localhost:8080) in your browser of choice 88 | * At the bottom of the `admin@patient-data-network` card, you can click on the _"Connect now"_ link 89 | * Now you are up and running, feel free to play around by switching your ID in the ID Registry between patients and practitioners.. 90 | * Feedback and contributions are welcome 91 | 92 | --- 93 | 94 | ## Acknowledgements 95 | Thank you to the Hyperledger Composer Project and HL7 FHIR creators and contributors, without whom this project would not exist. 96 | 97 | ## Creator 98 | [Mick Lynch](https://mlynch.redbrick.dcu.ie) is an engineer PhD who loves translating ideas into novel products that have a real impact. He has been working in health IT, software development and team management since 2006. 99 | -------------------------------------------------------------------------------- /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 | * Write your transction processor functions here 18 | */ 19 | 20 | 21 | /** 22 | * Consent transaction for sharing a data asset with a practitioner 23 | * @param {org.cc.patientdatanetwork.ShareWithPractitioner} ShareWithPractitioner 24 | * @transaction 25 | */ 26 | async function ShareWithPractitioner(tx) { 27 | // Get the Procedure asset + and share with practitioner. 28 | let practitionerId = tx.practitioner.participantId; 29 | 30 | // Get the asset registry for the asset. 31 | const assetRegistry = await getAssetRegistry(tx.assetType); 32 | let asset = await assetRegistry.get(tx.assetReference); 33 | await console.log('all the', tx.assetType, ' items are ', tx.assetReference); 34 | 35 | if (asset.practitionersWithAccess) { 36 | if (asset.practitionersWithAccess.indexOf(practitionerId) > -1) { 37 | console.log('Asset ' + asset.assetId + ' is already shared with ' + tx.practitioner.firstName); 38 | } 39 | else { 40 | // add practitioner ID to practitionersWithAccess array 41 | asset.practitionersWithAccess.push(practitionerId); 42 | } 43 | } 44 | else { 45 | console.log('You are sharing this item for the first time'); 46 | asset.practitionersWithAccess = [practitionerId]; 47 | } 48 | // Update the asset in the asset registry. 49 | await assetRegistry.update(asset); 50 | 51 | // Emit an event for the modified asset. 52 | let event = getFactory().newEvent('org.cc.patientdatanetwork', 'AssetSharedwithPractitioner'); 53 | event.assetType = tx.assetType; 54 | event.assetReference = tx.assetReference; 55 | event.practitioner = tx.practitioner; 56 | emit(event); 57 | } 58 | 59 | /** 60 | * ORGANIZATION SHARING 61 | * ==================== 62 | * The sections below share patient data assets to organizations 63 | */ 64 | 65 | 66 | /** 67 | * Consent transaction for a data asset with an Org 68 | * @param {org.cc.patientdatanetwork.ShareWithOrg} ShareWithOrg 69 | * @transaction 70 | */ 71 | async function ShareWithOrg(tx) { 72 | // Get the Medication asset + share with new practitioner. 73 | let organizationId = tx.organization.organizationId; 74 | 75 | // Get the asset registry for the asset. 76 | const assetRegistry = await getAssetRegistry(tx.assetType); 77 | let asset = await assetRegistry.get(tx.assetReference); 78 | 79 | if (asset.orgsWithAccess) { 80 | if (asset.orgsWithAccess.indexOf(organizationId) > -1) { 81 | console.log('Asset ' + asset.assetId + ' is already shared with ' + tx.organization.name); 82 | } 83 | else { 84 | // add practitioner ID to orgsWithAccess array 85 | asset.orgsWithAccess.push(organizationId); 86 | } 87 | } 88 | else { 89 | console.log('You are sharing this item for the first time'); 90 | asset.orgsWithAccess = [organizationId]; 91 | } 92 | 93 | // Update the asset in the asset registry. 94 | await assetRegistry.update(asset); 95 | 96 | // Emit an event for the modified asset. 97 | let event = getFactory().newEvent('org.cc.patientdatanetwork', 'AssetSharedWithOrg'); 98 | event.assetType = tx.assetType; 99 | event.assetReference = tx.assetReference; 100 | event.organization = tx.organization; 101 | emit(event); 102 | } 103 | -------------------------------------------------------------------------------- /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 organizations of type org.cc.patientdatanetwork.Organization 20 | | organizationId | name | 21 | | 1111 | HospitalABC | 22 | And I have added the following patients of type org.cc.patientdatanetwork.Patient 23 | | participantId | firstName | lastName | medicalRecordNumber | 24 | | alice@email.com | Alice | A | 1234 | 25 | | bob@email.com | Bob | B | 2345 | 26 | And I have added the following assets of type org.cc.patientdatanetwork.Procedure 27 | | assetId | owner | procedureName | 28 | | 1 | alice@email.com | 10 | 29 | | 2 | bob@email.com | 20 | 30 | And I have issued the participant org.cc.patientdatanetwork.Patient#alice@email.com with the identity alice1 31 | And I have issued the participant org.cc.patientdatanetwork.Patient#bob@email.com with the identity bob1 32 | 33 | Scenario: Alice can read all of her assets 34 | When I use the identity alice1 35 | Then I should have the following assets of type org.cc.patientdatanetwork.Procedure 36 | | assetId | owner | procedureName | 37 | | 1 | alice@email.com | Operation | 38 | 39 | Scenario: Bob can read all of the assets 40 | When I use the identity alice1 41 | Then I should have the following assets of type org.cc.patientdatanetwork.Procedure 42 | | assetId | owner | procedureName | 43 | | 2 | bob@email.com | Amputation | 44 | 45 | Scenario: Alice can add assets that she owns 46 | When I use the identity alice1 47 | And I add the following asset of type org.cc.patientdatanetwork.Procedure 48 | | assetId | owner | procedureName | 49 | | 3 | alice@email.com | 30 | 50 | Then I should have the following assets of type org.cc.patientdatanetwork.Procedure 51 | | assetId | owner | procedureName | 52 | | 3 | alice@email.com | 30 | 53 | 54 | Scenario: Alice cannot add assets that Bob owns 55 | When I use the identity alice1 56 | And I add the following asset of type org.cc.patientdatanetwork.Procedure 57 | | assetId | owner | procedureName | 58 | | 3 | bob@email.com | 30 | 59 | Then I should get an error matching /does not have .* access to resource/ 60 | 61 | Scenario: Bob can add assets that he owns 62 | When I use the identity bob1 63 | And I add the following asset of type org.cc.patientdatanetwork.Procedure 64 | | assetId | owner | procedureName | 65 | | 4 | bob@email.com | 40 | 66 | Then I should have the following assets of type org.cc.patientdatanetwork.Procedure 67 | | assetId | owner | procedureName | 68 | | 4 | bob@email.com | 40 | 69 | 70 | Scenario: Bob cannot add assets that Alice owns 71 | When I use the identity bob1 72 | And I add the following asset of type org.cc.patientdatanetwork.Procedure 73 | | assetId | owner | procedureName | 74 | | 4 | alice@email.com | 40 | 75 | Then I should get an error matching /does not have .* access to resource/ 76 | 77 | Scenario: Alice can update her assets 78 | When I use the identity alice1 79 | And I update the following asset of type org.cc.patientdatanetwork.Procedure 80 | | assetId | owner | procedureName | 81 | | 1 | alice@email.com | 50 | 82 | Then I should have the following assets of type org.cc.patientdatanetwork.Procedure 83 | | assetId | owner | procedureName | 84 | | 1 | alice@email.com | 50 | 85 | 86 | Scenario: Alice cannot update Bob's assets 87 | When I use the identity alice1 88 | And I update the following asset of type org.cc.patientdatanetwork.Procedure 89 | | assetId | owner | procedureName | 90 | | 2 | bob@email.com | 50 | 91 | Then I should get an error matching /does not have .* access to resource/ 92 | 93 | Scenario: Bob can update his assets 94 | When I use the identity bob1 95 | And I update the following asset of type org.cc.patientdatanetwork.Procedure 96 | | assetId | owner | procedureName | 97 | | 2 | bob@email.com | 60 | 98 | Then I should have the following assets of type org.cc.patientdatanetwork.Procedure 99 | | assetId | owner | procedureName | 100 | | 2 | bob@email.com | 60 | 101 | 102 | Scenario: Bob cannot update Alice's assets 103 | When I use the identity bob1 104 | And I update the following asset of type org.cc.patientdatanetwork.Procedure 105 | | assetId | owner | procedureName | 106 | | 1 | alice@email.com | 60 | 107 | Then I should get an error matching /does not have .* access to resource/ 108 | 109 | Scenario: Alice can remove her assets 110 | When I use the identity alice1 111 | And I remove the following asset of type org.cc.patientdatanetwork.Procedure 112 | | assetId | 113 | | 1 | 114 | Then I should not have the following assets of type org.cc.patientdatanetwork.Procedure 115 | | assetId | 116 | | 1 | 117 | 118 | Scenario: Alice cannot remove Bob's assets 119 | When I use the identity alice1 120 | And I remove the following asset of type org.cc.patientdatanetwork.Procedure 121 | | assetId | 122 | | 2 | 123 | Then I should get an error matching /does not have .* access to resource/ 124 | 125 | Scenario: Bob can remove his assets 126 | When I use the identity bob1 127 | And I remove the following asset of type org.cc.patientdatanetwork.Procedure 128 | | assetId | 129 | | 2 | 130 | Then I should not have the following assets of type org.cc.patientdatanetwork.Procedure 131 | | assetId | 132 | | 2 | 133 | 134 | Scenario: Bob cannot remove Alice's assets 135 | When I use the identity bob1 136 | And I remove the following asset of type org.cc.patientdatanetwork.Procedure 137 | | assetId | 138 | | 1 | 139 | Then I should get an error matching /does not have .* access to resource/ 140 | 141 | Scenario: Alice can submit a transaction for her assets 142 | When I use the identity alice1 143 | And I submit the following transaction of type org.cc.patientdatanetwork.ShareWithPractitioner 144 | """ 145 | [ 146 | {"$class":"org.cc.patientdatanetwork.ShareWithPractitioner", "assetId":"1", "practitioner":"practitionersWithAccess = [zara@email.com]"} 147 | ] 148 | """ 149 | Then I should have the following assets of type org.cc.patientdatanetwork.Procedure 150 | | assetId | owner | procedureName | 151 | | 1 | alice@email.com | 50 | 152 | And I should have received the following event of type org.cc.patientdatanetwork.ItemShared 153 | | asset | oldValue | newValue | 154 | | 1 | 10 | 50 | 155 | 156 | Scenario: Alice cannot submit a transaction for Bob's assets 157 | When I use the identity alice1 158 | And I submit the following transaction of type org.cc.patientdatanetwork.ShareWithPractitioner 159 | | asset | newValue | 160 | | 2 | 50 | 161 | Then I should get an error matching /does not have .* access to resource/ 162 | 163 | Scenario: Bob can submit a transaction for his assets 164 | When I use the identity bob1 165 | And I submit the following transaction of type org.cc.patientdatanetwork.ShareWithPractitioner 166 | | asset | newValue | 167 | | 2 | 60 | 168 | Then I should have the following assets of type org.cc.patientdatanetwork.Procedure 169 | | assetId | owner | procedureName | 170 | | 2 | bob@email.com | 60 | 171 | And I should have received the following event of type org.cc.patientdatanetwork.ItemShared 172 | | asset | oldValue | newValue | 173 | | 2 | 20 | 60 | 174 | 175 | Scenario: Bob cannot submit a transaction for Alice's assets 176 | When I use the identity bob1 177 | And I submit the following transaction of type org.cc.patientdatanetwork.ShareWithPractitioner 178 | | asset | newValue | 179 | | 1 | 60 | 180 | Then I should get an error matching /does not have .* access to resource/ 181 | -------------------------------------------------------------------------------- /models/org.cc.patientdatanetwork.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 | /** 16 | * Model definitions for FHIR (initially not implementing FHIR...but it will come) 17 | */ 18 | 19 | namespace org.cc.patientdatanetwork 20 | 21 | abstract concept Address { 22 | o String street 23 | o String city 24 | o String country 25 | } 26 | 27 | enum CanadaProvince { 28 | o ONTARIO 29 | o QUEBEC 30 | o BRITISH_COLUBIA 31 | o ALBERTA 32 | o MANITOBA 33 | o SASKATCHEWAN 34 | o NOVA_SCOTIA 35 | o NEW_BRUNSWICK 36 | o NEWFOUNDLAND_AND_LABRADOR 37 | o PRINCE_EDWARD_ISLAND 38 | o NORTHWEST_TERRITORIES 39 | o NUNAVAT 40 | o YUKON 41 | } 42 | 43 | concept CanadaAddress extends Address { 44 | o CanadaProvince province 45 | o String zipcode 46 | } 47 | abstract participant Person identified by participantId { 48 | o String participantId 49 | o String firstName 50 | o String lastName 51 | o String gender optional 52 | o DateTime dateofbirth optional 53 | } 54 | 55 | participant Patient extends Person { 56 | o String medicalRecordNumber 57 | o String resourceUri optional 58 | o String resourceHash optional 59 | o Address address optional 60 | } 61 | 62 | participant Practitioner extends Person { 63 | o String medicalIdNumber optional 64 | --> Organization workingAt 65 | o String resourceUri optional 66 | o String resourceHash optional 67 | o Address address optional 68 | } 69 | 70 | participant Organization identified by organizationId { 71 | o String organizationId 72 | o String name 73 | o Address address optional 74 | } 75 | 76 | /* 77 | * This is the abstract asset that controls all subsequent data assets. Each asset belongs to a Patient 78 | * - practitionersWithAccess is the list of practitioners with whom the data asset is shared 79 | * - orgsWithAccess is the list of organizations with whom the data asset is shared 80 | */ 81 | 82 | abstract asset PatientData identified by assetId { 83 | o String assetId 84 | --> Patient owner 85 | --> Organization custodian optional 86 | o String resourceUri optional 87 | o String resourceHash optional 88 | o String [] practitionersWithAccess optional 89 | o String [] orgsWithAccess optional 90 | } 91 | 92 | enum MedicationStatus { 93 | o ACTIVE 94 | o INACTIVE 95 | o ENTERED_IN_ERROR 96 | } 97 | /** 98 | * For more details please refer to https://www.hl7.org/fhir/Medication.html 99 | **/ 100 | asset Medication extends PatientData { 101 | o MedicationStatus status 102 | o String detail optional 103 | } 104 | /** 105 | * For more details please refer to https://www.hl7.org/fhir/Procedure.html 106 | **/ 107 | asset Procedure extends PatientData { 108 | o String procedureName 109 | o String detail optional 110 | } 111 | /** 112 | * For more details please refer to https://www.hl7.org/fhir/ImagingStudy.html 113 | **/ 114 | asset ImagingStudy extends PatientData { 115 | o String modality 116 | o String note optional 117 | } 118 | /** 119 | * For more details please refer to https://www.hl7.org/fhir/Encounter.html 120 | **/ 121 | asset Encounter extends PatientData { 122 | o String detail optional 123 | } 124 | /** 125 | * For more details please refer to https://www.hl7.org/fhir/Observation.html 126 | **/ 127 | asset Observation extends PatientData { 128 | o String detail optional 129 | } 130 | /** 131 | * For more details please refer to https://www.hl7.org/fhir/Appointment.html 132 | **/ 133 | asset Appointment extends PatientData { 134 | o String detail optional 135 | } 136 | /** 137 | * For more details please refer to https://www.hl7.org/fhir/Immunization.html 138 | **/ 139 | asset Immunization extends PatientData { 140 | o String detail optional 141 | } 142 | /** 143 | * For more details please refer to https://www.hl7.org/fhir/Questionnaire.html 144 | **/ 145 | asset Questionnaire extends PatientData { 146 | o String questionnaireName optional 147 | } 148 | /** 149 | * For more details please refer to https://www.hl7.org/fhir/QuestionnaireResponse.html 150 | **/ 151 | asset QuestionnaireResponse extends PatientData 152 | { 153 | o String detail optional 154 | } 155 | 156 | /** 157 | * For more details please refer to https://www.hl7.org/fhir/MedicationStatement.html 158 | **/ 159 | asset MedicationStatement extends PatientData 160 | { 161 | o String detail optional 162 | } 163 | /** 164 | * For more details please refer to https://www.hl7.org/fhir/Condition.html 165 | **/ 166 | asset Condition extends PatientData 167 | { 168 | o String detail optional 169 | } 170 | /** 171 | * For more details please refer to https://www.hl7.org/fhir/MedicationRequest.html 172 | **/ 173 | asset MedicationRequest extends PatientData 174 | { 175 | o String detail optional 176 | } 177 | /** 178 | * For more details please refer to https://www.hl7.org/fhir/Claim.html 179 | **/ 180 | asset Claim extends PatientData 181 | { 182 | o String detail optional 183 | } 184 | /** 185 | * For more details please refer to https://www.hl7.org/fhir/DiagnosticReport.html 186 | **/ 187 | asset DiagnosticReport extends PatientData 188 | { 189 | o String detail optional 190 | } 191 | /** 192 | * For more details please refer to https://www.hl7.org/fhir/CarePlan.html 193 | **/ 194 | asset CarePlan extends PatientData 195 | { 196 | o String detail optional 197 | } 198 | /** 199 | * For more details please refer to https://www.hl7.org/fhir/List.html 200 | **/ 201 | asset List extends PatientData 202 | { 203 | o String detail optional 204 | } 205 | /** 206 | * For more details please refer to https://www.hl7.org/fhir/HealthcareService.html 207 | **/ 208 | asset HealthcareService extends PatientData 209 | { 210 | o String detail optional 211 | } 212 | /** 213 | * For more details please refer to https://www.hl7.org/fhir/MedicationAdministration.html 214 | **/ 215 | asset MedicationAdministration extends PatientData 216 | { 217 | o String detail optional 218 | } 219 | /** 220 | * For more details please refer to https://www.hl7.org/fhir/Goal.html 221 | **/ 222 | asset Goal extends PatientData 223 | { 224 | o String detail optional 225 | } 226 | /** 227 | * For more details please refer to https://www.hl7.org/fhir/Bundle.html 228 | **/ 229 | asset Bundle extends PatientData 230 | { 231 | o String detail optional 232 | } 233 | /** 234 | * For more details please refer to https://www.hl7.org/fhir/AllergyIntolerance.html 235 | **/ 236 | asset AllergyIntolerance extends PatientData 237 | { 238 | o String detail optional 239 | } 240 | /** 241 | * For more details please refer to https://www.hl7.org/fhir/Provenance.html 242 | **/ 243 | asset Provenance extends PatientData 244 | { 245 | o String detail optional 246 | } 247 | /** 248 | * For more details please refer to https://www.hl7.org/fhir/StructureDefinition.html 249 | **/ 250 | asset StructureDefinition extends PatientData 251 | { 252 | o String detail optional 253 | } 254 | /** 255 | * For more details please refer to https://www.hl7.org/fhir/SearchParameter.html 256 | **/ 257 | asset SearchParameter extends PatientData 258 | { 259 | o String detail optional 260 | } 261 | 262 | 263 | /** 264 | * For more details please refer to https://www.hl7.org/fhir/ProcedureRequest.html 265 | **/ 266 | asset ProcedureRequest extends PatientData 267 | { 268 | o String detail optional 269 | } 270 | /** 271 | * For more details please refer to https://www.hl7.org/fhir/DocumentReference.html 272 | **/ 273 | asset DocumentReference extends PatientData 274 | { 275 | o String detail optional 276 | } 277 | 278 | /** 279 | * For more details please refer to https://www.hl7.org/fhir/Media.html 280 | **/ 281 | asset Media extends PatientData 282 | { 283 | o String detail optional 284 | } 285 | /** 286 | * For more details please refer to https://www.hl7.org/fhir/MedicationDispense.html 287 | **/ 288 | asset MedicationDispense extends PatientData 289 | { 290 | o String detail optional 291 | } 292 | /** 293 | * For more details please refer to https://www.hl7.org/fhir/EpisodeOfCare.html 294 | **/ 295 | asset EpisodeOfCare extends PatientData 296 | { 297 | o String detail optional 298 | } 299 | /** 300 | * For more details please refer to https://www.hl7.org/fhir/Composition.html 301 | **/ 302 | asset Composition extends PatientData 303 | { 304 | o String detail optional 305 | } 306 | /** 307 | * For more details please refer to https://www.hl7.org/fhir/DeviceMetric.html 308 | **/ 309 | asset DeviceMetric extends PatientData 310 | { 311 | o String detail optional 312 | } 313 | /** 314 | * For more details please refer to https://www.hl7.org/fhir/Communication.html 315 | **/ 316 | asset Communication extends PatientData 317 | { 318 | o String detail optional 319 | } 320 | /** 321 | * For more details please refer to https://www.hl7.org/fhir/FamilyMemberHistory.html 322 | **/ 323 | asset FamilyMemberHistory extends PatientData 324 | { 325 | o String detail optional 326 | } 327 | /** 328 | * For more details please refer to https://www.hl7.org/fhir/AppointmentResponse.html 329 | **/ 330 | asset AppointmentResponse extends PatientData 331 | { 332 | o String detail optional 333 | } 334 | /** 335 | * For more details please refer to https://www.hl7.org/fhir/AdverseEvent.html 336 | **/ 337 | asset AdverseEvent extends PatientData 338 | { 339 | o String detail optional 340 | } 341 | /** 342 | * For more details please refer to https://www.hl7.org/fhir/Basic.html 343 | **/ 344 | asset Basic extends PatientData 345 | { 346 | o String detail optional 347 | } 348 | /** 349 | * For more details please refer to https://www.hl7.org/fhir/Coverage.html 350 | **/ 351 | asset Coverage extends PatientData 352 | { 353 | o String detail optional 354 | } 355 | /** 356 | * For more details please refer to https://www.hl7.org/fhir/Specimen.html 357 | **/ 358 | asset Specimen extends PatientData 359 | { 360 | o String detail optional 361 | } 362 | /** 363 | * For more details please refer to https://www.hl7.org/fhir/CommunicationRequest.html 364 | **/ 365 | asset CommunicationRequest extends PatientData 366 | { 367 | o String detail optional 368 | } 369 | /** 370 | * For more details please refer to https://www.hl7.org/fhir/Consent.html 371 | **/ 372 | asset Consent extends PatientData 373 | { 374 | o String detail optional 375 | } 376 | /** 377 | * For more details please refer to https://www.hl7.org/fhir/ReferralRequest.html 378 | **/ 379 | asset ReferralRequest extends PatientData 380 | { 381 | o String detail optional 382 | } 383 | /** 384 | * For more details please refer to https://www.hl7.org/fhir/Task.html 385 | **/ 386 | asset Task extends PatientData 387 | { 388 | o String detail optional 389 | } 390 | /** 391 | * For more details please refer to https://www.hl7.org/fhir/ActivityDefinition.html 392 | **/ 393 | asset ActivityDefinition extends PatientData 394 | { 395 | o String detail optional 396 | } 397 | /** 398 | * For more details please refer to https://www.hl7.org/fhir/ClinicalImpression.html 399 | **/ 400 | asset ClinicalImpression extends PatientData 401 | { 402 | o String detail optional 403 | } 404 | /** 405 | * For more details please refer to https://www.hl7.org/fhir/OperationDefinition.html 406 | **/ 407 | asset OperationDefinition extends PatientData 408 | { 409 | o String detail optional 410 | } 411 | /** 412 | * For more details please refer to https://www.hl7.org/fhir/Schedule.html 413 | **/ 414 | asset Schedule extends PatientData 415 | { 416 | o String detail optional 417 | } 418 | /** 419 | * For more details please refer to https://www.hl7.org/fhir/Subscription.html 420 | **/ 421 | asset Subscription extends PatientData 422 | { 423 | o String detail optional 424 | } 425 | /** 426 | * For more details please refer to https://www.hl7.org/fhir/DocumentManifest.html 427 | **/ 428 | asset DocumentManifest extends PatientData 429 | { 430 | o String detail optional 431 | } 432 | /** 433 | * For more details please refer to https://www.hl7.org/fhir/ImplementationGuide.html 434 | **/ 435 | asset ImplementationGuide extends PatientData 436 | { 437 | o String detail optional 438 | } 439 | /** 440 | * For more details please refer to https://www.hl7.org/fhir/RiskAssessment.html 441 | **/ 442 | asset RiskAssessment extends PatientData 443 | { 444 | o String detail optional 445 | } 446 | /** 447 | * For more details please refer to https://www.hl7.org/fhir/Substance.html 448 | **/ 449 | asset Substance extends PatientData 450 | { 451 | o String detail optional 452 | } 453 | /** 454 | * For more details please refer to https://www.hl7.org/fhir/ExplanationOfBenefit.html 455 | **/ 456 | asset ExplanationOfBenefit extends PatientData 457 | { 458 | o String detail optional 459 | } 460 | /** 461 | * For more details please refer to https://www.hl7.org/fhir/CapabilityStatement.html 462 | **/ 463 | asset CapabilityStatement extends PatientData 464 | { 465 | o String detail optional 466 | } 467 | /** 468 | * For more details please refer to https://www.hl7.org/fhir/Group.html 469 | **/ 470 | asset Group extends PatientData 471 | { 472 | o String detail optional 473 | } 474 | /** 475 | * For more details please refer to https://www.hl7.org/fhir/BodySite.html 476 | **/ 477 | asset BodySite extends PatientData 478 | { 479 | o String detail optional 480 | } 481 | /** 482 | * For more details please refer to https://www.hl7.org/fhir/ClaimResponse.html 483 | **/ 484 | asset ClaimResponse extends PatientData 485 | { 486 | o String detail optional 487 | } 488 | /** 489 | * For more details please refer to https://www.hl7.org/fhir/PlanDefinition.html 490 | **/ 491 | asset PlanDefinition extends PatientData 492 | { 493 | o String detail optional 494 | } 495 | /** 496 | * For more details please refer to https://www.hl7.org/fhir/Account.html 497 | **/ 498 | asset Account extends PatientData 499 | { 500 | o String detail optional 501 | } 502 | /** 503 | * For more details please refer to https://www.hl7.org/fhir/DataElement.html 504 | **/ 505 | asset DataElement extends PatientData 506 | { 507 | o String detail optional 508 | } 509 | /** 510 | * For more details please refer to https://www.hl7.org/fhir/VisionPrescription.html 511 | **/ 512 | asset VisionPrescription extends PatientData 513 | { 514 | o String detail optional 515 | } 516 | /** 517 | * For more details please refer to https://www.hl7.org/fhir/ChargeItem.html 518 | **/ 519 | asset ChargeItem extends PatientData 520 | { 521 | o String detail optional 522 | } 523 | /** 524 | * For more details please refer to https://www.hl7.org/fhir/DetectedIssue.html 525 | **/ 526 | asset DetectedIssue extends PatientData 527 | { 528 | o String detail optional 529 | } 530 | /** 531 | * For more details please refer to https://www.hl7.org/fhir/DeviceUseStatement.html 532 | **/ 533 | asset DeviceUseStatement extends PatientData 534 | { 535 | o String detail optional 536 | } 537 | /** 538 | * For more details please refer to https://www.hl7.org/fhir/Library.html 539 | **/ 540 | asset Library extends PatientData 541 | { 542 | o String detail optional 543 | } 544 | /** 545 | * For more details please refer to https://www.hl7.org/fhir/NutritionOrder.html 546 | **/ 547 | asset NutritionOrder extends PatientData 548 | { 549 | o String detail optional 550 | } 551 | /** 552 | * For more details please refer to https://www.hl7.org/fhir/ProcessRequest.html 553 | **/ 554 | asset ProcessRequest extends PatientData 555 | { 556 | o String detail optional 557 | } 558 | /** 559 | * For more details please refer to https://www.hl7.org/fhir/ProcessResponse.html 560 | **/ 561 | asset ProcessResponse extends PatientData 562 | { 563 | o String detail optional 564 | } 565 | /** 566 | * For more details please refer to https://www.hl7.org/fhir/RequestGroup.html 567 | **/ 568 | asset RequestGroup extends PatientData 569 | { 570 | o String detail optional 571 | } 572 | /** 573 | * For more details please refer to https://www.hl7.org/fhir/StructureMap.html 574 | **/ 575 | asset StructureMap extends PatientData 576 | { 577 | o String detail optional 578 | } 579 | /** 580 | * For more details please refer to https://www.hl7.org/fhir/CompartmentDefinition.html 581 | **/ 582 | asset CompartmentDefinition extends PatientData 583 | { 584 | o String detail optional 585 | } 586 | /** 587 | * For more details please refer to https://www.hl7.org/fhir/EligibilityRequest.html 588 | **/ 589 | asset EligibilityRequest extends PatientData 590 | { 591 | o String detail optional 592 | } 593 | /** 594 | * For more details please refer to https://www.hl7.org/fhir/ImmunizationRecommendation.html 595 | **/ 596 | asset ImmunizationRecommendation extends PatientData 597 | { 598 | o String detail optional 599 | } 600 | /** 601 | * For more details please refer to https://www.hl7.org/fhir/SupplyDelivery.html 602 | **/ 603 | asset SupplyDelivery extends PatientData 604 | { 605 | o String detail optional 606 | } 607 | /** 608 | * For more details please refer to https://www.hl7.org/fhir/DeviceRequest.html 609 | **/ 610 | asset DeviceRequest extends PatientData 611 | { 612 | o String detail optional 613 | } 614 | /** 615 | * For more details please refer to https://www.hl7.org/fhir/ImagingManifest.html 616 | **/ 617 | asset ImagingManifest extends PatientData 618 | { 619 | o String detail optional 620 | } 621 | /** 622 | * For more details please refer to https://www.hl7.org/fhir/SupplyRequest.html 623 | **/ 624 | asset SupplyRequest extends PatientData 625 | { 626 | o String detail optional 627 | } 628 | /** 629 | * For more details please refer to https://www.hl7.org/fhir/Contract.html 630 | **/ 631 | asset Contract extends PatientData 632 | { 633 | o String detail optional 634 | } 635 | /** 636 | * For more details please refer to https://www.hl7.org/fhir/ExpansionProfile.html 637 | **/ 638 | asset ExpansionProfile extends PatientData 639 | { 640 | o String detail optional 641 | } 642 | /** 643 | * For more details please refer to https://www.hl7.org/fhir/Measure.html 644 | **/ 645 | asset Measure extends PatientData 646 | { 647 | o String detail optional 648 | } 649 | /** 650 | * For more details please refer to https://www.hl7.org/fhir/EligibilityResponse.html 651 | **/ 652 | asset EligibilityResponse extends PatientData 653 | { 654 | o String detail optional 655 | } 656 | /** 657 | * For more details please refer to https://www.hl7.org/fhir/EnrollmentRequest.html 658 | **/ 659 | asset EnrollmentRequest extends PatientData 660 | { 661 | o String detail optional 662 | } 663 | /** 664 | * For more details please refer to https://www.hl7.org/fhir/EnrollmentResponse.html 665 | **/ 666 | asset EnrollmentResponse extends PatientData 667 | { 668 | o String detail optional 669 | } 670 | /** 671 | * For more details please refer to https://www.hl7.org/fhir/GraphDefinition.html 672 | **/ 673 | asset GraphDefinition extends PatientData 674 | { 675 | o String detail optional 676 | } 677 | /** 678 | * For more details please refer to https://www.hl7.org/fhir/GuidanceResponse.html 679 | **/ 680 | asset GuidanceResponse extends PatientData 681 | { 682 | o String detail optional 683 | } 684 | /** 685 | * For more details please refer to https://www.hl7.org/fhir/MeasureReport.html 686 | **/ 687 | asset MeasureReport extends PatientData 688 | { 689 | o String detail optional 690 | } 691 | /** 692 | * For more details please refer to https://www.hl7.org/fhir/MessageDefinition.html 693 | **/ 694 | asset MessageDefinition extends PatientData 695 | { 696 | o String detail optional 697 | } 698 | /** 699 | * For more details please refer to https://www.hl7.org/fhir/PaymentNotice.html 700 | **/ 701 | asset PaymentNotice extends PatientData 702 | { 703 | o String detail optional 704 | } 705 | /** 706 | * For more details please refer to https://www.hl7.org/fhir/PaymentReconciliation.html 707 | **/ 708 | asset PaymentReconciliation extends PatientData 709 | { 710 | o String detail optional 711 | } 712 | /** 713 | * For more details please refer to https://www.hl7.org/fhir/ServiceDefinition.html 714 | **/ 715 | asset ServiceDefinition extends PatientData 716 | { 717 | o String detail optional 718 | } 719 | /** 720 | * For more details please refer to https://www.hl7.org/fhir/OperationOutcome.html 721 | **/ 722 | asset OperationOutcome extends PatientData 723 | { 724 | o String detail optional 725 | } 726 | 727 | /** 728 | * Sharing with Practitioners 729 | */ 730 | transaction ShareWithPractitioner { 731 | o String assetType 732 | o String assetReference 733 | --> Practitioner practitioner 734 | } 735 | 736 | /** 737 | * Sharing with Orgs 738 | */ 739 | 740 | transaction ShareWithOrg { 741 | o String assetType 742 | o String assetReference 743 | --> Organization organization 744 | } 745 | 746 | /** 747 | * Events 748 | */ 749 | event AssetSharedwithPractitioner { 750 | o String assetType 751 | o String assetReference 752 | o String auditResourceUri optional 753 | o String resourceHash optional 754 | --> Practitioner practitioner 755 | } 756 | 757 | event AssetSharedWithOrg { 758 | o String assetType 759 | o String assetReference 760 | o String auditResourceUri optional 761 | o String resourceHash optional 762 | --> Organization organization 763 | } -------------------------------------------------------------------------------- /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 | * Write the unit tests for your transction processor functions here 18 | */ 19 | 20 | const AdminConnection = require('composer-admin').AdminConnection; 21 | const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection; 22 | const { BusinessNetworkDefinition, CertificateUtil, IdCard } = require('composer-common'); 23 | const path = require('path'); 24 | 25 | const chai = require('chai'); 26 | chai.should(); 27 | chai.use(require('chai-as-promised')); 28 | 29 | const namespace = 'org.cc.patientdatanetwork'; 30 | const assetType = 'Procedure'; 31 | const assetNS = namespace + '.' + assetType; 32 | const organizationType = 'Organization'; 33 | const patientType = 'Patient'; 34 | const practitionerType = 'Practitioner'; 35 | const patientNS = namespace + '.' + patientType; 36 | const practitionerNS = namespace + '.' + practitionerType; 37 | const organizationNS = namespace+ '.' + organizationType; 38 | 39 | describe('#' + namespace, () => { 40 | // In-memory card store for testing so cards are not persisted to the file system 41 | const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore( { type: 'composer-wallet-inmemory' } ); 42 | 43 | // Embedded connection used for local testing 44 | const connectionProfile = { 45 | name: 'embedded', 46 | 'x-type': 'embedded' 47 | }; 48 | 49 | // Name of the business network card containing the administrative identity for the business network 50 | const adminCardName = 'admin'; 51 | 52 | // Admin connection to the blockchain, used to deploy the business network 53 | let adminConnection; 54 | 55 | // This is the business network connection the tests will use. 56 | let businessNetworkConnection; 57 | 58 | // This is the factory for creating instances of types. 59 | let factory; 60 | 61 | // These are the identities for HopstailABC, Alice, Bob (Patients) and Zara (Doctor). 62 | const hospitalABCCardName = 'hospitalABC'; 63 | const aliceCardName = 'alice'; 64 | const bobCardName = 'bob'; 65 | const zaraCardName = 'zara'; 66 | 67 | // These are a list of receieved events. 68 | let events; 69 | 70 | let businessNetworkName; 71 | 72 | before(async () => { 73 | // Generate certificates for use with the embedded connection 74 | const credentials = CertificateUtil.generate({ commonName: 'admin' }); 75 | 76 | // Identity used with the admin connection to deploy business networks 77 | const deployerMetadata = { 78 | version: 1, 79 | userName: 'PeerAdmin', 80 | roles: [ 'PeerAdmin', 'ChannelAdmin' ] 81 | }; 82 | const deployerCard = new IdCard(deployerMetadata, connectionProfile); 83 | deployerCard.setCredentials(credentials); 84 | const deployerCardName = 'PeerAdmin'; 85 | 86 | adminConnection = new AdminConnection({ cardStore: cardStore }); 87 | 88 | await adminConnection.importCard(deployerCardName, deployerCard); 89 | await adminConnection.connect(deployerCardName); 90 | }); 91 | 92 | /** 93 | * 94 | * @param {String} cardName The card name to use for this identity 95 | * @param {Object} identity The identity details 96 | */ 97 | async function importCardForIdentity(cardName, identity) { 98 | const metadata = { 99 | userName: identity.userID, 100 | version: 1, 101 | enrollmentSecret: identity.userSecret, 102 | businessNetwork: businessNetworkName 103 | }; 104 | const card = new IdCard(metadata, connectionProfile); 105 | await adminConnection.importCard(cardName, card); 106 | } 107 | 108 | // This is called before each test is executed. 109 | beforeEach(async () => { 110 | // Generate a business network definition from the project directory. 111 | let businessNetworkDefinition = await BusinessNetworkDefinition.fromDirectory(path.resolve(__dirname, '..')); 112 | businessNetworkName = businessNetworkDefinition.getName(); 113 | await adminConnection.install(businessNetworkDefinition); 114 | const startOptions = { 115 | networkAdmins: [ 116 | { 117 | userName: 'admin', 118 | enrollmentSecret: 'adminpw' 119 | } 120 | ] 121 | }; 122 | const adminCards = await adminConnection.start(businessNetworkName, businessNetworkDefinition.getVersion(), startOptions); 123 | await adminConnection.importCard(adminCardName, adminCards.get('admin')); 124 | 125 | // Create and establish a business network connection 126 | businessNetworkConnection = new BusinessNetworkConnection({ cardStore: cardStore }); 127 | events = []; 128 | businessNetworkConnection.on('event', event => { 129 | events.push(event); 130 | }); 131 | await businessNetworkConnection.connect(adminCardName); 132 | 133 | // Get the factory for the business network. 134 | factory = businessNetworkConnection.getBusinessNetwork().getFactory(); 135 | 136 | const organizationRegistry = await businessNetworkConnection.getParticipantRegistry(organizationNS); 137 | // Create organization 138 | const hospitalABC = factory.newResource(namespace, organizationType, '1111'); 139 | hospitalABC.name = 'Hospital ABC'; 140 | 141 | organizationRegistry.addAll([hospitalABC]); 142 | 143 | const patientRegistry = await businessNetworkConnection.getParticipantRegistry(patientNS); 144 | // Create the patients. 145 | const alice = factory.newResource(namespace, patientType, 'alice@email.com'); 146 | alice.medicalRecordNumber = '1234'; 147 | alice.firstName = 'Alice'; 148 | alice.lastName = 'A'; 149 | alice.gender = 'F'; 150 | 151 | const bob = factory.newResource(namespace, patientType, 'bob@email.com'); 152 | bob.medicalRecordNumber = '2345'; 153 | bob.firstName = 'Bob'; 154 | bob.lastName = 'B'; 155 | bob.gender = 'M'; 156 | 157 | patientRegistry.addAll([alice, bob]); 158 | 159 | 160 | const practitionerRegistry = await businessNetworkConnection.getParticipantRegistry(practitionerNS); 161 | // create the practitioners 162 | const zara = factory.newResource(namespace, practitionerType, 'zara@email.com'); 163 | zara.workingAt = factory.newRelationship(namespace, organizationType, '#1111'); 164 | zara.firstName = 'Zara'; 165 | zara.lastName = 'Doctor'; 166 | zara.gender = 'f'; 167 | 168 | practitionerRegistry.addAll([zara]); 169 | 170 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 171 | // Create the assets. 172 | const asset1 = factory.newResource(namespace, assetType, '1'); 173 | asset1.owner = factory.newRelationship(namespace, patientType, 'alice@email.com'); 174 | asset1.procedureName = 'Operation'; 175 | 176 | const asset2 = factory.newResource(namespace, assetType, '2'); 177 | asset2.owner = factory.newRelationship(namespace, patientType, 'bob@email.com'); 178 | asset2.procedureName = 'Amputation'; 179 | 180 | assetRegistry.addAll([asset1, asset2]); 181 | 182 | // Issue the identities. 183 | let identity = await businessNetworkConnection.issueIdentity(patientNS + '#alice@email.com', 'alice1'); 184 | await importCardForIdentity(aliceCardName, identity); 185 | identity = await businessNetworkConnection.issueIdentity(patientNS + '#bob@email.com', 'bob1'); 186 | await importCardForIdentity(bobCardName, identity); 187 | identity = await businessNetworkConnection.issueIdentity(practitionerNS + '#zara@email.com', 'zara1'); 188 | await importCardForIdentity(zaraCardName, identity); 189 | identity = await businessNetworkConnection.issueIdentity(organizationNS + '#1111', 'hospital1'); 190 | await importCardForIdentity(hospitalABCCardName, identity); 191 | }); 192 | 193 | /** 194 | * Reconnect using a different identity. 195 | * @param {String} cardName The name of the card for the identity to use 196 | */ 197 | async function useIdentity(cardName) { 198 | await businessNetworkConnection.disconnect(); 199 | businessNetworkConnection = new BusinessNetworkConnection({ cardStore: cardStore }); 200 | events = []; 201 | businessNetworkConnection.on('event', (event) => { 202 | events.push(event); 203 | }); 204 | await businessNetworkConnection.connect(cardName); 205 | factory = businessNetworkConnection.getBusinessNetwork().getFactory(); 206 | } 207 | 208 | it('Alice can read all of her assets', async () => { 209 | // Use the identity for Alice. 210 | await useIdentity(aliceCardName); 211 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 212 | const assets = await assetRegistry.getAll(); 213 | 214 | // Validate the assets. 215 | assets.should.have.lengthOf(1); 216 | const asset1 = assets[0]; 217 | asset1.owner.getFullyQualifiedIdentifier().should.equal(patientNS + '#alice@email.com'); 218 | asset1.procedureName.should.equal('Operation'); 219 | }); 220 | 221 | it('Bob can read all of his assets', async () => { 222 | // Use the identity for Bob. 223 | await useIdentity(bobCardName); 224 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 225 | const assets = await assetRegistry.getAll(); 226 | 227 | // Validate the assets. 228 | assets.should.have.lengthOf(1); 229 | const asset2 = assets[0]; 230 | asset2.owner.getFullyQualifiedIdentifier().should.equal(patientNS + '#bob@email.com'); 231 | asset2.procedureName.should.equal('Amputation'); 232 | }); 233 | 234 | it('Alice can add assets that she owns', async () => { 235 | // Use the identity for Alice. 236 | await useIdentity(aliceCardName); 237 | 238 | // Create the asset. 239 | let asset3 = factory.newResource(namespace, assetType, '3'); 240 | asset3.owner = factory.newRelationship(namespace, patientType, 'alice@email.com'); 241 | asset3.procedureName = '30'; 242 | 243 | // Add the asset, then get the asset. 244 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 245 | await assetRegistry.add(asset3); 246 | 247 | // Validate the asset. 248 | asset3 = await assetRegistry.get('3'); 249 | asset3.owner.getFullyQualifiedIdentifier().should.equal(patientNS + '#alice@email.com'); 250 | asset3.procedureName.should.equal('30'); 251 | }); 252 | 253 | it('Alice cannot add assets that Bob owns', async () => { 254 | // Use the identity for Alice. 255 | await useIdentity(aliceCardName); 256 | 257 | // Create the asset. 258 | const asset3 = factory.newResource(namespace, assetType, '3'); 259 | asset3.owner = factory.newRelationship(namespace, patientType, 'bob@email.com'); 260 | asset3.procedureName = '30'; 261 | 262 | // Try to add the asset, should fail. 263 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 264 | assetRegistry.add(asset3).should.be.rejectedWith(/does not have .* access to resource/); 265 | }); 266 | 267 | it('Bob can add assets that he owns', async () => { 268 | // Use the identity for Bob. 269 | await useIdentity(bobCardName); 270 | 271 | // Create the asset. 272 | let asset4 = factory.newResource(namespace, assetType, '4'); 273 | asset4.owner = factory.newRelationship(namespace, patientType, 'bob@email.com'); 274 | asset4.procedureName = '40'; 275 | 276 | // Add the asset, then get the asset. 277 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 278 | await assetRegistry.add(asset4); 279 | 280 | // Validate the asset. 281 | asset4 = await assetRegistry.get('4'); 282 | asset4.owner.getFullyQualifiedIdentifier().should.equal(patientNS + '#bob@email.com'); 283 | asset4.procedureName.should.equal('40'); 284 | }); 285 | 286 | it('Bob cannot add assets that Alice owns', async () => { 287 | // Use the identity for Bob. 288 | await useIdentity(bobCardName); 289 | 290 | // Create the asset. 291 | const asset4 = factory.newResource(namespace, assetType, '4'); 292 | asset4.owner = factory.newRelationship(namespace, patientType, 'alice@email.com'); 293 | asset4.procedureName = '40'; 294 | 295 | // Try to add the asset, should fail. 296 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 297 | assetRegistry.add(asset4).should.be.rejectedWith(/does not have .* access to resource/); 298 | 299 | }); 300 | 301 | it('Alice can update her assets', async () => { 302 | // Use the identity for Alice. 303 | await useIdentity(aliceCardName); 304 | 305 | // Create the asset. 306 | let asset1 = factory.newResource(namespace, assetType, '1'); 307 | asset1.owner = factory.newRelationship(namespace, patientType, 'alice@email.com'); 308 | asset1.procedureName = '50'; 309 | 310 | // Update the asset, then get the asset. 311 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 312 | await assetRegistry.update(asset1); 313 | 314 | // Validate the asset. 315 | asset1 = await assetRegistry.get('1'); 316 | asset1.owner.getFullyQualifiedIdentifier().should.equal(patientNS + '#alice@email.com'); 317 | asset1.procedureName.should.equal('50'); 318 | }); 319 | 320 | it('Alice cannot update Bob\'s assets', async () => { 321 | // Use the identity for Alice. 322 | await useIdentity(aliceCardName); 323 | 324 | // Create the asset. 325 | const asset2 = factory.newResource(namespace, assetType, '2'); 326 | asset2.owner = factory.newRelationship(namespace, patientType, 'bob@email.com'); 327 | asset2.procedureName = '50'; 328 | 329 | // Try to update the asset, should fail. 330 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 331 | assetRegistry.update(asset2).should.be.rejectedWith(/does not have .* access to resource/); 332 | }); 333 | 334 | it('Bob can update his assets', async () => { 335 | // Use the identity for Bob. 336 | await useIdentity(bobCardName); 337 | 338 | // Create the asset. 339 | let asset2 = factory.newResource(namespace, assetType, '2'); 340 | asset2.owner = factory.newRelationship(namespace, patientType, 'bob@email.com'); 341 | asset2.procedureName = '60'; 342 | 343 | // Update the asset, then get the asset. 344 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 345 | await assetRegistry.update(asset2); 346 | 347 | // Validate the asset. 348 | asset2 = await assetRegistry.get('2'); 349 | asset2.owner.getFullyQualifiedIdentifier().should.equal(patientNS + '#bob@email.com'); 350 | asset2.procedureName.should.equal('60'); 351 | }); 352 | 353 | it('Bob cannot update Alice\'s assets', async () => { 354 | // Use the identity for Bob. 355 | await useIdentity(bobCardName); 356 | 357 | // Create the asset. 358 | const asset1 = factory.newResource(namespace, assetType, '1'); 359 | asset1.owner = factory.newRelationship(namespace, patientType, 'alice@email.com'); 360 | asset1.procedureName = '60'; 361 | 362 | // Update the asset, then get the asset. 363 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 364 | assetRegistry.update(asset1).should.be.rejectedWith(/does not have .* access to resource/); 365 | 366 | }); 367 | 368 | it('Alice can remove her assets', async () => { 369 | // Use the identity for Alice. 370 | await useIdentity(aliceCardName); 371 | 372 | // Remove the asset, then test the asset exists. 373 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 374 | await assetRegistry.remove('1'); 375 | const exists = await assetRegistry.exists('1'); 376 | exists.should.be.false; 377 | }); 378 | 379 | it('Alice cannot remove Bob\'s assets', async () => { 380 | // Use the identity for Alice. 381 | await useIdentity(aliceCardName); 382 | 383 | // Remove the asset, then test the asset exists. 384 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 385 | assetRegistry.remove('2').should.be.rejectedWith(/does not have .* access to resource/); 386 | }); 387 | 388 | it('Bob can remove his assets', async () => { 389 | // Use the identity for Bob. 390 | await useIdentity(bobCardName); 391 | 392 | // Remove the asset, then test the asset exists. 393 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 394 | await assetRegistry.remove('2'); 395 | const exists = await assetRegistry.exists('2'); 396 | exists.should.be.false; 397 | }); 398 | 399 | it('Bob cannot remove Alice\'s assets', async () => { 400 | // Use the identity for Bob. 401 | await useIdentity(bobCardName); 402 | 403 | // Remove the asset, then test the asset exists. 404 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 405 | assetRegistry.remove('1').should.be.rejectedWith(/does not have .* access to resource/); 406 | }); 407 | 408 | it('Alice can submit a transaction for her assets', async () => { 409 | // Use the identity for Alice. 410 | await useIdentity(aliceCardName); 411 | 412 | // Submit the transaction. 413 | const transaction = factory.newTransaction(namespace, 'ShareWithPractitioner'); 414 | transaction.assetType = namespace+'.'+assetType; 415 | transaction.assetReference = '1'; 416 | transaction.practitioner = factory.newRelationship(namespace, practitionerType, 'zara@email.com'); 417 | await businessNetworkConnection.submitTransaction(transaction); 418 | 419 | // Get the asset. 420 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 421 | const asset1 = await assetRegistry.get('1'); 422 | 423 | // Validate the asset. 424 | asset1.owner.getFullyQualifiedIdentifier().should.equal(patientNS + '#alice@email.com'); 425 | //asset1.procedureName.should.equal('Labotomy'); 426 | 427 | // Validate the events. 428 | events.should.have.lengthOf(1); 429 | const event = events[0]; 430 | event.eventId.should.be.a('string'); 431 | event.timestamp.should.be.an.instanceOf(Date); 432 | event.assetReference.should.equal('1'); 433 | }); 434 | 435 | it('Alice cannot submit a transaction for Bob\'s assets', async () => { 436 | // Use the identity for Alice. 437 | await useIdentity(aliceCardName); 438 | 439 | // Submit the transaction. 440 | const transaction = factory.newTransaction(namespace, 'ShareWithPractitioner'); 441 | transaction.assetType = namespace+'.'+assetType; 442 | transaction.assetReference = '2'; 443 | transaction.practitioner = factory.newRelationship(namespace, practitionerType, 'zara@email.com'); 444 | businessNetworkConnection.submitTransaction(transaction).should.be.rejectedWith(/does not have .* access to resource/); 445 | }); 446 | 447 | it('Bob can submit a transaction for his assets', async () => { 448 | // Use the identity for Bob. 449 | await useIdentity(bobCardName); 450 | 451 | // Submit the transaction. 452 | const transaction = factory.newTransaction(namespace, 'ShareWithPractitioner'); 453 | transaction.assetType = namespace+'.'+assetType; 454 | transaction.assetReference = '2'; 455 | transaction.practitioner = factory.newRelationship(namespace, practitionerType, 'zara@email.com'); 456 | await businessNetworkConnection.submitTransaction(transaction); 457 | 458 | // Get the asset. 459 | const assetRegistry = await businessNetworkConnection.getAssetRegistry(assetNS); 460 | const asset2 = await assetRegistry.get('2'); 461 | 462 | // Validate the asset. 463 | asset2.owner.getFullyQualifiedIdentifier().should.equal(patientNS + '#bob@email.com'); 464 | //asset2.procedureName.should.equal('Injection'); 465 | 466 | // Validate the events. 467 | events.should.have.lengthOf(1); 468 | const event = events[0]; 469 | event.eventId.should.be.a('string'); 470 | event.timestamp.should.be.an.instanceOf(Date); 471 | event.assetReference.should.equal('2'); 472 | }); 473 | 474 | it('Bob cannot submit a transaction for Alice\'s assets', async () => { 475 | // Use the identity for Bob. 476 | await useIdentity(bobCardName); 477 | 478 | // Submit the transaction. 479 | const transaction = factory.newTransaction(namespace, 'ShareWithPractitioner'); 480 | transaction.assetType = namespace+'.'+assetType; 481 | transaction.assetReference = '1'; 482 | transaction.practitioner = factory.newRelationship(namespace, practitionerType, 'zara@email.com'); 483 | businessNetworkConnection.submitTransaction(transaction).should.be.rejectedWith(/does not have .* access to resource/); 484 | }); 485 | 486 | }); 487 | --------------------------------------------------------------------------------