comparison OrthancFramework/Sources/WebServiceParameters.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Core/WebServiceParameters.cpp@ae0e3fd609df
children 3dda0d73193c
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "PrecompiledHeaders.h"
35 #include "WebServiceParameters.h"
36
37 #include "Logging.h"
38 #include "OrthancException.h"
39 #include "SerializationToolbox.h"
40 #include "Toolbox.h"
41
42 #if ORTHANC_SANDBOXED == 0
43 # include "SystemToolbox.h"
44 #endif
45
46 #include <cassert>
47
48 namespace Orthanc
49 {
50 static const char* KEY_CERTIFICATE_FILE = "CertificateFile";
51 static const char* KEY_CERTIFICATE_KEY_FILE = "CertificateKeyFile";
52 static const char* KEY_CERTIFICATE_KEY_PASSWORD = "CertificateKeyPassword";
53 static const char* KEY_HTTP_HEADERS = "HttpHeaders";
54 static const char* KEY_PASSWORD = "Password";
55 static const char* KEY_PKCS11 = "Pkcs11";
56 static const char* KEY_URL = "Url";
57 static const char* KEY_URL_2 = "URL";
58 static const char* KEY_USERNAME = "Username";
59
60
61 static bool IsReservedKey(const std::string& key)
62 {
63 return (key == KEY_CERTIFICATE_FILE ||
64 key == KEY_CERTIFICATE_KEY_FILE ||
65 key == KEY_CERTIFICATE_KEY_PASSWORD ||
66 key == KEY_HTTP_HEADERS ||
67 key == KEY_PASSWORD ||
68 key == KEY_PKCS11 ||
69 key == KEY_URL ||
70 key == KEY_URL_2 ||
71 key == KEY_USERNAME);
72 }
73
74
75 WebServiceParameters::WebServiceParameters() :
76 pkcs11Enabled_(false)
77 {
78 SetUrl("http://127.0.0.1:8042/");
79 }
80
81
82 void WebServiceParameters::ClearClientCertificate()
83 {
84 certificateFile_.clear();
85 certificateKeyFile_.clear();
86 certificateKeyPassword_.clear();
87 }
88
89
90 void WebServiceParameters::SetUrl(const std::string& url)
91 {
92 if (!Toolbox::StartsWith(url, "http://") &&
93 !Toolbox::StartsWith(url, "https://"))
94 {
95 throw OrthancException(ErrorCode_BadFileFormat, "Bad URL: " + url);
96 }
97
98 // Add trailing slash if needed
99 if (url[url.size() - 1] == '/')
100 {
101 url_ = url;
102 }
103 else
104 {
105 url_ = url + '/';
106 }
107 }
108
109
110 void WebServiceParameters::ClearCredentials()
111 {
112 username_.clear();
113 password_.clear();
114 }
115
116
117 void WebServiceParameters::SetCredentials(const std::string& username,
118 const std::string& password)
119 {
120 if (username.empty() &&
121 !password.empty())
122 {
123 throw OrthancException(ErrorCode_BadFileFormat);
124 }
125 else
126 {
127 username_ = username;
128 password_ = password;
129 }
130 }
131
132
133 void WebServiceParameters::SetClientCertificate(const std::string& certificateFile,
134 const std::string& certificateKeyFile,
135 const std::string& certificateKeyPassword)
136 {
137 if (certificateFile.empty())
138 {
139 throw OrthancException(ErrorCode_ParameterOutOfRange);
140 }
141
142 if (certificateKeyPassword.empty())
143 {
144 throw OrthancException(
145 ErrorCode_BadFileFormat,
146 "The password for the HTTPS certificate is not provided: " + certificateFile);
147 }
148
149 certificateFile_ = certificateFile;
150 certificateKeyFile_ = certificateKeyFile;
151 certificateKeyPassword_ = certificateKeyPassword;
152 }
153
154
155 void WebServiceParameters::FromSimpleFormat(const Json::Value& peer)
156 {
157 assert(peer.isArray());
158
159 pkcs11Enabled_ = false;
160 ClearClientCertificate();
161
162 if (peer.size() != 1 &&
163 peer.size() != 3)
164 {
165 throw OrthancException(ErrorCode_BadFileFormat);
166 }
167
168 SetUrl(peer.get(0u, "").asString());
169
170 if (peer.size() == 1)
171 {
172 ClearCredentials();
173 }
174 else if (peer.size() == 2)
175 {
176 throw OrthancException(ErrorCode_BadFileFormat,
177 "The HTTP password is not provided");
178 }
179 else if (peer.size() == 3)
180 {
181 SetCredentials(peer.get(1u, "").asString(),
182 peer.get(2u, "").asString());
183 }
184 else
185 {
186 throw OrthancException(ErrorCode_BadFileFormat);
187 }
188 }
189
190
191 static std::string GetStringMember(const Json::Value& peer,
192 const std::string& key,
193 const std::string& defaultValue)
194 {
195 if (!peer.isMember(key))
196 {
197 return defaultValue;
198 }
199 else if (peer[key].type() != Json::stringValue)
200 {
201 throw OrthancException(ErrorCode_BadFileFormat);
202 }
203 else
204 {
205 return peer[key].asString();
206 }
207 }
208
209
210 void WebServiceParameters::FromAdvancedFormat(const Json::Value& peer)
211 {
212 assert(peer.isObject());
213
214 std::string url = GetStringMember(peer, KEY_URL, "");
215 if (url.empty())
216 {
217 SetUrl(GetStringMember(peer, KEY_URL_2, ""));
218 }
219 else
220 {
221 SetUrl(url);
222 }
223
224 SetCredentials(GetStringMember(peer, KEY_USERNAME, ""),
225 GetStringMember(peer, KEY_PASSWORD, ""));
226
227 std::string file = GetStringMember(peer, KEY_CERTIFICATE_FILE, "");
228 if (!file.empty())
229 {
230 SetClientCertificate(file, GetStringMember(peer, KEY_CERTIFICATE_KEY_FILE, ""),
231 GetStringMember(peer, KEY_CERTIFICATE_KEY_PASSWORD, ""));
232 }
233 else
234 {
235 ClearClientCertificate();
236 }
237
238 if (peer.isMember(KEY_PKCS11))
239 {
240 if (peer[KEY_PKCS11].type() == Json::booleanValue)
241 {
242 pkcs11Enabled_ = peer[KEY_PKCS11].asBool();
243 }
244 else
245 {
246 throw OrthancException(ErrorCode_BadFileFormat);
247 }
248 }
249 else
250 {
251 pkcs11Enabled_ = false;
252 }
253
254
255 headers_.clear();
256
257 if (peer.isMember(KEY_HTTP_HEADERS))
258 {
259 const Json::Value& h = peer[KEY_HTTP_HEADERS];
260 if (h.type() != Json::objectValue)
261 {
262 throw OrthancException(ErrorCode_BadFileFormat);
263 }
264 else
265 {
266 Json::Value::Members keys = h.getMemberNames();
267 for (size_t i = 0; i < keys.size(); i++)
268 {
269 const Json::Value& value = h[keys[i]];
270 if (value.type() != Json::stringValue)
271 {
272 throw OrthancException(ErrorCode_BadFileFormat);
273 }
274 else
275 {
276 headers_[keys[i]] = value.asString();
277 }
278 }
279 }
280 }
281
282
283 userProperties_.clear();
284
285 const Json::Value::Members members = peer.getMemberNames();
286
287 for (Json::Value::Members::const_iterator it = members.begin();
288 it != members.end(); ++it)
289 {
290 if (!IsReservedKey(*it))
291 {
292 switch (peer[*it].type())
293 {
294 case Json::stringValue:
295 userProperties_[*it] = peer[*it].asString();
296 break;
297
298 case Json::booleanValue:
299 userProperties_[*it] = peer[*it].asBool() ? "1" : "0";
300 break;
301
302 case Json::intValue:
303 userProperties_[*it] = boost::lexical_cast<std::string>(peer[*it].asInt());
304 break;
305
306 default:
307 throw OrthancException(ErrorCode_BadFileFormat,
308 "User-defined properties associated with a Web service must be strings: " + *it);
309 }
310 }
311 }
312 }
313
314
315 void WebServiceParameters::Unserialize(const Json::Value& peer)
316 {
317 try
318 {
319 if (peer.isArray())
320 {
321 FromSimpleFormat(peer);
322 }
323 else if (peer.isObject())
324 {
325 FromAdvancedFormat(peer);
326 }
327 else
328 {
329 throw OrthancException(ErrorCode_BadFileFormat);
330 }
331 }
332 catch (OrthancException&)
333 {
334 throw;
335 }
336 catch (...)
337 {
338 throw OrthancException(ErrorCode_BadFileFormat);
339 }
340 }
341
342
343 void WebServiceParameters::ListHttpHeaders(std::set<std::string>& target) const
344 {
345 target.clear();
346
347 for (Dictionary::const_iterator it = headers_.begin();
348 it != headers_.end(); ++it)
349 {
350 target.insert(it->first);
351 }
352 }
353
354
355 bool WebServiceParameters::LookupHttpHeader(std::string& value,
356 const std::string& key) const
357 {
358 Dictionary::const_iterator found = headers_.find(key);
359
360 if (found == headers_.end())
361 {
362 return false;
363 }
364 else
365 {
366 value = found->second;
367 return true;
368 }
369 }
370
371
372 void WebServiceParameters::AddUserProperty(const std::string& key,
373 const std::string& value)
374 {
375 if (IsReservedKey(key))
376 {
377 throw OrthancException(
378 ErrorCode_ParameterOutOfRange,
379 "Cannot use this reserved key to name an user property: " + key);
380 }
381 else
382 {
383 userProperties_[key] = value;
384 }
385 }
386
387
388 void WebServiceParameters::ListUserProperties(std::set<std::string>& target) const
389 {
390 target.clear();
391
392 for (Dictionary::const_iterator it = userProperties_.begin();
393 it != userProperties_.end(); ++it)
394 {
395 target.insert(it->first);
396 }
397 }
398
399
400 bool WebServiceParameters::LookupUserProperty(std::string& value,
401 const std::string& key) const
402 {
403 Dictionary::const_iterator found = userProperties_.find(key);
404
405 if (found == userProperties_.end())
406 {
407 return false;
408 }
409 else
410 {
411 value = found->second;
412 return true;
413 }
414 }
415
416
417 bool WebServiceParameters::GetBooleanUserProperty(const std::string& key,
418 bool defaultValue) const
419 {
420 Dictionary::const_iterator found = userProperties_.find(key);
421
422 if (found == userProperties_.end())
423 {
424 return defaultValue;
425 }
426 else if (found->second == "0" ||
427 found->second == "false")
428 {
429 return false;
430 }
431 else if (found->second == "1" ||
432 found->second == "true")
433 {
434 return true;
435 }
436 else
437 {
438 throw OrthancException(ErrorCode_BadFileFormat, "Bad value for a Boolean user property in the parameters "
439 "of a Web service: Property \"" + key + "\" equals: " + found->second);
440 }
441 }
442
443
444 bool WebServiceParameters::IsAdvancedFormatNeeded() const
445 {
446 return (!certificateFile_.empty() ||
447 !certificateKeyFile_.empty() ||
448 !certificateKeyPassword_.empty() ||
449 pkcs11Enabled_ ||
450 !headers_.empty() ||
451 !userProperties_.empty());
452 }
453
454
455 void WebServiceParameters::Serialize(Json::Value& value,
456 bool forceAdvancedFormat,
457 bool includePasswords) const
458 {
459 if (forceAdvancedFormat ||
460 IsAdvancedFormatNeeded())
461 {
462 value = Json::objectValue;
463 value[KEY_URL] = url_;
464
465 if (!username_.empty() ||
466 !password_.empty())
467 {
468 value[KEY_USERNAME] = username_;
469
470 if (includePasswords)
471 {
472 value[KEY_PASSWORD] = password_;
473 }
474 }
475
476 if (!certificateFile_.empty())
477 {
478 value[KEY_CERTIFICATE_FILE] = certificateFile_;
479 }
480
481 if (!certificateKeyFile_.empty())
482 {
483 value[KEY_CERTIFICATE_KEY_FILE] = certificateKeyFile_;
484 }
485
486 if (!certificateKeyPassword_.empty() &&
487 includePasswords)
488 {
489 value[KEY_CERTIFICATE_KEY_PASSWORD] = certificateKeyPassword_;
490 }
491
492 value[KEY_PKCS11] = pkcs11Enabled_;
493
494 value[KEY_HTTP_HEADERS] = Json::objectValue;
495 for (Dictionary::const_iterator it = headers_.begin();
496 it != headers_.end(); ++it)
497 {
498 value[KEY_HTTP_HEADERS][it->first] = it->second;
499 }
500
501 for (Dictionary::const_iterator it = userProperties_.begin();
502 it != userProperties_.end(); ++it)
503 {
504 value[it->first] = it->second;
505 }
506 }
507 else
508 {
509 value = Json::arrayValue;
510 value.append(url_);
511
512 if (!username_.empty() ||
513 !password_.empty())
514 {
515 value.append(username_);
516 value.append(includePasswords ? password_ : "");
517 }
518 }
519 }
520
521
522 #if ORTHANC_SANDBOXED == 0
523 void WebServiceParameters::CheckClientCertificate() const
524 {
525 if (!certificateFile_.empty())
526 {
527 if (!SystemToolbox::IsRegularFile(certificateFile_))
528 {
529 throw OrthancException(ErrorCode_InexistentFile,
530 "Cannot open certificate file: " + certificateFile_);
531 }
532
533 if (!certificateKeyFile_.empty() &&
534 !SystemToolbox::IsRegularFile(certificateKeyFile_))
535 {
536 throw OrthancException(ErrorCode_InexistentFile,
537 "Cannot open key file: " + certificateKeyFile_);
538 }
539 }
540 }
541 #endif
542
543
544 void WebServiceParameters::FormatPublic(Json::Value& target) const
545 {
546 target = Json::objectValue;
547
548 // Only return the public information identifying the destination.
549 // "Security"-related information such as passwords and HTTP
550 // headers are shown as "null" values.
551 target[KEY_URL] = url_;
552
553 if (!username_.empty())
554 {
555 target[KEY_USERNAME] = username_;
556 target[KEY_PASSWORD] = Json::nullValue;
557 }
558
559 if (!certificateFile_.empty())
560 {
561 target[KEY_CERTIFICATE_FILE] = certificateFile_;
562 target[KEY_CERTIFICATE_KEY_FILE] = Json::nullValue;
563 target[KEY_CERTIFICATE_KEY_PASSWORD] = Json::nullValue;
564 }
565
566 target[KEY_PKCS11] = pkcs11Enabled_;
567
568 Json::Value headers = Json::arrayValue;
569
570 for (Dictionary::const_iterator it = headers_.begin();
571 it != headers_.end(); ++it)
572 {
573 // Only list the HTTP headers, not their value
574 headers.append(it->first);
575 }
576
577 target[KEY_HTTP_HEADERS] = headers;
578
579 for (Dictionary::const_iterator it = userProperties_.begin();
580 it != userProperties_.end(); ++it)
581 {
582 target[it->first] = it->second;
583 }
584 }
585 }