Skip to main content

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.

note

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:

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()

Consider a Document entity created with the initial Healthcare Party user:

val oldDocument = sdk.document.createDocument(
DecryptedDocument(
id = UUID.randomUUID().toString(),
name = "An important document"
).let {
sdk.document.withEncryptionMetadata(it, null)
}
)

If the other Healthcare Party tries to access it using the ID, the operation will fail with an error:

try {
otherSdk.document.getDocument(oldDocument.id)
} catch (e: Exception) {
println("This means I am not authorized to read the document -> \${e.message}")
}

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:

val updatedDocument = sdk.document.shareWith(
delegateId = otherHcp.id,
document = oldDocument
)

At this point, the other Healthcare Party can access the document successfully:

val oldDocumentOtherHcp = 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:

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)

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:

val newDocumentOtherHcp = 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:

val newPatient = DecryptedPatient(
id = UUID.randomUUID().toString(),
firstName = "Edmond",
lastName = "Dantes",
)
val patientWithMetadata = sdk.patient.withEncryptionMetadata(newPatient)
val createdPatient = 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.

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)

Finally, you have to create a temporary access token for the User, so that they can log in.

val loginToken = sdk.user.getToken(createdUser.id, "login")

Now, the User can log in to the SDK, initializing their cryptographic keys:

CardinalSdk.initialize(
applicationId = null,
baseUrl = CARDINAL_URL,
authenticationMethod = AuthenticationMethod.UsingCredentials(
UsernamePassword(login, loginToken)
),
baseStorage = FileStorageFacade("./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:

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
)
)
note

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:

val patientSdk = CardinalSdk.initialize(
applicationId = null,
baseUrl = CARDINAL_URL,
authenticationMethod = AuthenticationMethod.UsingCredentials(
UsernamePassword(login, loginToken)
),
baseStorage = FileStorageFacade("./scratch/storage")
)
warning

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:

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)

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:

try {
patientSdk.healthElement.getHealthElement(createdHealthElement.id)
} catch (e: Exception) {
println("This means the patient cannot access this health element -> \${e.message}")
}

Next, the Healthcare Party can share the entity with the Patient:

val healthElement = sdk.healthElement.shareWith(
delegateId = patient.id,
healthElement = createdHealthElement
)

Finally, the Patient can access it:

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.

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)

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:

val retrievedHealthElement = patientSdk.healthElement.getHealthElement(newCreatedHealthElement.id)