|
@@ -211,6 +211,60 @@ void EncryptionManager::decryptSensitiveFields(Document& doc) {
|
|
|
decryptJsonField(doc.data, "", doc.collection, doc.id);
|
|
decryptJsonField(doc.data, "", doc.collection, doc.id);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+void EncryptionManager::preserveUnchangedEncryption(Document& newDoc, const Document& existingDoc) {
|
|
|
|
|
+ if (!isEnabled() || !existingDoc.encrypted) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::lock_guard lock(mutex_);
|
|
|
|
|
+ preserveUnchangedJsonField(newDoc.data, existingDoc.data, "");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void EncryptionManager::preserveUnchangedJsonField(nlohmann::json& newObj,
|
|
|
|
|
+ const nlohmann::json& existingObj,
|
|
|
|
|
+ const std::string& path) {
|
|
|
|
|
+ if (!newObj.is_object() || !existingObj.is_object()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (auto& [key, newValue] : newObj.items()) {
|
|
|
|
|
+ if (!existingObj.contains(key)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string fieldPath = path.empty() ? key : path + "." + key;
|
|
|
|
|
+ const auto& existingValue = existingObj[key];
|
|
|
|
|
+
|
|
|
|
|
+ if (newValue.is_string() && existingValue.is_string() && isSensitiveField(fieldPath)) {
|
|
|
|
|
+ const std::string& newPlaintext = newValue.get_ref<const std::string&>();
|
|
|
|
|
+ const std::string& existingEncrypted = existingValue.get_ref<const std::string&>();
|
|
|
|
|
+
|
|
|
|
|
+ // Only compare if existing is encrypted and new is plaintext
|
|
|
|
|
+ if (existingEncrypted.find(ENCRYPTED_PREFIX) == 0 &&
|
|
|
|
|
+ newPlaintext.find(ENCRYPTED_PREFIX) != 0) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ std::string existingDecrypted = decryptValue(existingEncrypted);
|
|
|
|
|
+ if (existingDecrypted == newPlaintext) {
|
|
|
|
|
+ // Same value — preserve existing encrypted form to avoid false history diff
|
|
|
|
|
+ newValue = existingEncrypted;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (...) {
|
|
|
|
|
+ // Decryption failed — let encryptSensitiveFields handle it normally
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (newValue.is_object() && existingValue.is_object()) {
|
|
|
|
|
+ preserveUnchangedJsonField(newValue, existingValue, fieldPath);
|
|
|
|
|
+ } else if (newValue.is_array() && existingValue.is_array()) {
|
|
|
|
|
+ // For arrays, recurse into matching indices
|
|
|
|
|
+ size_t minSize = std::min(newValue.size(), existingValue.size());
|
|
|
|
|
+ for (size_t i = 0; i < minSize; ++i) {
|
|
|
|
|
+ preserveUnchangedJsonField(newValue[i], existingValue[i],
|
|
|
|
|
+ fieldPath + "[" + std::to_string(i) + "]");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
std::string EncryptionManager::encryptValue(const std::string& plaintext) {
|
|
std::string EncryptionManager::encryptValue(const std::string& plaintext) {
|
|
|
if (!isEnabled()) {
|
|
if (!isEnabled()) {
|
|
|
return plaintext;
|
|
return plaintext;
|