├── 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 |
--------------------------------------------------------------------------------