# HG changeset patch # User Alain Mazy # Date 1678894602 -3600 # Node ID 94a9484d7f8f490142fcd66bf9aaa43a9e276918 # Parent d301047ee3c456231c4ff4b3ff13e02a5059a567 fix security issues allowing to browse remote dicom servers + introduced UnitTests diff -r d301047ee3c4 -r 94a9484d7f8f CMakeLists.txt --- a/CMakeLists.txt Thu Mar 09 14:37:52 2023 +0100 +++ b/CMakeLists.txt Wed Mar 15 16:36:42 2023 +0100 @@ -57,10 +57,16 @@ link_libraries(${ORTHANC_FRAMEWORK_LIBRARIES}) + set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") + set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") + mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) + include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake) + else() include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake) set(ENABLE_LOCALE OFF) # Disable support for locales (notably in Boost) + set(ENABLE_GOOGLE_TEST ON) set(ENABLE_MODULE_IMAGES OFF) set(ENABLE_MODULE_JOBS OFF) set(ENABLE_MODULE_DICOM OFF) @@ -84,8 +90,6 @@ message(FATAL_ERROR "Unsupported version of the Orthanc plugin SDK: ${ORTHANC_SDK_VERSION}") endif() - -include_directories(${CMAKE_SOURCE_DIR}/Resources/Orthanc//Sdk-1.3.1) else () CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H) if (NOT HAVE_ORTHANC_H) @@ -128,12 +132,21 @@ DEFAULT_CONFIGURATION ${CMAKE_SOURCE_DIR}/Plugin/DefaultConfiguration.json ) - EmbedResources( +EmbedResources( --no-upcase-check ${ADDITIONAL_RESOURCES} ) -add_library(OrthancAuthorization SHARED +# As the embedded resources are shared by both the "UnitTests" and the +# "OrthancAuthorization" targets, avoid race conditions in the code +# generation by adding a target between them +add_custom_target( + AutogeneratedTarget + DEPENDS + ${AUTOGENERATED_SOURCES} + ) + +set(PLUGIN_SOURCES ${CMAKE_SOURCE_DIR}/Plugin/AccessedResource.cpp ${CMAKE_SOURCE_DIR}/Plugin/AssociativeArray.cpp ${CMAKE_SOURCE_DIR}/Plugin/AuthorizationParserBase.cpp @@ -150,8 +163,14 @@ ${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp ${ORTHANC_CORE_SOURCES} ${AUTOGENERATED_SOURCES} - ) +) + +add_library(OrthancAuthorization SHARED + ${PLUGIN_SOURCES} +) + +add_dependencies(OrthancAuthorization AutogeneratedTarget) message("Setting the version of the plugin to ${ORTHANC_PLUGIN_VERSION}") add_definitions(-DORTHANC_PLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}") @@ -165,3 +184,24 @@ RUNTIME DESTINATION lib # Destination for Windows LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux ) + +add_executable(UnitTests + ${PLUGIN_SOURCES} + ${ORTHANC_CORE_SOURCES} + ${GOOGLE_TEST_SOURCES} + UnitTestsSources/UnitTestsMain.cpp + ) + +# add_dependencies(UnitTests AutogeneratedTarget) + +target_include_directories(UnitTests PUBLIC ${ORTHANC_FRAMEWORK_ROOT}) +target_compile_definitions(UnitTests PUBLIC + -DHAS_ORTHANC_EXCEPTION=1 + -DORTHANC_ENABLE_LOGGING_PLUGIN=1 + -DBUILD_UNIT_TESTS=1 + ) + +target_link_libraries(UnitTests + ${GOOGLE_TEST_LIBRARIES} + # ${AUTOGENERATED_SOURCES} + ) \ No newline at end of file diff -r d301047ee3c4 -r 94a9484d7f8f NEWS --- a/NEWS Thu Mar 09 14:37:52 2023 +0100 +++ b/NEWS Wed Mar 15 16:36:42 2023 +0100 @@ -7,6 +7,10 @@ * new GET "auth/user/profile" Rest API route to retrieve user permissions * new PUT "auth/tokens/{token-type}" Rest API route to create tokens * new POST "auth/tokens/decode" Rest API route to decode tokens +* SECURITY FIX: in prior versions, it was possible to browse remote + dicom-web servers without being authenticated. (The API routes + /dicom-web/servers/.../studies were unprotected). The local + dicom-web server was correctly protected. 2022-11-16 - v 0.4.1 diff -r d301047ee3c4 -r 94a9484d7f8f Plugin/AuthorizationParserBase.cpp --- a/Plugin/AuthorizationParserBase.cpp Thu Mar 09 14:37:52 2023 +0100 +++ b/Plugin/AuthorizationParserBase.cpp Wed Mar 15 16:36:42 2023 +0100 @@ -102,6 +102,18 @@ target.push_back(AccessedResource(Orthanc::ResourceType_Study, study, studyDicomUid)); } + void AuthorizationParserBase::AddDicomPatient(AccessedResources& target, + const std::string& patientId) + { + std::string patient; + + if (!resourceHierarchy_->LookupOrthancId(patient, Orthanc::ResourceType_Patient, patientId)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + AddResourceInternal(target, Orthanc::ResourceType_Patient, patient); + } void AuthorizationParserBase::AddDicomSeries(AccessedResources& target, const std::string& studyDicomUid, diff -r d301047ee3c4 -r 94a9484d7f8f Plugin/AuthorizationParserBase.h --- a/Plugin/AuthorizationParserBase.h Thu Mar 09 14:37:52 2023 +0100 +++ b/Plugin/AuthorizationParserBase.h Wed Mar 15 16:36:42 2023 +0100 @@ -23,6 +23,11 @@ #include // For std::unique_ptr<> +#if BUILD_UNIT_TESTS == 1 +# include +#endif + + namespace OrthancPlugins { class AuthorizationParserBase : public IAuthorizationParser @@ -47,6 +52,9 @@ void AddOrthancPatient(AccessedResources& target, const std::string& orthancId); + void AddDicomPatient(AccessedResources& target, + const std::string& patientId); + void AddDicomStudy(AccessedResources& target, const std::string& studyDicomUid); @@ -67,5 +75,12 @@ { resourceHierarchy_->Invalidate(level, id); } + + FRIEND_TEST(DefaultAuthorizationParser, Parse); + protected: + ResourceHierarchyCache* GetResourceHierarchy() + { + return resourceHierarchy_.get(); + } }; } diff -r d301047ee3c4 -r 94a9484d7f8f Plugin/DefaultAuthorizationParser.cpp --- a/Plugin/DefaultAuthorizationParser.cpp Thu Mar 09 14:37:52 2023 +0100 +++ b/Plugin/DefaultAuthorizationParser.cpp Wed Mar 15 16:36:42 2023 +0100 @@ -50,7 +50,7 @@ "^" + tmp + "/studies/([.0-9]+)/series/([.0-9]+)/instances/([.0-9]+)(|/|/frames/.*)$"); dicomWebQidoRsFind_ = boost::regex( - "^" + tmp + "/(studies|series|instances)\?(.*)$"); + "^" + tmp + "/(studies|series|instances)$"); } @@ -134,40 +134,46 @@ } else if (boost::regex_match(uri, what, dicomWebQidoRsFind_)) { - std::string studyInstanceUid, seriesInstanceUid, sopInstanceUid; + std::string studyInstanceUid, seriesInstanceUid, sopInstanceUid, patientId; studyInstanceUid = Orthanc::HttpToolbox::GetArgument(getArguments, "0020000D", ""); seriesInstanceUid = Orthanc::HttpToolbox::GetArgument(getArguments, "0020000E", ""); sopInstanceUid = Orthanc::HttpToolbox::GetArgument(getArguments, "00080018", ""); + patientId = Orthanc::HttpToolbox::GetArgument(getArguments, "00100010", ""); if (!sopInstanceUid.empty() && !seriesInstanceUid.empty() && !studyInstanceUid.empty()) { AddDicomInstance(target, studyInstanceUid, seriesInstanceUid, sopInstanceUid); + return true; } else if (!seriesInstanceUid.empty() && !studyInstanceUid.empty()) { AddDicomSeries(target, studyInstanceUid, seriesInstanceUid); + return true; } else if (!studyInstanceUid.empty()) { AddDicomStudy(target, studyInstanceUid); + return true; } - return true; + else if (!patientId.empty()) + { + AddDicomPatient(target, patientId); + return true; + } } - else - { - // Unknown type of resource: Consider it as a system access + + // Unknown type of resource: Consider it as a system access - // Remove the trailing slashes if need be - std::string s = uri; - while (!s.empty() && - s[s.length() - 1] == '/') - { - s = s.substr(0, s.length() - 1); - } - - target.push_back(AccessedResource(AccessLevel_System, s, "")); - return true; - } + // Remove the trailing slashes if need be + std::string s = uri; + while (!s.empty() && + s[s.length() - 1] == '/') + { + s = s.substr(0, s.length() - 1); + } + + target.push_back(AccessedResource(AccessLevel_System, s, "")); + return true; } } diff -r d301047ee3c4 -r 94a9484d7f8f Plugin/DefaultConfiguration.json --- a/Plugin/DefaultConfiguration.json Thu Mar 09 14:37:52 2023 +0100 +++ b/Plugin/DefaultConfiguration.json Wed Mar 15 16:36:42 2023 +0100 @@ -50,6 +50,7 @@ // The default configuration is suitable for Orthanc-Explorer-2 (see TBD sample) "Permissions" : [ ["post", "^/auth/tokens/decode$", ""], + ["post", "^/tools/lookup$", ""], // currently used to authorize downloads in Stone (to map the StudyInstanceUID into an OrthancID. Not ideal -> we should define a new API that has the resource ID in the path to be able to check it at resource level) but, on another hand, you do not get any Patient information from this route // elemental browsing in OE2 ["post", "^/tools/find$", "all|view"], @@ -83,7 +84,14 @@ // modifications/anonymization ["post", "^/(patients|studies|series|instances)/([a-f0-9-]+)/modify(.*)$", "all|modify"], - ["post", "^/(patients|studies|series|instances)/([a-f0-9-]+)/anonymize(.*)$", "all|anonymize"] + ["post", "^/(patients|studies|series|instances)/([a-f0-9-]+)/anonymize(.*)$", "all|anonymize"], + + // deletes + ["delete" , "^/(patients|studies|series|instances)/([a-f0-9-]+)$", "all|delete"], + + // settings + ["put", "^/tools/log-level$", "all|settings"], + ["get", "^/tools/log-level$", "all|settings"] ] } } \ No newline at end of file diff -r d301047ee3c4 -r 94a9484d7f8f Plugin/Plugin.cpp --- a/Plugin/Plugin.cpp Thu Mar 09 14:37:52 2023 +0100 +++ b/Plugin/Plugin.cpp Wed Mar 15 16:36:42 2023 +0100 @@ -745,7 +745,6 @@ { uncheckedFolders_.push_back("/stone-webviewer/"); uncheckedResources_.insert("/system"); // for Stone to check that Orthanc is the server providing the data - uncheckedResources_.insert("/tools/lookup"); // for Downloads (we consider that having access to tools/lookup can not give information about other patients/studies since it only return IDs, no patient data) tokens_.insert(OrthancPlugins::Token(OrthancPlugins::TokenType_HttpHeader, "Authorization")); } diff -r d301047ee3c4 -r 94a9484d7f8f Plugin/ResourceHierarchyCache.cpp --- a/Plugin/ResourceHierarchyCache.cpp Thu Mar 09 14:37:52 2023 +0100 +++ b/Plugin/ResourceHierarchyCache.cpp Wed Mar 15 16:36:42 2023 +0100 @@ -193,4 +193,21 @@ return false; } } + + void ResourceHierarchyCache::AddOrthancDicomMapping(Orthanc::ResourceType level, + const std::string& orthancId, + const std::string& dicomUid) + { + dicomToOrthanc_->Store(ComputeKey(level, dicomUid), orthancId, 0 /* no expiration */); + orthancToDicom_->Store(ComputeKey(level, orthancId), dicomUid, 0 /* no expiration */); + } + + void ResourceHierarchyCache::AddParentLink(Orthanc::ResourceType childLevel, + const std::string& childOrthancId, + const std::string& parentOrthancId) + { + cache_->Store(ComputeKey(childLevel, childOrthancId), parentOrthancId, 0 /* no expiration */); + } + + } diff -r d301047ee3c4 -r 94a9484d7f8f Plugin/ResourceHierarchyCache.h --- a/Plugin/ResourceHierarchyCache.h Thu Mar 09 14:37:52 2023 +0100 +++ b/Plugin/ResourceHierarchyCache.h Wed Mar 15 16:36:42 2023 +0100 @@ -26,6 +26,10 @@ #include +#if BUILD_UNIT_TESTS == 1 +# include +#endif + namespace OrthancPlugins { class ResourceHierarchyCache : public boost::noncopyable @@ -84,5 +88,16 @@ bool LookupOrthancId(std::string& target, Orthanc::ResourceType level, const std::string& dicomUid); + + FRIEND_TEST(DefaultAuthorizationParser, Parse); + protected: + void AddOrthancDicomMapping(Orthanc::ResourceType level, + const std::string& orthancId, + const std::string& dicomUid); + + void AddParentLink(Orthanc::ResourceType childLevel, + const std::string& childOrthancId, + const std::string& parentOrthancId); + }; } diff -r d301047ee3c4 -r 94a9484d7f8f UnitTestsSources/UnitTestsMain.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Mar 15 16:36:42 2023 +0100 @@ -0,0 +1,246 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2021 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include +#include +#include + +#include "../Plugin/DefaultAuthorizationParser.h" +#include "../Plugin/AssociativeArray.h" +#include "../Plugin/AccessedResource.h" +#include "../Plugin/MemoryCache.h" +#include "../Plugin/PermissionParser.h" +#include "../Plugin/ResourceHierarchyCache.h" + +using namespace OrthancPlugins; + +std::string instanceOrthancId = "44444444-44444444-44444444-44444444-44444444"; +std::string seriesOrthancId = "33333333-33333333-33333333-33333333-33333333"; +std::string studyOrthancId = "22222222-22222222-22222222-22222222-22222222"; +std::string patientOrthancId = "11111111-11111111-11111111-11111111-11111111"; + +std::string instanceDicomUid = "4.4"; +std::string seriesDicomUid = "3.3"; +std::string studyDicomUid = "2.2"; +std::string patientDicomUid = "PATIENT.1"; + +bool IsAccessing(const IAuthorizationParser::AccessedResources& accesses, AccessLevel level, const std::string& orthancId) +{ + for (IAuthorizationParser::AccessedResources::const_iterator it = accesses.begin(); it != accesses.end(); ++it) + { + if (it->GetLevel() == level && it->GetOrthancId() == orthancId) + { + return true; + } + } + return false; +} + +namespace OrthancPlugins +{ + // The namespace is necessary for friend classes to work + // http://code.google.com/p/googletest/wiki/AdvancedGuide#Private_Class_Members + +TEST(DefaultAuthorizationParser, Parse) +{ + MemoryCache::Factory factory(10); + DefaultAuthorizationParser parser(factory, "/dicom-web/"); + ResourceHierarchyCache* cache = parser.GetResourceHierarchy(); + + cache->AddOrthancDicomMapping(Orthanc::ResourceType_Instance, instanceOrthancId, instanceDicomUid); + cache->AddOrthancDicomMapping(Orthanc::ResourceType_Series, seriesOrthancId, seriesDicomUid); + cache->AddOrthancDicomMapping(Orthanc::ResourceType_Study, studyOrthancId, studyDicomUid); + cache->AddOrthancDicomMapping(Orthanc::ResourceType_Patient, patientOrthancId, patientDicomUid); + + cache->AddParentLink(Orthanc::ResourceType_Instance, instanceOrthancId, seriesOrthancId); + cache->AddParentLink(Orthanc::ResourceType_Series, seriesOrthancId, studyOrthancId); + cache->AddParentLink(Orthanc::ResourceType_Study, studyOrthancId, patientOrthancId); + + IAuthorizationParser::AccessedResources accesses; + AssociativeArray noGetArguments(0, NULL, NULL, false); + + accesses.clear(); + parser.Parse(accesses, "/studies/22222222-22222222-22222222-22222222-22222222/", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/studies/22222222-22222222-22222222-22222222-22222222/instances", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/studies/22222222-22222222-22222222-22222222-22222222/archive", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/osimis-viewer/studies/22222222-22222222-22222222-22222222-22222222/archive", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/series/33333333-33333333-33333333-33333333-33333333/", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/series/33333333-33333333-33333333-33333333-33333333/media", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/series/33333333-33333333-33333333-33333333-33333333/modify", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/web-viewer/series/33333333-33333333-33333333-33333333-33333333", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/osimis-viewer/series/33333333-33333333-33333333-33333333-33333333", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/instances/44444444-44444444-44444444-44444444-44444444/file", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Instance, instanceOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/web-viewer/instances/jpeg95-44444444-44444444-44444444-44444444-44444444_0", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Instance, instanceOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/osimis-viewer/images/44444444-44444444-44444444-44444444-44444444/0/high-quality", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Instance, instanceOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/system", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_System, "/system")); + + + ///////////////////////// dicom-web + accesses.clear(); + parser.Parse(accesses, "/dicom-web/studies/2.2", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/dicom-web/studies/2.2/series/3.3", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/dicom-web/studies/2.2/series/3.3/instances/4.4", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Instance, instanceOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + accesses.clear(); + parser.Parse(accesses, "/dicom-web/studies/2.2/series/3.3/instances/4.4/frames/0", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Instance, instanceOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + + { + accesses.clear(); + const char* getKeys[] = {"0020000D"}; + const char* getValues[] = {"2.2"}; + AssociativeArray getArguments(1, getKeys, getValues, false); + parser.Parse(accesses, "/dicom-web/studies", getArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + } + { + accesses.clear(); + const char* getKeys[] = {"0020000D", "0020000E"}; + const char* getValues[] = {"2.2", "3.3"}; + AssociativeArray getArguments(2, getKeys, getValues, false); + parser.Parse(accesses, "/dicom-web/series", getArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + } + { + accesses.clear(); + const char* getKeys[] = {"0020000D", "00080018", "0020000E"}; + const char* getValues[] = {"2.2", "4.4", "3.3", }; + AssociativeArray getArguments(3, getKeys, getValues, false); + parser.Parse(accesses, "/dicom-web/studies", getArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Instance, instanceOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Series, seriesOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Study, studyOrthancId)); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + } + { + accesses.clear(); + const char* getKeys[] = {"00100010"}; + const char* getValues[] = {"PATIENT.1"}; + AssociativeArray getArguments(1, getKeys, getValues, false); + parser.Parse(accesses, "/dicom-web/studies", getArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_Patient, patientOrthancId)); + } + + { // qido with no arguments = search all => system resource + accesses.clear(); + parser.Parse(accesses, "/dicom-web/studies", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_System, "/dicom-web/studies")); + } + + accesses.clear(); + parser.Parse(accesses, "/dicom-web/servers", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_System, "/dicom-web/servers")); + + accesses.clear(); + parser.Parse(accesses, "/dicom-web/info", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_System, "/dicom-web/info")); + + accesses.clear(); + parser.Parse(accesses, "/dicom-web/servers/test/qido", noGetArguments.GetMap()); + ASSERT_TRUE(IsAccessing(accesses, AccessLevel_System, "/dicom-web/servers/test/qido")); + +} +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}