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 {