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
- Dart
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()
final username = getUsernameFromUI();
final otherPassword = getPasswordFromTheUI();
final otherSdk = await CardinalSdk.initialize(
null,
cardinalUrl,
AuthenticationMethod.UsingCredentials(Credentials.UsernamePassword(username, otherPassword)),
StorageOptions.PlatformDefault
);
final otherHcp = await otherSdk.healthcareParty.getCurrentHealthcareParty();
Consider a Document
entity created with the initial Healthcare Party user:
- Kotlin
- Python
- Typescript
- Dart
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
)
)
final oldDocument = await sdk.document.createDocument(
await sdk.document.withEncryptionMetadata(
DecryptedDocument(
generateUuid(),
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
- Dart
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)
}
try {
await otherSdk.document.getDocument(oldDocument.id);
} on Exception catch (e) {
print("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
- Dart
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
)
final updatedDocument = await sdk.document.shareWith(otherHcp.id, oldDocument);
At this point, the other Healthcare Party can access the document successfully:
- Kotlin
- Python
- Typescript
- Dart
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)
final 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
- Dart
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)
final newDocument = DecryptedDocument(
generateUuid(),
name: "Another important document"
);
final newDocumentWithMetadata = await sdk.document.withEncryptionMetadata(
newDocument,
null,
delegates: { otherHcp.id: AccessLevel.read }
);
final 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
- Dart
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)
final 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
- Dart
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)
final newPatient = DecryptedPatient(
generateUuid(),
firstName: "Edmond",
lastName: "Dantes",
);
final patientWithMetadata = await sdk.patient.withEncryptionMetadata(newPatient);
final 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
- Dart
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)
final login = "edmond.dantes.${generateUuid().substring(0, 6)}@icure.com";
final patientUser = User(
generateUuid(),
patientId: createdPatient.id,
login: login,
email: login
);
final 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
- Dart
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")
final loginToken = await sdk.user.getToken(createdUser.id, "login");
Now, the User can log in to the SDK, initializing their cryptographic keys:
- Kotlin
- Python
- Typescript
- Dart
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")
)
await CardinalSdk.initialize(
null,
cardinalUrl,
AuthenticationMethod.UsingCredentials(Credentials.UsernamePassword(login, loginToken)),
StorageOptions.PlatformDefault
);
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
- Dart
val patient = sdk.patient.shareWith(
delegateId = createdPatient.id,
patient = createdPatient,
options = PatientShareOptions(
shareSecretIds = SecretIdShareOptions.AllAvailable(true),
shareEncryptionKey = ShareMetadataBehaviour.IfAvailable,
requestedPermissions = RequestedPermission.MaxWrite
)
)
patient = sdk.patient.share_with_blocking(
delegate_id=created_patient.id,
patient=created_patient,
options=PatientShareOptions(
share_secret_ids=SecretIdShareOptionsAllAvailable(True),
share_encryption_key=ShareMetadataBehaviour.IfAvailable,
requested_permissions=RequestedPermission.MaxWrite
)
)
const patient = await sdk.patient.shareWith(
createdPatient.id,
createdPatient,
{
options: new PatientShareOptions({
shareSecretIds: new SecretIdShareOptions.AllAvailable({requireAtLeastOne: true}),
shareEncryptionKey: ShareMetadataBehaviour.IfAvailable,
requestedPermissions: RequestedPermission.MaxWrite
})
}
)
const patient = await sdk.patient.shareWith(
createdPatient.id,
createdPatient,
{
options: new PatientShareOptions({
shareSecretIds: new SecretIdShareOptions.AllAvailable({requireAtLeastOne: true}),
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
- Dart
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")
)
const patientSdk = await CardinalSdk.initialize(
null,
cardinalUrl,
AuthenticationMethod.UsingCredentials(Credentials.UsernamePassword(login, loginToken)),
StorageOptions.PlatformDefault
);
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
- Dart
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)
final healthElement = DecryptedHealthElement(
generateUuid(),
descr: "This is some medical context"
);
final healthElementWithMetadata = await sdk.healthElement.withEncryptionMetadata(healthElement, patient);
final 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
- Dart
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)
}
try {
await patientSdk.healthElement.getHealthElement(createdHealthElement.id);
} catch (e) {
print("This means the patient cannot get this health element -> $e");
}
Next, the Healthcare Party can share the entity with the Patient:
- Kotlin
- Python
- Typescript
- Dart
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
)
final healthElement = await sdk.healthElement.shareWith(patient.id, createdHealthElement);
Finally, the Patient can access it:
- Kotlin
- Python
- Typescript
- Dart
patientSdk.healthElement.getHealthElement(createdHealthElement.id)
patient_sdk.health_element.get_health_element_blocking(created_health_element.id)
await patientSdk.healthElement.getHealthElement(createdHealthElement.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
- Dart
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)
final newHealthElement = DecryptedHealthElement(
generateUuid(),
descr: "This is some other medical context"
);
final newHealthElementWithMetadata = await sdk.healthElement.withEncryptionMetadata(
newHealthElement,
patient,
delegates: { patient.id: AccessLevel.write }
);
final 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
- Dart
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)
final retrievedHealthElement = await patientSdk.healthElement.getHealthElement(newCreatedHealthElement.id)