# HG changeset patch # User Alain Mazy # Date 1708011021 -3600 # Node ID 423531fb120060f5973d2f0374610503c32aa044 # Parent 20c638fa8b07b37f45d716ed605bf7983d2429ab SINGLE_RESOURCE_PATTERNS to facilitate api-key support diff -r 20c638fa8b07 -r 423531fb1200 NEWS --- a/NEWS Thu Feb 15 12:04:28 2024 +0100 +++ b/NEWS Thu Feb 15 16:30:21 2024 +0100 @@ -1,7 +1,13 @@ Pending changes in the mainline =============================== -* Added new default permissions for dicom-web STOW-RS, QIDO-RS and WADO-RS. +* Added new default permissions "SINGLE_RESOURCE_PATTERNS" and + reorganized the permissions accordingly. + This notably facilitate accessing a single resource with a user token. + The plugins first check that the user has the right permissions to access the route + and then, the plugins check if the study has one of the authorized_labels + of the user. This notably improves user token handling outside of OE2 + e.g when using api-key. 2023-12-19 - v 0.6.2 diff -r 20c638fa8b07 -r 423531fb1200 Plugin/DefaultAuthorizationParser.cpp --- a/Plugin/DefaultAuthorizationParser.cpp Thu Feb 15 12:04:28 2024 +0100 +++ b/Plugin/DefaultAuthorizationParser.cpp Thu Feb 15 16:30:21 2024 +0100 @@ -57,7 +57,20 @@ "^" + tmp + "/(studies|series|instances)(|/)$"); } - bool DefaultAuthorizationParser::IsListOfResources(const std::string& uri) + void DefaultAuthorizationParser::GetSingleResourcePatterns(std::vector& patterns) const + { + patterns.push_back(resourcesPattern_); + patterns.push_back(seriesPattern_); + patterns.push_back(instancesPattern_); + patterns.push_back(osimisViewerSeries_); + patterns.push_back(osimisViewerImages_); + patterns.push_back(osimisViewerStudies_); + patterns.push_back(dicomWebStudies_); + patterns.push_back(dicomWebSeries_); + patterns.push_back(dicomWebInstances_); + } + + bool DefaultAuthorizationParser::IsListOfResources(const std::string& uri) const { if (boost::regex_match(uri, listOfResourcesPattern_)) { diff -r 20c638fa8b07 -r 423531fb1200 Plugin/DefaultAuthorizationParser.h --- a/Plugin/DefaultAuthorizationParser.h Thu Feb 15 12:04:28 2024 +0100 +++ b/Plugin/DefaultAuthorizationParser.h Thu Feb 15 16:30:21 2024 +0100 @@ -28,7 +28,7 @@ class DefaultAuthorizationParser : public AuthorizationParserBase { private: - boost::mutex mutex_; + mutable boost::mutex mutex_; boost::regex resourcesPattern_; boost::regex seriesPattern_; boost::regex instancesPattern_; @@ -52,6 +52,8 @@ const std::string& uri, const std::map& getArguments); - virtual bool IsListOfResources(const std::string& uri); + virtual bool IsListOfResources(const std::string& uri) const; + + virtual void GetSingleResourcePatterns(std::vector& patterns) const; }; } diff -r 20c638fa8b07 -r 423531fb1200 Plugin/DefaultConfiguration.json --- a/Plugin/DefaultConfiguration.json Thu Feb 15 12:04:28 2024 +0100 +++ b/Plugin/DefaultConfiguration.json Thu Feb 15 16:30:21 2024 +0100 @@ -59,12 +59,13 @@ // elemental browsing in OE2 ["post", "^/tools/find$", "all|view"], ["get" , "^/(patients|studies|series|instances)(|/)", "all|view"], - ["get" , "^/(patients|studies|series|instances)/([a-f0-9-]+)$", "all|view"], - ["get" , "^/(patients|studies|series|instances)/([a-f0-9-]+)/(studies|study|series|instances)$", "all|view"], - ["get" , "^/instances/([a-f0-9-]+)/(tags|header)$", "all|view"], ["get" , "^/statistics$", "all|view"], ["get" , "^/changes$", "all|view"], + // single resources patterns (SINGLE_RESOURCE_PATTERNS is an alias for all single resource patterns defined in https://orthanc.uclouvain.be/hg/orthanc-authorization/file/tip/Plugin/DefaultAuthorizationParser.cpp) + // (a user must have access to the route + have an authorized label to access the resource) + ["get" , "SINGLE_RESOURCE_PATTERNS", "all|view"], + // create links to open viewer or download resources ["put", "^/auth/tokens/(viewer-instant-link|meddream-instant-link)$", "all|view"], ["put", "^/auth/tokens/(download-instant-link)$", "all|download"], @@ -91,10 +92,6 @@ // DICOMWeb QIDO-RS ["get" , "^/DICOM_WEB_ROOT/(studies|series|instances)(|/)$", "all|view"], - // DICOMWeb WADO-RS (a user must have access to the route + have an authorized label) - ["get" , "^/DICOM_WEB_ROOT/studies/([.0-9]+)(|/series|/metadata)(|/)$", "all|view"], - ["get" , "^/DICOM_WEB_ROOT/studies/([.0-9]+)/series/([.0-9]+)(|/instances|/rendered|/metadata)(|/)$", "all|view"], - ["get" , "^/DICOM_WEB_ROOT/studies/([.0-9]+)/series/([.0-9]+)/instances/([.0-9]+)(|/|/frames/.*|/rendered|/metadata|/bulk/.*)(|/)$", "all|view"], // modifications/anonymization ["post", "^/(patients|studies|series|instances)/([a-f0-9-]+)/(modify|merge)(.*)$", "all|modify"], @@ -102,7 +99,6 @@ // labels ["get", "^/tools/labels$", "all|view|edit-labels"], - ["get" , "^/(patients|studies|series|instances)/([a-f0-9-]+)/labels$", "all|view"], ["put" , "^/(patients|studies|series|instances)/([a-f0-9-]+)/labels/(.*)$", "all|edit-labels"], ["delete" , "^/(patients|studies|series|instances)/([a-f0-9-]+)/labels/(.*)$", "all|edit-labels"], diff -r 20c638fa8b07 -r 423531fb1200 Plugin/IAuthorizationParser.h --- a/Plugin/IAuthorizationParser.h Thu Feb 15 12:04:28 2024 +0100 +++ b/Plugin/IAuthorizationParser.h Thu Feb 15 16:30:21 2024 +0100 @@ -21,8 +21,10 @@ #include "AccessedResource.h" #include +#include #include #include +#include namespace OrthancPlugins { @@ -43,6 +45,8 @@ const std::string& uri, const std::map& getArguments) = 0; - virtual bool IsListOfResources(const std::string& uri) = 0; + virtual bool IsListOfResources(const std::string& uri) const = 0; + + virtual void GetSingleResourcePatterns(std::vector& patterns) const = 0; }; } diff -r 20c638fa8b07 -r 423531fb1200 Plugin/PermissionParser.cpp --- a/Plugin/PermissionParser.cpp Thu Feb 15 12:04:28 2024 +0100 +++ b/Plugin/PermissionParser.cpp Thu Feb 15 16:30:21 2024 +0100 @@ -70,7 +70,7 @@ { } - void PermissionParser::Add(const Json::Value& configuration) + void PermissionParser::Add(const Json::Value& configuration, const IAuthorizationParser* authorizationParser) { if (configuration.type() != Json::arrayValue) { @@ -85,11 +85,28 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, "Permissions elements should be an array of min size 3."); } - Add(permission[0].asString(), // 0 = HTTP method - permission[1].asString(), // 1 = pattern - permission[2].asString() // 2 = list of | separated permissions (no space) - // 3 = optional comment - ); + if (permission[1].asString() == "SINGLE_RESOURCE_PATTERNS") + { + std::vector singleResourcePatterns; + authorizationParser->GetSingleResourcePatterns(singleResourcePatterns); + + for (std::vector::const_iterator it = singleResourcePatterns.begin(); it != singleResourcePatterns.end(); ++it) + { + Add(permission[0].asString(), // 0 = HTTP method + it->str(), // 1 = pattern + permission[2].asString() // 2 = list of | separated permissions (no space) + // 3 = optional comment + ); + } + } + else + { + Add(permission[0].asString(), // 0 = HTTP method + permission[1].asString(), // 1 = pattern + permission[2].asString() // 2 = list of | separated permissions (no space) + // 3 = optional comment + ); + } } } diff -r 20c638fa8b07 -r 423531fb1200 Plugin/PermissionParser.h --- a/Plugin/PermissionParser.h Thu Feb 15 12:04:28 2024 +0100 +++ b/Plugin/PermissionParser.h Thu Feb 15 16:30:21 2024 +0100 @@ -50,7 +50,7 @@ const std::string& patternRegex, const std::string& permission); - void Add(const Json::Value& configuration); + void Add(const Json::Value& configuration, const IAuthorizationParser* authorizationParser); bool Parse(std::set& permissions, std::string& matchedPattern, diff -r 20c638fa8b07 -r 423531fb1200 Plugin/Plugin.cpp --- a/Plugin/Plugin.cpp Thu Feb 15 12:04:28 2024 +0100 +++ b/Plugin/Plugin.cpp Thu Feb 15 16:30:21 2024 +0100 @@ -32,6 +32,8 @@ // Configuration of the authorization plugin +static bool resourceTokensEnabled_ = false; +static bool userTokensEnabled_ = false; static std::unique_ptr authorizationParser_; static std::unique_ptr authorizationService_; static std::unique_ptr permissionParser_; @@ -121,8 +123,8 @@ if (authorizationParser_->IsListOfResources(uri)) { - granted = false; - return true; // if a user does not have access to all labels, he can not have access to a list of resources + granted = false; // if a user does not have access to all labels, he can not have access to a list of resources + return true; } // Loop over all the accessed resources to ensure access is @@ -365,7 +367,7 @@ // If we get till here, it means that we have a resource token -> check that the resource is accessible //////////////////////////////////////////////////////////////// - if (authorizationParser_.get() != NULL && + if (resourceTokensEnabled_ && authorizationService_.get() != NULL) { // Parse the resources that are accessed through this URI @@ -1137,20 +1139,23 @@ pluginConfiguration.LookupStringValue(urlUserProfile, WEB_SERVICE_USER_PROFILE); } + authorizationParser_.reset(new OrthancPlugins::DefaultAuthorizationParser(factory, dicomWebRoot)); + if (!urlTokenValidation.empty()) { - LOG(WARNING) << "Authorization plugin: url defined for Token Validation: " << urlTokenValidation; - authorizationParser_.reset - (new OrthancPlugins::DefaultAuthorizationParser(factory, dicomWebRoot)); + LOG(WARNING) << "Authorization plugin: url defined for Token Validation: " << urlTokenValidation << ", resource tokens validation is enabled"; + resourceTokensEnabled_ = true; } else { - LOG(WARNING) << "Authorization plugin: no url defined for Token Validation"; + LOG(WARNING) << "Authorization plugin: no url defined for Token Validation, resource tokens validation is disabled"; + resourceTokensEnabled_ = false; } if (!urlUserProfile.empty()) { - LOG(WARNING) << "Authorization plugin: url defined for User Profile: " << urlUserProfile; + LOG(WARNING) << "Authorization plugin: url defined for User Profile: " << urlUserProfile << ", user tokens validation is enabled"; + userTokensEnabled_ = true; static const char* PERMISSIONS = "Permissions"; if (!pluginConfiguration.GetJson().isMember(PERMISSIONS)) @@ -1161,11 +1166,12 @@ permissionParser_.reset (new OrthancPlugins::PermissionParser(dicomWebRoot, oe2Root)); - permissionParser_->Add(pluginConfiguration.GetJson()[PERMISSIONS]); + permissionParser_->Add(pluginConfiguration.GetJson()[PERMISSIONS], authorizationParser_.get()); } else { - LOG(WARNING) << "Authorization plugin: no url defined for User Profile"; + LOG(WARNING) << "Authorization plugin: no url defined for User Profile" << ", user tokens validation is disabled"; + userTokensEnabled_ = false; } if (!urlTokenCreationBase.empty()) @@ -1177,7 +1183,7 @@ LOG(WARNING) << "Authorization plugin: no base url defined for Token Creation"; } - if (authorizationParser_.get() == NULL && permissionParser_.get() == NULL) + if (!resourceTokensEnabled_ && permissionParser_.get() == NULL) { if (hasBasicAuthEnabled) { @@ -1326,7 +1332,7 @@ OrthancPlugins::RegisterRestCallback("/auth/tokens/(.*)", true); } - if (authorizationParser_.get() != NULL || permissionParser_.get() != NULL) + if (resourceTokensEnabled_ || userTokensEnabled_) { if (hasBasicAuthEnabled) {