Mercurial > hg > orthanc-authorization
changeset 214:a45ba8f12791
filter returned Labels
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Tue, 25 Feb 2025 19:19:11 +0100 (3 months ago) |
parents | 93eb6ce17f6d |
children | 56aef13ad10c |
files | NEWS Plugin/Plugin.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h |
diffstat | 4 files changed, 164 insertions(+), 13 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Mon Feb 24 11:51:56 2025 +0100 +++ b/NEWS Tue Feb 25 19:19:11 2025 +0100 @@ -1,6 +1,14 @@ Pending changes in the mainline =============================== +* The plugin now filters out all unauthorized labels from the "Labels" fields + in the responses of these API routes: + - /tools/find + - /studies/{id} & similar routes + - /studies/{id}/series & similar routes + - /series/{id}/study & similar routes + - /series/{id}/labels & similar routes + In the past, this was only done in /tools/labels * Allow using the auth-plugin together with "AuthenticationEnabled": true. https://discourse.orthanc-server.org/t/user-based-access-control-with-label-based-resource-access/5454 * Added a default permission for /auth/tokens/volview-viewer-publication
--- a/Plugin/Plugin.cpp Mon Feb 24 11:51:56 2025 +0100 +++ b/Plugin/Plugin.cpp Tue Feb 25 19:19:11 2025 +0100 @@ -687,6 +687,45 @@ } } +void FilterAuthorizedLabels(Json::Value& labels, const OrthancPlugins::IAuthorizationService::UserProfile& profile) +{ + if (HasAccessToAllLabels(profile)) + { + return; + } + else + { + std::set<std::string> inLabelsSet; + std::set<std::string> outLabelsSet; + + Orthanc::SerializationToolbox::ReadSetOfStrings(inLabelsSet, labels); + + Orthanc::Toolbox::GetIntersection(outLabelsSet, inLabelsSet, profile.authorizedLabels); + + Orthanc::SerializationToolbox::WriteSetOfStrings(labels, outLabelsSet); + } +} + + +void FilterLabelsInResourceObject(Json::Value& resource, const OrthancPlugins::IAuthorizationService::UserProfile& profile) +{ + if (resource.isMember("Labels")) + { + FilterAuthorizedLabels(resource["Labels"], profile); + } +} + + +void FilterLabelsInResourceArray(Json::Value& resources, const OrthancPlugins::IAuthorizationService::UserProfile& profile) +{ + for (Json::ArrayIndex i = 0; i < resources.size(); ++i) + { + if (resources[i].isObject()) + { + FilterLabelsInResourceObject(resources[i], profile); + } + } +} void ToolsFindOrCountResources(OrthancPluginRestOutput* output, @@ -798,6 +837,7 @@ if (OrthancPlugins::RestApiPost(result, nativeUrl, query, false)) { + FilterLabelsInResourceArray(result, profile); OrthancPlugins::AnswerJson(result, output); } @@ -816,7 +856,6 @@ throw; } } - } void ToolsFind(OrthancPluginRestOutput* output, @@ -847,7 +886,7 @@ } else { - // The filtering to this route is performed by this plugin as it is done for any other route before we get here. + // The filtering to this route is performed by this plugin as it is done for any other route before we get here // If the logged in user has restrictions on the labels he can access, modify the tools/labels response before answering OrthancPlugins::IAuthorizationService::UserProfile profile; @@ -863,19 +902,9 @@ Json::Value jsonLabels; if (OrthancPlugins::RestApiGet(jsonLabels, "/tools/labels", false)) { - std::set<std::string> allLabels; - Orthanc::SerializationToolbox::ReadSetOfStrings(allLabels, jsonLabels); - - if (!HasAccessToAllLabels(profile)) - { - std::set<std::string> authorizedLabels; - - Orthanc::Toolbox::GetIntersection(authorizedLabels, allLabels, profile.authorizedLabels); - Orthanc::SerializationToolbox::WriteSetOfStrings(jsonLabels, authorizedLabels); - } + FilterAuthorizedLabels(jsonLabels, profile); OrthancPlugins::AnswerJson(jsonLabels, output); } - } else { @@ -897,6 +926,73 @@ } } +typedef void (*JsonLabelsFilter) (Json::Value& labels, + const OrthancPlugins::IAuthorizationService::UserProfile& profile); + + +// calls the core api and filter the "Labels" in the response +void FilterLabelsFromGetCoreUrl(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request, + JsonLabelsFilter jsonLabelsFilter) +{ + OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context, output, "GET"); + } + else + { + // The filtering to this route is performed by this plugin as it is done for any other route before we get here so we don't care about authorization here. + + // If the logged in user has restrictions on the labels he can access, modify the "Labels" field in the response before answering + OrthancPlugins::IAuthorizationService::UserProfile profile; + GetUserProfileInternal(profile, request); + + Json::Value response; + + OrthancPlugins::HttpHeaders headers; + OrthancPlugins::GetHttpHeaders(headers, request); + std::string getArguments; + OrthancPlugins::SerializeGetArguments(getArguments, request); + + std::string coreUrl = url; + if (!getArguments.empty()) + { + coreUrl += "?" + getArguments; + } + + if (OrthancPlugins::RestApiGet(response, coreUrl, headers, false)) + { + jsonLabelsFilter(response, profile); + OrthancPlugins::AnswerJson(response, output); + } + } +} + + +void FilterLabelsFromSingleResource(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + FilterLabelsFromGetCoreUrl(output, url, request, FilterLabelsInResourceObject); +} + +void FilterLabelsFromResourceList(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + FilterLabelsFromGetCoreUrl(output, url, request, FilterLabelsInResourceArray); +} + +void FilterLabelsFromResourceLabels(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + FilterLabelsFromGetCoreUrl(output, url, request, FilterAuthorizedLabels); +} + void CreateToken(OrthancPluginRestOutput* output, const char* /*url*/, @@ -1532,6 +1628,29 @@ OrthancPlugins::RegisterRestCallback<ToolsLabels>("/tools/labels", true); OrthancPlugins::RegisterRestCallback<AuthSettingsRoles>("/auth/settings/roles", true); OrthancPlugins::RegisterRestCallback<GetPermissionList>("/auth/settings/permissions", true); + + OrthancPlugins::RegisterRestCallback<FilterLabelsFromSingleResource>("/instances/([^/]*)", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromSingleResource>("/series/([^/]*)", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromSingleResource>("/studies/([^/]*)", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromSingleResource>("/patients/([^/]*)", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromSingleResource>("/instances/([^/]*)/patient", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromSingleResource>("/instances/([^/]*)/study", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromSingleResource>("/instances/([^/]*)/series", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromSingleResource>("/series/([^/]*)/patient", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromSingleResource>("/series/([^/]*)/study", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromSingleResource>("/studies/([^/]*)/patient", true); + + OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceLabels>("/instances/([^/]*)/labels", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceLabels>("/series/([^/]*)/labels", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceLabels>("/studies/([^/]*)/labels", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceLabels>("/patients/([^/]*)/labels", true); + + OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceList>("/series/([^/]*)/instances", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceList>("/studies/([^/]*)/instances", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceList>("/studies/([^/]*)/series", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceList>("/patients/([^/]*)/instances", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceList>("/patients/([^/]*)/series", true); + OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceList>("/patients/([^/]*)/studies", true); } if (!urlTokenCreationBase.empty())
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Mon Feb 24 11:51:56 2025 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Feb 25 19:19:11 2025 +0100 @@ -26,6 +26,7 @@ #include <boost/algorithm/string/predicate.hpp> #include <boost/move/unique_ptr.hpp> #include <boost/thread.hpp> +#include <boost/algorithm/string/join.hpp> #include <json/reader.h> @@ -4077,6 +4078,26 @@ } } + void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request) + { + output.clear(); + std::vector<std::string> arguments; + for (uint32_t i = 0; i < request->getCount; ++i) + { + if (request->getValues[i] && strlen(request->getValues[i]) > 0) + { + arguments.push_back(std::string(request->getKeys[i]) + "=" + std::string(request->getValues[i])); + } + else + { + arguments.push_back(std::string(request->getKeys[i])); + } + } + + output = boost::algorithm::join(arguments, "&"); + } + + #if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4) static void SetPluginProperty(const std::string& pluginIdentifier, _OrthancPluginProperty property,
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Mon Feb 24 11:51:56 2025 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Feb 25 19:19:11 2025 +0100 @@ -1399,6 +1399,9 @@ // helper method to convert Http headers from the plugin SDK to a std::map void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request); +// helper method to re-serialize the get arguments from the SDK into a string +void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request); + #if HAS_ORTHANC_PLUGIN_WEBDAV == 1 class IWebDavCollection : public boost::noncopyable {