Mercurial > hg > orthanc
comparison OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp @ 2324:53df86a17e99
fix issue #55 (prevent anonymization/modification to unexpectingly break the database model)
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 14 Jul 2017 11:17:56 +0200 |
parents | d19e716b79fa |
children | 415450f11cc7 |
comparison
equal
deleted
inserted
replaced
2323:f5fc61337bdf | 2324:53df86a17e99 |
---|---|
50 { | 50 { |
51 TagOperation_Keep, | 51 TagOperation_Keep, |
52 TagOperation_Remove | 52 TagOperation_Remove |
53 }; | 53 }; |
54 | 54 |
55 static bool IsDatabaseKey(const DicomTag& tag) | |
56 { | |
57 return (tag == DICOM_TAG_PATIENT_ID || | |
58 tag == DICOM_TAG_STUDY_INSTANCE_UID || | |
59 tag == DICOM_TAG_SERIES_INSTANCE_UID || | |
60 tag == DICOM_TAG_SOP_INSTANCE_UID); | |
61 } | |
62 | |
55 static void ParseListOfTags(DicomModification& target, | 63 static void ParseListOfTags(DicomModification& target, |
56 const Json::Value& query, | 64 const Json::Value& query, |
57 TagOperation operation) | 65 TagOperation operation, |
66 bool force) | |
58 { | 67 { |
59 if (!query.isArray()) | 68 if (!query.isArray()) |
60 { | 69 { |
61 throw OrthancException(ErrorCode_BadRequest); | 70 throw OrthancException(ErrorCode_BadRequest); |
62 } | 71 } |
64 for (Json::Value::ArrayIndex i = 0; i < query.size(); i++) | 73 for (Json::Value::ArrayIndex i = 0; i < query.size(); i++) |
65 { | 74 { |
66 std::string name = query[i].asString(); | 75 std::string name = query[i].asString(); |
67 | 76 |
68 DicomTag tag = FromDcmtkBridge::ParseTag(name); | 77 DicomTag tag = FromDcmtkBridge::ParseTag(name); |
78 | |
79 if (!force && IsDatabaseKey(tag)) | |
80 { | |
81 LOG(ERROR) << "Marking tag \"" << name << "\" as to be " | |
82 << (operation == TagOperation_Keep ? "kept" : "removed") | |
83 << " requires the \"Force\" option to be set to true"; | |
84 throw OrthancException(ErrorCode_BadRequest); | |
85 } | |
69 | 86 |
70 switch (operation) | 87 switch (operation) |
71 { | 88 { |
72 case TagOperation_Keep: | 89 case TagOperation_Keep: |
73 target.Keep(tag); | 90 target.Keep(tag); |
85 } | 102 } |
86 } | 103 } |
87 | 104 |
88 | 105 |
89 static void ParseReplacements(DicomModification& target, | 106 static void ParseReplacements(DicomModification& target, |
90 const Json::Value& replacements) | 107 const Json::Value& replacements, |
108 bool force) | |
91 { | 109 { |
92 if (!replacements.isObject()) | 110 if (!replacements.isObject()) |
93 { | 111 { |
94 throw OrthancException(ErrorCode_BadRequest); | 112 throw OrthancException(ErrorCode_BadRequest); |
95 } | 113 } |
99 { | 117 { |
100 const std::string& name = members[i]; | 118 const std::string& name = members[i]; |
101 const Json::Value& value = replacements[name]; | 119 const Json::Value& value = replacements[name]; |
102 | 120 |
103 DicomTag tag = FromDcmtkBridge::ParseTag(name); | 121 DicomTag tag = FromDcmtkBridge::ParseTag(name); |
122 | |
123 if (!force && IsDatabaseKey(tag)) | |
124 { | |
125 LOG(ERROR) << "Marking tag \"" << name << "\" as to be replaced " | |
126 << "requires the \"Force\" option to be set to true"; | |
127 throw OrthancException(ErrorCode_BadRequest); | |
128 } | |
129 | |
104 target.Replace(tag, value, false); | 130 target.Replace(tag, value, false); |
105 | 131 |
106 VLOG(1) << "Replace: " << name << " " << tag | 132 VLOG(1) << "Replace: " << name << " " << tag |
107 << " == " << value.toStyledString() << std::endl; | 133 << " == " << value.toStyledString() << std::endl; |
108 } | 134 } |
114 uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); | 140 uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); |
115 return "Anonymized" + boost::lexical_cast<std::string>(seq); | 141 return "Anonymized" + boost::lexical_cast<std::string>(seq); |
116 } | 142 } |
117 | 143 |
118 | 144 |
145 static bool GetBooleanValue(const std::string& member, | |
146 const Json::Value& json, | |
147 bool defaultValue) | |
148 { | |
149 if (!json.isMember(member)) | |
150 { | |
151 return defaultValue; | |
152 } | |
153 else if (json[member].type() == Json::booleanValue) | |
154 { | |
155 return json[member].asBool(); | |
156 } | |
157 else | |
158 { | |
159 LOG(ERROR) << "Member \"" << member << "\" should be a Boolean value"; | |
160 throw OrthancException(ErrorCode_BadFileFormat); | |
161 } | |
162 } | |
163 | |
119 | 164 |
120 bool OrthancRestApi::ParseModifyRequest(DicomModification& target, | 165 bool OrthancRestApi::ParseModifyRequest(DicomModification& target, |
121 const Json::Value& request) | 166 const Json::Value& request) |
122 { | 167 { |
123 if (request.isObject()) | 168 if (request.isObject()) |
124 { | 169 { |
125 if (request.isMember("RemovePrivateTags")) | 170 bool force = GetBooleanValue("Force", request, false); |
171 | |
172 if (GetBooleanValue("RemovePrivateTags", request, false)) | |
126 { | 173 { |
127 target.SetRemovePrivateTags(true); | 174 target.SetRemovePrivateTags(true); |
128 } | 175 } |
129 | 176 |
130 if (request.isMember("Remove")) | 177 if (request.isMember("Remove")) |
131 { | 178 { |
132 ParseListOfTags(target, request["Remove"], TagOperation_Remove); | 179 ParseListOfTags(target, request["Remove"], TagOperation_Remove, force); |
133 } | 180 } |
134 | 181 |
135 if (request.isMember("Replace")) | 182 if (request.isMember("Replace")) |
136 { | 183 { |
137 ParseReplacements(target, request["Replace"]); | 184 ParseReplacements(target, request["Replace"], force); |
138 } | 185 } |
139 | 186 |
140 // The "Keep" operation only makes sense for the tags | 187 // The "Keep" operation only makes sense for the tags |
141 // StudyInstanceUID, SeriesInstanceUID and SOPInstanceUID. Avoid | 188 // StudyInstanceUID, SeriesInstanceUID and SOPInstanceUID. Avoid |
142 // this feature as much as possible, as this breaks the DICOM | 189 // this feature as much as possible, as this breaks the DICOM |
143 // model of the real world, except if you know exactly what | 190 // model of the real world, except if you know exactly what |
144 // you're doing! | 191 // you're doing! |
145 if (request.isMember("Keep")) | 192 if (request.isMember("Keep")) |
146 { | 193 { |
147 ParseListOfTags(target, request["Keep"], TagOperation_Keep); | 194 ParseListOfTags(target, request["Keep"], TagOperation_Keep, force); |
148 } | 195 } |
149 | 196 |
150 return true; | 197 return true; |
151 } | 198 } |
152 else | 199 else |
183 !request.isObject()) | 230 !request.isObject()) |
184 { | 231 { |
185 return false; | 232 return false; |
186 } | 233 } |
187 | 234 |
235 bool force = GetBooleanValue("Force", request, false); | |
236 | |
188 // As of Orthanc 1.2.1, the default anonymization is done | 237 // As of Orthanc 1.2.1, the default anonymization is done |
189 // according to PS 3.15-2017c Table E.1-1 (basic profile) | 238 // according to PS 3.15-2017c Table E.1-1 (basic profile) |
190 DicomVersion version = DicomVersion_2017c; | 239 DicomVersion version = DicomVersion_2017c; |
191 if (request.isMember("DicomVersion")) | 240 if (request.isMember("DicomVersion")) |
192 { | 241 { |
201 } | 250 } |
202 | 251 |
203 target.SetupAnonymization(version); | 252 target.SetupAnonymization(version); |
204 std::string patientName = target.GetReplacementAsString(DICOM_TAG_PATIENT_NAME); | 253 std::string patientName = target.GetReplacementAsString(DICOM_TAG_PATIENT_NAME); |
205 | 254 |
206 if (request.isMember("KeepPrivateTags")) | 255 if (GetBooleanValue("KeepPrivateTags", request, false)) |
207 { | 256 { |
208 target.SetRemovePrivateTags(false); | 257 target.SetRemovePrivateTags(false); |
209 } | 258 } |
210 | 259 |
211 if (request.isMember("Remove")) | 260 if (request.isMember("Remove")) |
212 { | 261 { |
213 ParseListOfTags(target, request["Remove"], TagOperation_Remove); | 262 ParseListOfTags(target, request["Remove"], TagOperation_Remove, force); |
214 } | 263 } |
215 | 264 |
216 if (request.isMember("Replace")) | 265 if (request.isMember("Replace")) |
217 { | 266 { |
218 ParseReplacements(target, request["Replace"]); | 267 ParseReplacements(target, request["Replace"], force); |
219 } | 268 } |
220 | 269 |
221 if (request.isMember("Keep")) | 270 if (request.isMember("Keep")) |
222 { | 271 { |
223 ParseListOfTags(target, request["Keep"], TagOperation_Keep); | 272 ParseListOfTags(target, request["Keep"], TagOperation_Keep, force); |
224 } | 273 } |
225 | 274 |
226 if (target.IsReplaced(DICOM_TAG_PATIENT_NAME) && | 275 if (target.IsReplaced(DICOM_TAG_PATIENT_NAME) && |
227 target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName) | 276 target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName) |
228 { | 277 { |