Share Encrypted Data
Sharing a piece of encrypted information means allowing another user to read it by encrypting the cryptographic key associated with the data using the recipient's key.
You can only share encrypted data with Data Owners, such as Healthcare Parties, Patients, or Devices. Additionally, the recipient Data Owner must have initialized their cryptographic keys. To initialize the keys, a Data Owner must log in to the SDK. Therefore, the Data Owner must be associated with a valid user to successfully complete the login and participate in a data-sharing procedure.
Share Data with a Healthcare Party​
Share an Existing Entity with a Healthcare Party​
For this example, you need another Healthcare Party user. You can create one in the Cockpit by following this guide.
Once you have created the new user, log in to initialize their cryptographic keys:
- Kotlin
- Python
- Typescript
print("Login of the other HCP: ")
val username = readln().trim()
print("Insert the password for this HCP: ")
val otherPassword = readln()
val otherSdk = CardinalSdk.initialize(
applicationId = null,
baseUrl = CARDINAL_URL,
authenticationMethod = AuthenticationMethod.UsingCredentials(
UsernamePassword(username, otherPassword)
),
baseStorage = FileStorageFacade("./scratch/storage")
)
val otherHcp = otherSdk.healthcareParty.getCurrentHealthcareParty()
username = input("Login of the other hcp: ").strip()
other_password = input("Insert the password for this hcp: ")
other_sdk = CardinalSdk(
application_id=None,
baseurl=CARDINAL_URL,
authentication_method=UsernamePassword(username, other_password),
storage_facade=FileSystemStorage("./scratch/storage")
)
other_hcp = other_sdk.healthcare_party.get_current_healthcare_party_blocking()
const username = (await readLn("Login of the other hcp: ")).trim()
const otherPassword = await readLn("Insert the password for this hcp: ")
const otherSdk = await CardinalSdk.initialize(
undefined,
CARDINAL_URL,
new AuthenticationMethod.UsingCredentials.UsernamePassword(username, otherPassword),
StorageFacade.usingFileSystem("../scratch/storage")
)
const otherHcp = await otherSdk.healthcareParty.getCurrentHealthcareParty()
Consider a Document
entity created with the initial Healthcare Party user:
- Kotlin
- Python
- Typescript
val oldDocument = sdk.document.createDocument(
DecryptedDocument(
id = UUID.randomUUID().toString(),
name = "An important document"
).let {
sdk.document.withEncryptionMetadata(it, null)
}
)
old_document_without_encryption_meta = DecryptedDocument(
id=str(uuid.uuid4()),
name="An important document"
)
old_document = sdk.document.create_document_blocking(
sdk.document.with_encryption_metadata_blocking(old_document_without_encryption_meta, None)
)
const oldDocument = await sdk.document.createDocument(
await sdk.document.withEncryptionMetadata(new DecryptedDocument({
id: uuid(),
name: "An important document"
}),
null
)
)
If the other Healthcare Party tries to access it using the ID, the operation will fail with an error:
- Kotlin
- Python
- Typescript
try {
otherSdk.document.getDocument(oldDocument.id)
} catch (e: Exception) {
println("This means I am not authorized to read the document -> \${e.message}")
}
try:
other_sdk.document.get_document_blocking(old_document.id)
except Exception as e:
print(f"This means I am not authorized to read the document -> {e}")
try {
await otherSdk.document.getDocument(oldDocument.id)
} catch (e) {
console.error("This means I am not authorized to read the document -> ", e)
}
The initial Healthcare Party can then grant access using the shareWith
method. This method takes two parameters:
the ID of the recipient Data Owner (i.e., the Healthcare Party, Patient, or Device) and the entity to share:
- Kotlin
- Python
- Typescript
val updatedDocument = sdk.document.shareWith(
delegateId = otherHcp.id,
document = oldDocument
)
updated_document = sdk.document.share_with_blocking(
delegate_id=other_hcp.id,
document=old_document
)
updatedDocument = await sdk.document.shareWith(
otherHcp.id,
oldDocument
)
At this point, the other Healthcare Party can access the document successfully:
- Kotlin
- Python
- Typescript
val oldDocumentOtherHcp = otherSdk.document.getDocument(oldDocument.id)
old_document_other_hcp = other_sdk.document.get_document_blocking(old_document.id)
const oldDocumentOtherHcp = await otherSdk.document.getDocument(oldDocument.id)
Share a New Entity with a Healthcare Party​
When creating an entity, you can directly specify the other Data Owner to share the entity with by including them when initializing the encryption metadata:
- Kotlin
- Python
- Typescript
val newDocument = DecryptedDocument(
id = UUID.randomUUID().toString(),
name = "Another important document"
)
val newDocumentWithMetadata = sdk.document.withEncryptionMetadata(
newDocument,
null,
delegates = mapOf(otherHcp.id to AccessLevel.Read)
)
val createdNewDocument = sdk.document.createDocument(newDocumentWithMetadata)
new_document = DecryptedDocument(
id=str(uuid.uuid4()),
name="Another important document"
)
new_document_with_metadata = sdk.document.with_encryption_metadata_blocking(
new_document,
None,
delegates={other_hcp.id: AccessLevel.Read}
)
created_new_document = sdk.document.create_document_blocking(new_document_with_metadata)
const newDocument = new DecryptedDocument({
id: uuid(),
name: "Another important document"
})
const newDocumentWithMetadata = await sdk.document.withEncryptionMetadata(
newDocument,
null,
{ delegates: { [otherHcp.id]: AccessLevel.Read } }
)
const createdNewDocument = await sdk.document.createDocument(newDocumentWithMetadata)
The other Healthcare Party is a delegate for the new Document
with Read permissions. This means they can access the
entity and read the encrypted information but cannot modify it:
- Kotlin
- Python
- Typescript
val newDocumentOtherHcp = otherSdk.document.getDocument(createdNewDocument.id)
new_document_other_hcp = other_sdk.document.get_document_blocking(created_new_document.id)
const newDocumentOtherHcp = await otherSdk.document.getDocument(createdNewDocument.id)
Share Data with a Patient​
The flow to share data with a Patient user is the same as to share data with a Healthcare Party user. However, it is not possible to initialize a Patient User using the Cockpit.
To create a Patient user, you first need to create a Patient:
- Kotlin
- Python
- Typescript
val newPatient = DecryptedPatient(
id = UUID.randomUUID().toString(),
firstName = "Edmond",
lastName = "Dantes",
)
val patientWithMetadata = sdk.patient.withEncryptionMetadata(newPatient)
val createdPatient = sdk.patient.createPatient(patientWithMetadata)
new_patient = DecryptedPatient(
id=str(uuid.uuid4()),
first_name="Edmond",
last_name="Dantes",
)
patient_with_metadata = sdk.patient.with_encryption_metadata_blocking(new_patient)
created_patient = sdk.patient.create_patient_blocking(patient_with_metadata)
const newPatient = new DecryptedPatient({
id: uuid(),
firstName: "Edmond",
lastName: "Dantes",
})
const patientWithMetadata = await sdk.patient.withEncryptionMetadata(newPatient)
const createdPatient = await sdk.patient.createPatient(patientWithMetadata)
Then, you need to create a User for that Patient. You can link the User to the Patient by setting the patientId
property on the User to the id of the newly created Patient.
- Kotlin
- Python
- Typescript
val login = "edmond.dantes.${UUID.randomUUID().toString().substring(0, 6)}@icure.com"
val patientUser = User(
id = UUID.randomUUID().toString(),
patientId = createdPatient.id,
login = login,
email = login
)
val createdUser = sdk.user.createUser(patientUser)
login = f"edmond.dantes.{str(uuid.uuid4())[0:6]}@icure.com"
patient_user = User(
id=str(uuid.uuid4()),
patient_id=created_patient.id,
login=login,
email=login
)
created_user = sdk.user.create_user_blocking(patient_user)
const login = `edmond.dantes.${uuid().substring(0, 6)}@icure.com`
const patientUser = new User({
id: uuid(),
patientId: createdPatient.id,
login: login,
email: login
})
const createdUser = await sdk.user.createUser(patientUser)
Finally, you have to create a temporary access token for the User, so that they can log in.
- Kotlin
- Python
- Typescript
val loginToken = sdk.user.getToken(createdUser.id, "login")
login_token = sdk.user.get_token_blocking(created_user.id, "login")
const loginToken = await sdk.user.getToken(createdUser.id, "login")
Now, the User can log in to the SDK, initializing their cryptographic keys:
- Kotlin
- Python
- Typescript
CardinalSdk.initialize(
applicationId = null,
baseUrl = CARDINAL_URL,
authenticationMethod = AuthenticationMethod.UsingCredentials(
UsernamePassword(login, loginToken)
),
baseStorage = FileStorageFacade("./scratch/storage")
)
CardinalSdk(
application_id=None,
baseurl=CARDINAL_URL,
authentication_method=UsernamePassword(login, login_token),
storage_facade=FileSystemStorage("./scratch/storage")
)
await CardinalSdk.initialize(
undefined,
CARDINAL_URL,
new AuthenticationMethod.UsingCredentials.UsernamePassword(login, loginToken),
StorageFacade.usingFileSystem("../scratch/storage")
)
However, the Patient User cannot access itself, as the Patient entity could not be shared with them as the cryptographic keys were not initialized yet. Now that they are, the Healthcare Party that is managing this registration can share the Patient:
- Kotlin
- Python
- Typescript
val patientSecretIds = sdk.patient.getSecretIdsOf(createdPatient)
val patient = sdk.patient.shareWith(
delegateId = createdPatient.id,
patient = createdPatient,
options = PatientShareOptions(
shareSecretIds = patientSecretIds,
shareEncryptionKey = ShareMetadataBehaviour.IfAvailable,
requestedPermissions = RequestedPermission.MaxWrite
)
)
patient_secret_ids = sdk.patient.get_secret_ids_of_blocking(created_patient)
patient = sdk.patient.share_with_blocking(
delegate_id=created_patient.id,
patient=created_patient,
options=PatientShareOptions(
share_secret_ids=patient_secret_ids,
share_encryption_key=ShareMetadataBehaviour.IfAvailable,
requested_permissions=RequestedPermission.MaxWrite
)
)
const patientSecretIds = await sdk.patient.getSecretIdsOf(createdPatient)
const patient = await sdk.patient.shareWith(
createdPatient.id,
createdPatient,
new PatientShareOptions({
shareSecretIds: patientSecretIds,
shareEncryptionKey: ShareMetadataBehaviour.IfAvailable,
requestedPermissions: RequestedPermission.MaxWrite
})
)
A user that acts as a patient must be able to access their own patient entity to create, read, and share data through Cardinal.
Now, the Patient can finally log in and have access to their full information:
- Kotlin
- Python
- Typescript
val patientSdk = CardinalSdk.initialize(
applicationId = null,
baseUrl = CARDINAL_URL,
authenticationMethod = AuthenticationMethod.UsingCredentials(
UsernamePassword(login, loginToken)
),
baseStorage = FileStorageFacade("./scratch/storage")
)
patient_sdk = CardinalSdk(
application_id=None,
baseurl=CARDINAL_URL,
authentication_method=UsernamePassword(login, login_token),
storage_facade=FileSystemStorage("./scratch/storage")
)
const patientSdk = await CardinalSdk.initialize(
undefined,
CARDINAL_URL,
new AuthenticationMethod.UsingCredentials.UsernamePassword(login, loginToken),
StorageFacade.usingFileSystem("../scratch/storage")
)
This registration flow makes sense only in the context of this example, to make it self-contained. To learn how to register a Patient in a real context, check this how to.
Share an Existing Entity with a Patient​
Sharing an entity with a Patient follows the same flow as sharing with a Healthcare Party. First, the Healthcare Party
needs to create an entity, such as a HealthElement
, to represent a medical condition or prolonged context:
- Kotlin
- Python
- Typescript
val healthElement = DecryptedHealthElement(
id = UUID.randomUUID().toString(),
descr = "This is some medical context"
)
val healthElementWithMetadata = sdk.healthElement.withEncryptionMetadata(healthElement, patient)
val createdHealthElement = sdk.healthElement.createHealthElement(healthElementWithMetadata)
health_element = DecryptedHealthElement(
id=str(uuid.uuid4()),
descr="This is some medical context"
)
health_element_with_metadata = sdk.health_element.with_encryption_metadata_blocking(health_element, patient)
created_health_element = sdk.health_element.create_health_element_blocking(health_element_with_metadata)
const healthElement = new DecryptedHealthElement({
id: uuid(),
descr: "This is some medical context"
})
const healthElementWithMetadata = await sdk.healthElement.withEncryptionMetadata(healthElement, patient)
const createdHealthElement = await sdk.healthElement.createHealthElement(healthElementWithMetadata)
It is important to note that even though the HealthElement
is linked to the patient by the encryption metadata,
the Patient does not yet have the right to access it:
- Kotlin
- Python
- Typescript
try {
patientSdk.healthElement.getHealthElement(createdHealthElement.id)
} catch (e: Exception) {
println("This means the patient cannot access this health element -> \${e.message}")
}
try:
patient_sdk.health_element.get_health_element_blocking(created_health_element.id)
except Exception as e:
print(f"This means the patient cannot get this health element -> {e}")
try {
await patientSdk.healthElement.getHealthElement(createdHealthElement.id)
} catch (e) {
console.error("This means the patient cannot get this health element", e)
}
Next, the Healthcare Party can share the entity with the Patient:
- Kotlin
- Python
- Typescript
val healthElement = sdk.healthElement.shareWith(
delegateId = patient.id,
healthElement = createdHealthElement
)
health_element = sdk.health_element.share_with_blocking(
delegate_id=patient.id,
health_element=created_health_element
)
const healthElement = await sdk.healthElement.shareWith(
patient.id,
createdHealthElement
)
Finally, the Patient can access it:
- Kotlin
- Python
- Typescript
patientSdk.healthElement.getHealthElement(createdHealthElement.id)
patient_sdk.health_element.get_health_element_blocking(created_health_element.id)
await patientSdk.healthElement.getHealthElement(createdHealthElement.id)
Share a New Entity with a Patient​
As with the Healthcare Party case, a Patient can be directly included in the delegations of the encryption metadata for a newly created entity.
- Kotlin
- Python
- Typescript
val newHealthElement = DecryptedHealthElement(
id = UUID.randomUUID().toString(),
descr = "This is some other medical context"
)
val newHealthElementWithMetadata = sdk.healthElement.withEncryptionMetadata(
newHealthElement,
patient,
delegates = mapOf(patient.id to AccessLevel.Write)
)
val newCreatedHealthElement = sdk.healthElement.createHealthElement(newHealthElementWithMetadata)
new_health_element = DecryptedHealthElement(
id=str(uuid.uuid4()),
descr="This is some other medical context"
)
new_health_element_with_metadata = sdk.health_element.with_encryption_metadata_blocking(
new_health_element,
patient,
delegates={patient.id: AccessLevel.Write}
)
new_created_health_element = sdk.health_element.create_health_element_blocking(new_health_element_with_metadata)
const newHealthElement = new DecryptedHealthElement({
id: uuid(),
descr: "This is some other medical context"
})
const newHealthElementWithMetadata = await sdk.healthElement.withEncryptionMetadata(
newHealthElement,
patient,
{ delegates: { [patient.id]: AccessLevel.Write } }
)
const newCreatedHealthElement = await sdk.healthElement.createHealthElement(newHealthElementWithMetadata)
It is important to note that the Patient linked to an entity is entirely separate from a patient with a delegation for the entity: setting up a delegation does not create a link between the patient and the entity, and creating the link does not set up a delegation.
Now, the Patient has read and write access to the entity and can directly retrieve it:
- Kotlin
- Python
- Typescript
val retrievedHealthElement = patientSdk.healthElement.getHealthElement(newCreatedHealthElement.id)
retrieved_health_element = patient_sdk.health_element.get_health_element_blocking(new_created_health_element.id)
const retrievedHealthElement = await patientSdk.healthElement.getHealthElement(newCreatedHealthElement.id)