# HG changeset patch # User Sebastien Jodogne # Date 1500023876 -7200 # Node ID 53df86a17e99fac0bdb70d9769ec04495d977b36 # Parent f5fc61337bdf3d5b4baa8ba86c18c9d74ea3db60 fix issue #55 (prevent anonymization/modification to unexpectingly break the database model) diff -r f5fc61337bdf -r 53df86a17e99 NEWS --- a/NEWS Fri Jul 14 10:50:00 2017 +0200 +++ b/NEWS Fri Jul 14 11:17:56 2017 +0200 @@ -34,12 +34,14 @@ * Ability to retrieve raw frames encoded as unsigned 32-bits integers * Fix issue 29 (more consistent handling of the "--upgrade" argument) -* Fix issue 31 (Create new modality types for Philips ADW, GE Xeleris, GE AWServer) +* Fix issue 31 (create new modality types for Philips ADW, GE Xeleris, GE AWServer) * Fix issue 35 (AET name is not transferred to Orthanc using DCMTK 3.6.0) -* Fix issue 44 (Bad interpretation of photometric interpretation MONOCHROME1) +* Fix issue 44 (bad interpretation of photometric interpretation MONOCHROME1) * Fix issue 45 (crash when providing a folder to "--config" command-line option) * Fix issue 46 (PHI remaining after anonymization) -* Fix issue 49 (Worklists: accentuated characters are removed from C-Find responses) +* Fix issue 49 (worklists: accentuated characters are removed from C-Find responses) +* Fix issue 55 (modification/anonymization of tags that might break the database + model now require the "Force" parameter to be set to "true" in the query) * Fix Debian #865606 (orthanc FTBFS with libdcmtk-dev 3.6.1~20170228-2) * Fix XSS inside DICOM in Orthanc Explorer (as reported by Victor Pasnkel, Morphus Labs) * Upgrade forthcoming DCMTK 3.6.1 to snapshot 20170228 diff -r f5fc61337bdf -r 53df86a17e99 OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Fri Jul 14 10:50:00 2017 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Fri Jul 14 11:17:56 2017 +0200 @@ -52,9 +52,18 @@ TagOperation_Remove }; + static bool IsDatabaseKey(const DicomTag& tag) + { + return (tag == DICOM_TAG_PATIENT_ID || + tag == DICOM_TAG_STUDY_INSTANCE_UID || + tag == DICOM_TAG_SERIES_INSTANCE_UID || + tag == DICOM_TAG_SOP_INSTANCE_UID); + } + static void ParseListOfTags(DicomModification& target, const Json::Value& query, - TagOperation operation) + TagOperation operation, + bool force) { if (!query.isArray()) { @@ -67,6 +76,14 @@ DicomTag tag = FromDcmtkBridge::ParseTag(name); + if (!force && IsDatabaseKey(tag)) + { + LOG(ERROR) << "Marking tag \"" << name << "\" as to be " + << (operation == TagOperation_Keep ? "kept" : "removed") + << " requires the \"Force\" option to be set to true"; + throw OrthancException(ErrorCode_BadRequest); + } + switch (operation) { case TagOperation_Keep: @@ -87,7 +104,8 @@ static void ParseReplacements(DicomModification& target, - const Json::Value& replacements) + const Json::Value& replacements, + bool force) { if (!replacements.isObject()) { @@ -101,6 +119,14 @@ const Json::Value& value = replacements[name]; DicomTag tag = FromDcmtkBridge::ParseTag(name); + + if (!force && IsDatabaseKey(tag)) + { + LOG(ERROR) << "Marking tag \"" << name << "\" as to be replaced " + << "requires the \"Force\" option to be set to true"; + throw OrthancException(ErrorCode_BadRequest); + } + target.Replace(tag, value, false); VLOG(1) << "Replace: " << name << " " << tag @@ -116,25 +142,46 @@ } + static bool GetBooleanValue(const std::string& member, + const Json::Value& json, + bool defaultValue) + { + if (!json.isMember(member)) + { + return defaultValue; + } + else if (json[member].type() == Json::booleanValue) + { + return json[member].asBool(); + } + else + { + LOG(ERROR) << "Member \"" << member << "\" should be a Boolean value"; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + bool OrthancRestApi::ParseModifyRequest(DicomModification& target, const Json::Value& request) { if (request.isObject()) { - if (request.isMember("RemovePrivateTags")) + bool force = GetBooleanValue("Force", request, false); + + if (GetBooleanValue("RemovePrivateTags", request, false)) { target.SetRemovePrivateTags(true); } if (request.isMember("Remove")) { - ParseListOfTags(target, request["Remove"], TagOperation_Remove); + ParseListOfTags(target, request["Remove"], TagOperation_Remove, force); } if (request.isMember("Replace")) { - ParseReplacements(target, request["Replace"]); + ParseReplacements(target, request["Replace"], force); } // The "Keep" operation only makes sense for the tags @@ -144,7 +191,7 @@ // you're doing! if (request.isMember("Keep")) { - ParseListOfTags(target, request["Keep"], TagOperation_Keep); + ParseListOfTags(target, request["Keep"], TagOperation_Keep, force); } return true; @@ -185,6 +232,8 @@ return false; } + bool force = GetBooleanValue("Force", request, false); + // As of Orthanc 1.2.1, the default anonymization is done // according to PS 3.15-2017c Table E.1-1 (basic profile) DicomVersion version = DicomVersion_2017c; @@ -203,24 +252,24 @@ target.SetupAnonymization(version); std::string patientName = target.GetReplacementAsString(DICOM_TAG_PATIENT_NAME); - if (request.isMember("KeepPrivateTags")) + if (GetBooleanValue("KeepPrivateTags", request, false)) { target.SetRemovePrivateTags(false); } if (request.isMember("Remove")) { - ParseListOfTags(target, request["Remove"], TagOperation_Remove); + ParseListOfTags(target, request["Remove"], TagOperation_Remove, force); } if (request.isMember("Replace")) { - ParseReplacements(target, request["Replace"]); + ParseReplacements(target, request["Replace"], force); } if (request.isMember("Keep")) { - ParseListOfTags(target, request["Keep"], TagOperation_Keep); + ParseListOfTags(target, request["Keep"], TagOperation_Keep, force); } if (target.IsReplaced(DICOM_TAG_PATIENT_NAME) &&