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
   {