///|
pub(all) enum Sensitivity {
Public
Internal
Sensitive
Restricted
} derive(Debug, Eq, ToJson)
///|
pub(all) struct PrivacyPolicy {
name : String
sensitive_fields : Array[String]
restricted_fields : Array[String]
preserve_resource_type : Bool
preserve_id : Bool
} derive(Debug, Eq, ToJson)
///|
pub(all) struct PrivacyAudit {
resource_type : String
found_sensitive_fields : Array[String]
removed_fields : Array[String]
retained_fields : Array[String]
} derive(Debug, Eq, ToJson)
///|
pub fn PrivacyPolicy::standard() -> PrivacyPolicy {
{
name: "standard-clinical",
sensitive_fields: ["telecom", "address", "photo", "identifier", "contact"],
restricted_fields: [
"communication", "deceasedDateTime", "multipleBirthInteger",
],
preserve_resource_type: true,
preserve_id: false,
}
}
///|
pub fn PrivacyPolicy::strict() -> PrivacyPolicy {
{
name: "strict-clinical",
sensitive_fields: [
"telecom", "address", "photo", "identifier", "contact", "name",
],
restricted_fields: [
"communication", "deceasedDateTime", "multipleBirthInteger", "text", "div",
],
preserve_resource_type: true,
preserve_id: false,
}
}
///|
pub fn PrivacyPolicy::name(self : PrivacyPolicy) -> String {
self.name
}
///|
pub fn PrivacyPolicy::field_count(self : PrivacyPolicy) -> Int {
self.sensitive_fields.length() + self.restricted_fields.length()
}
///|
pub fn classify_field(policy : PrivacyPolicy, field : String) -> Sensitivity {
if policy.restricted_fields.contains(field) {
Restricted
} else if policy.sensitive_fields.contains(field) {
Sensitive
} else if field == "resourceType" || field == "id" {
Internal
} else {
Public
}
}
///|
pub fn policy_allows(policy : PrivacyPolicy, field : String) -> Bool {
match classify_field(policy, field) {
Public | Internal => true
_ => false
}
}
///|
pub fn sensitive_fields_in_resource(
value : Json,
policy : PrivacyPolicy,
) -> Array[String] {
let output : Array[String] = []
for field in json_object_keys(value) {
match classify_field(policy, field) {
Sensitive | Restricted => output.push(field)
_ => ()
}
}
output
}
///|
pub fn resource_contains_sensitive_data(
value : Json,
policy : PrivacyPolicy,
) -> Bool {
sensitive_fields_in_resource(value, policy).length() > 0
}
///|
pub fn redact_by_policy(value : Json, policy : PrivacyPolicy) -> Json {
let fields = policy.sensitive_fields.add(policy.restricted_fields)
let output = redact_fields(value, fields)
if policy.preserve_resource_type {
match resource_type(value) {
None => output
Some(kind) =>
match output {
Object(values) => {
values["resourceType"] = Json::string(kind)
output
}
_ => output
}
}
} else {
output
}
}
///|
pub fn privacy_audit(value : Json, policy : PrivacyPolicy) -> PrivacyAudit {
let found = sensitive_fields_in_resource(value, policy)
let retained : Array[String] = []
for field in json_object_keys(value) {
if !found.contains(field) &&
(policy.preserve_resource_type || field != "resourceType") {
if policy.preserve_id || field != "id" {
retained.push(field)
}
}
}
{
resource_type: resource_type(value).unwrap_or(""),
found_sensitive_fields: found,
removed_fields: found,
retained_fields: retained,
}
}
///|
pub fn mask_identifier(value : String, visible? : Int = 2) -> String {
if value.length() <= visible {
"*".repeat(value.length())
} else {
let prefix = value[0:visible].to_owned()
prefix + "*".repeat(value.length() - visible)
}
}
///|
pub fn pseudonymize_identifier(
value : String,
scope? : String = "moonbit-fhir",
) -> String {
let hash = String::hash(scope + ":" + value)
"anon-\{if hash < 0 { -hash } else { hash }}"
}
///|
pub fn pseudonymize_resource_id(value : Json) -> Json {
match field_string(value, "id") {
None => value
Some(id) =>
match value {
Object(fields) => {
fields["id"] = Json::string(pseudonymize_identifier(id))
value
}
_ => value
}
}
}
///|
pub fn privacy_summary(value : Json, policy : PrivacyPolicy) -> String {
let audit = privacy_audit(value, policy)
"\{audit.resource_type}: remove=\{audit.removed_fields.length()} retain=\{audit.retained_fields.length()}"
}