changeset 109:7381a7674b36

wip: adding labels
author Alain Mazy <am@osimis.io>
date Fri, 18 Aug 2023 12:08:49 +0200
parents 68ce6fd8b22a
children aa56dcf599b9
files CMakeLists.txt Plugin/AccessedResource.cpp Plugin/AccessedResource.h Plugin/AuthorizationParserBase.cpp Plugin/AuthorizationParserBase.h Plugin/AuthorizationWebService.cpp Plugin/AuthorizationWebService.h Plugin/BaseAuthorizationService.h Plugin/CachedAuthorizationService.cpp Plugin/CachedAuthorizationService.h Plugin/DefaultAuthorizationParser.cpp Plugin/IAuthorizationService.h Plugin/OrthancResource.cpp Plugin/OrthancResource.h Plugin/Plugin.cpp Plugin/ResourceHierarchyCache.cpp Plugin/ResourceHierarchyCache.h Resources/Orthanc/CMake/Compiler.cmake Resources/Orthanc/CMake/DownloadOrthancFramework.cmake
diffstat 19 files changed, 332 insertions(+), 96 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Mon Aug 14 10:25:40 2023 +0200
+++ b/CMakeLists.txt	Fri Aug 18 12:08:49 2023 +0200
@@ -25,8 +25,9 @@
   set(ORTHANC_FRAMEWORK_VERSION "mainline")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
 else()
-  set(ORTHANC_FRAMEWORK_VERSION "1.11.3")
-  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+  # TODO: switch to 1.12.2 once available
+  set(ORTHANC_FRAMEWORK_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
 endif()
 
 # Parameters of the build
@@ -170,6 +171,8 @@
   ${PLUGIN_SOURCES}
 )
 
+DefineSourceBasenameForTarget(OrthancAuthorization)
+
 add_dependencies(OrthancAuthorization AutogeneratedTarget)
 
 message("Setting the version of the plugin to ${ORTHANC_PLUGIN_VERSION}")
--- a/Plugin/AccessedResource.cpp	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/AccessedResource.cpp	Fri Aug 18 12:08:49 2023 +0200
@@ -24,15 +24,17 @@
 {
   AccessedResource::AccessedResource(AccessLevel level,
                                      const std::string& orthancId,
-                                     const std::string& dicomUid) :
+                                     const std::string& dicomUid,
+                                     const std::set<std::string>& labels) :
     level_(level),
     orthancId_(orthancId),
-    dicomUid_(dicomUid)
+    dicomUid_(dicomUid),
+    labels_(labels)
   {
     if (level_ == AccessLevel_System &&
-        !dicomUid.empty())
+        (!dicomUid.empty() || !labels.empty()))
     {
-      // The "DICOM UID" makes no sense for custom Orthanc URIs
+      // The "DICOM UID" and labels make no sense for custom Orthanc URIs
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);        
     }
   }
@@ -40,9 +42,11 @@
 
   AccessedResource::AccessedResource(Orthanc::ResourceType level,
                                      const std::string& orthancId,
-                                     const std::string& dicomUid) :
+                                     const std::string& dicomUid,
+                                     const std::set<std::string>& labels) :
     orthancId_(orthancId),
-    dicomUid_(dicomUid)
+    dicomUid_(dicomUid),
+    labels_(labels)
   {
     switch (level)
     {
@@ -79,4 +83,17 @@
       return dicomUid_;
     }
   }
+
+    const std::set<std::string>& AccessedResource::GetLabels() const
+    {
+    if (level_ == AccessLevel_System)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);        
+    }
+    else
+    {
+      return labels_;
+    }
+  }
+
 }
--- a/Plugin/AccessedResource.h	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/AccessedResource.h	Fri Aug 18 12:08:49 2023 +0200
@@ -19,6 +19,7 @@
 #pragma once
 
 #include "Enumerations.h"
+#include <set>
 
 namespace OrthancPlugins
 {
@@ -28,15 +29,19 @@
     AccessLevel    level_;
     std::string    orthancId_;
     std::string    dicomUid_;
+    std::set<std::string> labels_;
 
   public:
     AccessedResource(AccessLevel level,
                      const std::string& orthancId,
-                     const std::string& dicomUid);
+                     const std::string& dicomUid,
+                     const std::set<std::string>& labels
+                     );
 
     AccessedResource(Orthanc::ResourceType level,
                      const std::string& orthancId,
-                     const std::string& dicomUid);
+                     const std::string& dicomUid,
+                     const std::set<std::string>& labels);
 
     AccessLevel GetLevel() const
     {
@@ -49,5 +54,7 @@
     }
 
     const std::string& GetDicomUid() const;
+
+    const std::set<std::string>& GetLabels() const;
   };
 }
--- a/Plugin/AuthorizationParserBase.cpp	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/AuthorizationParserBase.cpp	Fri Aug 18 12:08:49 2023 +0200
@@ -24,13 +24,14 @@
 {
   void AuthorizationParserBase::AddResourceInternal(AccessedResources& target,
                                                     Orthanc::ResourceType level,
-                                                    const std::string& orthancId)
+                                                    const std::string& orthancId,
+                                                    const std::set<std::string>& labels)
   {
     std::string dicomUid;
 
     if (resourceHierarchy_->LookupDicomUid(dicomUid, level, orthancId))
     {
-      target.push_back(AccessedResource(level, orthancId, dicomUid));
+      target.push_back(AccessedResource(level, orthancId, dicomUid, labels));
     }
   }
     
@@ -44,10 +45,19 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }
 
-    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient);
-    AddResourceInternal(target, Orthanc::ResourceType_Study, study);
-    AddResourceInternal(target, Orthanc::ResourceType_Series, series);
-    AddResourceInternal(target, Orthanc::ResourceType_Instance, orthancId);
+    std::set<std::string> labels;
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Patient, patient));
+    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient, labels);
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Study, study));
+    AddResourceInternal(target, Orthanc::ResourceType_Study, study, labels);
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Series, series));
+    AddResourceInternal(target, Orthanc::ResourceType_Series, series, labels);
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Instance, orthancId));
+    AddResourceInternal(target, Orthanc::ResourceType_Instance, orthancId, labels);
   }
 
   
@@ -60,9 +70,16 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }
 
-    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient);
-    AddResourceInternal(target, Orthanc::ResourceType_Study, study);
-    AddResourceInternal(target, Orthanc::ResourceType_Series, orthancId);
+    std::set<std::string> labels;
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Patient, patient));
+    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient, labels);
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Study, study));
+    AddResourceInternal(target, Orthanc::ResourceType_Study, study, labels);
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Series, orthancId));
+    AddResourceInternal(target, Orthanc::ResourceType_Series, orthancId, labels);
   }
 
   
@@ -75,15 +92,23 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }
 
-    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient);
-    AddResourceInternal(target, Orthanc::ResourceType_Study, orthancId);
+    std::set<std::string> labels;
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Patient, patient));
+    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient, labels);
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Study, orthancId));
+    AddResourceInternal(target, Orthanc::ResourceType_Study, orthancId, labels);
   }
 
   
   void AuthorizationParserBase::AddOrthancPatient(AccessedResources& target,
                                                   const std::string& orthancId)
   {
-    AddResourceInternal(target, Orthanc::ResourceType_Patient, orthancId);
+    std::set<std::string> labels;
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Patient, orthancId));
+    AddResourceInternal(target, Orthanc::ResourceType_Patient, orthancId, labels);
   }
 
   
@@ -98,8 +123,13 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }
 
-    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient);
-    target.push_back(AccessedResource(Orthanc::ResourceType_Study, study, studyDicomUid));
+    std::set<std::string> labels;
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Patient, patient));
+    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient, labels);
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Study, study));
+    target.push_back(AccessedResource(Orthanc::ResourceType_Study, study, studyDicomUid, labels));
   }
 
   void AuthorizationParserBase::AddDicomPatient(AccessedResources& target,
@@ -112,7 +142,10 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }
 
-    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient);
+    std::set<std::string> labels;
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Patient, patient));
+    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient, labels);
   }
   
   void AuthorizationParserBase::AddDicomSeries(AccessedResources& target,
@@ -126,8 +159,12 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }
 
+    std::set<std::string> labels;
+
     AddDicomStudy(target, studyDicomUid);
-    target.push_back(AccessedResource(Orthanc::ResourceType_Series, series, seriesDicomUid));
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Series, series));
+    target.push_back(AccessedResource(Orthanc::ResourceType_Series, series, seriesDicomUid, labels));
   }
 
   
@@ -144,8 +181,12 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }
 
+    std::set<std::string> labels;
+
     AddDicomSeries(target, studyDicomUid, seriesDicomUid);
-    target.push_back(AccessedResource(Orthanc::ResourceType_Instance, instance, instanceDicomUid));
+
+    resourceHierarchy_->GetLabels(labels, OrthancResource(Orthanc::ResourceType_Instance, instance));
+    target.push_back(AccessedResource(Orthanc::ResourceType_Instance, instance, instanceDicomUid, labels));
   }
 
   
--- a/Plugin/AuthorizationParserBase.h	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/AuthorizationParserBase.h	Fri Aug 18 12:08:49 2023 +0200
@@ -37,7 +37,8 @@
 
     void AddResourceInternal(AccessedResources& target,
                              Orthanc::ResourceType level,
-                             const std::string& orthancId);
+                             const std::string& orthancId,
+                             const std::set<std::string>& labels);
     
   protected:
     void AddOrthancInstance(AccessedResources& target,
--- a/Plugin/AuthorizationWebService.cpp	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/AuthorizationWebService.cpp	Fri Aug 18 12:08:49 2023 +0200
@@ -24,12 +24,16 @@
 #include <Toolbox.h>
 #include <HttpClient.h>
 #include <algorithm>
+#include "SerializationToolbox.h"
 
 namespace OrthancPlugins
 {
   static const char* GRANTED = "granted";
   static const char* VALIDITY = "validity";
   static const char* PERMISSIONS = "permissions";
+  static const char* AUTHORIZED_LABELS = "authorized-labels";
+  static const char* FORBIDDEN_LABELS = "forbidden-labels";
+  static const char* USER_NAME = "name";
 
 
   bool AuthorizationWebService::IsGrantedInternal(unsigned int& validity,
@@ -89,6 +93,11 @@
       body["server-id"] = Json::nullValue;
     }
 
+    if (access.GetLabels().size() > 0)
+    {
+      Orthanc::SerializationToolbox::WriteSetOfStrings(body, access.GetLabels(), "labels");
+    }
+
     Orthanc::WebServiceParameters authWebservice;
 
     if (!username_.empty())
@@ -315,7 +324,7 @@
 
 
   bool AuthorizationWebService::GetUserProfileInternal(unsigned int& validity,
-                                                       Json::Value& profile /* out */,
+                                                       UserProfile& profile /* out */,
                                                        const Token* token,
                                                        const std::string& tokenValue)
   {
@@ -361,15 +370,52 @@
       authClient.AddHeader("Expect", "");
       authClient.SetTimeout(10);
 
-      authClient.ApplyAndThrowException(profile);
+      Json::Value jsonProfile;
+      authClient.ApplyAndThrowException(jsonProfile);
 
-      if (profile.isMember("validity"))
+      if (jsonProfile.type() != Json::objectValue ||
+          !jsonProfile.isMember(PERMISSIONS) ||
+          !jsonProfile.isMember(VALIDITY) ||
+          !jsonProfile.isMember(AUTHORIZED_LABELS) ||
+          !jsonProfile.isMember(FORBIDDEN_LABELS) ||
+          !jsonProfile.isMember(USER_NAME) ||
+          jsonProfile[PERMISSIONS].type() != Json::arrayValue ||
+          jsonProfile[AUTHORIZED_LABELS].type() != Json::arrayValue ||
+          jsonProfile[FORBIDDEN_LABELS].type() != Json::arrayValue ||
+          jsonProfile[VALIDITY].type() != Json::intValue ||
+          jsonProfile[USER_NAME].type() != Json::stringValue)
       {
-        validity = profile["validity"].asInt();
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                        "Syntax error in the result of the Auth Web service, the format of the UserProfile is invalid");
+      }
+
+      validity = jsonProfile[VALIDITY].asUInt();
+      
+      profile.name = jsonProfile[USER_NAME].asString();
+      
+      for (Json::ArrayIndex i = 0; i < jsonProfile[PERMISSIONS].size(); ++i)
+      {
+        profile.permissions.insert(jsonProfile[PERMISSIONS][i].asString());
+      }
+      for (Json::ArrayIndex i = 0; i < jsonProfile[AUTHORIZED_LABELS].size(); ++i)
+      {
+        profile.authorizedLabels.insert(jsonProfile[AUTHORIZED_LABELS][i].asString());
       }
-      else
+      for (Json::ArrayIndex i = 0; i < jsonProfile[FORBIDDEN_LABELS].size(); ++i)
+      {
+        profile.forbiddenLabels.insert(jsonProfile[FORBIDDEN_LABELS][i].asString());
+      }
+
+      if (profile.authorizedLabels.size() > 0 && profile.forbiddenLabels.size() > 0)
       {
-        validity = 0;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                        "Syntax error in the result of the Auth Web service, the UserProfile can not contain both authorized and forbidden labels");
+      }
+
+      if (profile.authorizedLabels.size() == 0 && profile.forbiddenLabels.size() == 0)
+      {
+        LOG(WARNING) << "The UserProfile does not contain any authorized or forbidden labels, assuming the user has access to all data (equivalent to \"authorized_labels\": [\"*\"]) !";
+        profile.authorizedLabels.insert("*");
       }
 
       return true;
@@ -385,27 +431,15 @@
                                                           const Token* token,
                                                           const std::string& tokenValue)
   {
-    Json::Value profile;
+    UserProfile profile;
 
 
     if (GetUserProfileInternal(validity, profile, token, tokenValue))
     {
-      if (profile.type() != Json::objectValue ||
-          !profile.isMember(PERMISSIONS) ||
-          !profile.isMember(VALIDITY) ||
-          profile[PERMISSIONS].type() != Json::arrayValue ||
-          profile[VALIDITY].type() != Json::intValue)
+      std::set<std::string>& permissions = profile.permissions;
+      for (std::set<std::string>::const_iterator it = permissions.begin(); it != permissions.end(); ++it)
       {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
-                                        "Syntax error in the result of the Web service");
-      }
-
-      validity = profile[VALIDITY].asUInt();
-
-      Json::Value& permissions = profile[PERMISSIONS];
-      for (Json::ArrayIndex i = 0; i < permissions.size(); ++i)
-      {
-        if (permission == permissions[i].asString())
+        if (permission == *it)
         {
           return true;
         }
--- a/Plugin/AuthorizationWebService.h	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/AuthorizationWebService.h	Fri Aug 18 12:08:49 2023 +0200
@@ -42,7 +42,7 @@
                            const std::string& tokenValue) ORTHANC_OVERRIDE;
     
     virtual bool GetUserProfileInternal(unsigned int& validity,
-                                Json::Value& profile /* out */,
+                                UserProfile& profile /* out */,
                                 const Token* token,
                                 const std::string& tokenValue) ORTHANC_OVERRIDE;
 
--- a/Plugin/BaseAuthorizationService.h	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/BaseAuthorizationService.h	Fri Aug 18 12:08:49 2023 +0200
@@ -36,7 +36,7 @@
                                    const std::string& tokenValue) = 0;
     
     virtual bool GetUserProfileInternal(unsigned int& validity,
-                                        Json::Value& profile /* out */,
+                                        UserProfile& profile /* out */,
                                         const Token* token,
                                         const std::string& tokenValue) = 0;
 
@@ -67,7 +67,7 @@
     }
 
     virtual bool GetUserProfile(unsigned int& validity,
-                                Json::Value& profile /* out */,
+                                UserProfile& profile /* out */,
                                 const Token& token,
                                 const std::string& tokenValue)
     {
@@ -75,7 +75,7 @@
     }
 
     virtual bool GetAnonymousUserProfile(unsigned int& validity /* out */,
-                                         Json::Value& profile /* out */)
+                                         UserProfile& profile /* out */)
     {
       return GetUserProfileInternal(validity, profile, NULL, "");
     }
--- a/Plugin/CachedAuthorizationService.cpp	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/CachedAuthorizationService.cpp	Fri Aug 18 12:08:49 2023 +0200
@@ -113,7 +113,7 @@
 
   
   bool CachedAuthorizationService::GetUserProfileInternal(unsigned int& validity,
-                                                          Json::Value& profile /* out */,
+                                                          UserProfile& profile /* out */,
                                                           const Token* token,
                                                           const std::string& tokenValue)
   {
--- a/Plugin/CachedAuthorizationService.h	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/CachedAuthorizationService.h	Fri Aug 18 12:08:49 2023 +0200
@@ -52,7 +52,7 @@
                                    const std::string& tokenValue) ORTHANC_OVERRIDE;
     
     virtual bool GetUserProfileInternal(unsigned int& validity,
-                                        Json::Value& profile /* out */,
+                                        UserProfile& profile /* out */,
                                         const Token* token,
                                         const std::string& tokenValue) ORTHANC_OVERRIDE;
 
--- a/Plugin/DefaultAuthorizationParser.cpp	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/DefaultAuthorizationParser.cpp	Fri Aug 18 12:08:49 2023 +0200
@@ -192,7 +192,9 @@
       s = s.substr(0, s.length() - 1);
     }
         
-    target.push_back(AccessedResource(AccessLevel_System, s, ""));
+    std::set<std::string> labels;
+
+    target.push_back(AccessedResource(AccessLevel_System, s, "", labels));
     return true;
   }
 }
--- a/Plugin/IAuthorizationService.h	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/IAuthorizationService.h	Fri Aug 18 12:08:49 2023 +0200
@@ -24,6 +24,7 @@
 #include <orthanc/OrthancCPlugin.h>
 #include <boost/noncopyable.hpp>
 #include <json/json.h>
+#include <set>
 
 namespace OrthancPlugins
 {
@@ -52,6 +53,14 @@
       std::string tokenType;
     };
 
+    struct UserProfile
+    {
+      std::string name;
+      std::set<std::string> permissions;
+      std::set<std::string> authorizedLabels;
+      std::set<std::string> forbiddenLabels;
+    };
+
     virtual ~IAuthorizationService()
     {
     }
@@ -67,12 +76,12 @@
                                           const AccessedResource& access) = 0;
 
     virtual bool GetUserProfile(unsigned int& validity /* out */,
-                                Json::Value& profile /* out */,
+                                UserProfile& profile /* out */,
                                 const Token& token,
                                 const std::string& tokenValue) = 0;
 
     virtual bool GetAnonymousUserProfile(unsigned int& validity /* out */,
-                                         Json::Value& profile /* out */) = 0;
+                                         UserProfile& profile /* out */) = 0;
 
     virtual bool HasUserPermission(unsigned int& validity /* out */,
                                    const std::set<std::string>& anyOfPermissions,
--- a/Plugin/OrthancResource.cpp	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/OrthancResource.cpp	Fri Aug 18 12:08:49 2023 +0200
@@ -20,6 +20,8 @@
 
 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
 
+static const char* LABELS_KEY = "Labels";
+
 namespace OrthancPlugins
 {
   void OrthancResource::GetDicomUidInternal(std::string& result,
@@ -157,7 +159,8 @@
     
   bool OrthancResource::GetHierarchy(std::string& dicomUid /* out */,
                                      OrthancResource& parent /* out */,
-                                     std::list<OrthancResource>& children /* out */) const
+                                     std::list<OrthancResource>& children /* out */,
+                                     std::set<std::string>& labels) const
   {
     Json::Value content;
         
@@ -235,7 +238,24 @@
         children.push_back(OrthancResource(childrenType, child.asString()));
       }
     }
-        
+
+    labels.clear();
+    if (content.isMember(LABELS_KEY) || 
+        content[LABELS_KEY].type() != Json::arrayValue)
+    {
+      for (Json::Value::ArrayIndex i = 0; i < content[LABELS_KEY].size(); i++)
+      {
+        const Json::Value& label = content[LABELS_KEY][i];
+
+        if (label.type() != Json::stringValue)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+        }
+
+        labels.insert(label.asString());
+      }
+    }
+
     return true;
   }
 
--- a/Plugin/OrthancResource.h	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/OrthancResource.h	Fri Aug 18 12:08:49 2023 +0200
@@ -66,7 +66,8 @@
     
     bool GetHierarchy(std::string& dicomUid /* out */,
                       OrthancResource& parent /* out */,
-                      std::list<OrthancResource>& children /* out */) const;
+                      std::list<OrthancResource>& children /* out */,
+                      std::set<std::string>& labels /* out */) const;
 
     static bool LookupOrthancId(std::string& result,
                                 Orthanc::ResourceType level,
--- a/Plugin/Plugin.cpp	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/Plugin.cpp	Fri Aug 18 12:08:49 2023 +0200
@@ -22,7 +22,6 @@
 #include "AuthorizationWebService.h"
 #include "PermissionParser.h"
 #include "MemoryCache.h"
-
 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
 
 #include <Compatibility.h>  // For std::unique_ptr<>
@@ -88,9 +87,11 @@
   {
     unsigned int validity;  // ignored
 
+    // Allow GET accesses to unchecked resources/folders (usually static resources)
+    ////////////////////////////////////////////////////////////////
+
     if (method == OrthancPluginHttpMethod_Get)
     {
-      // Allow GET accesses to static resources
       if (uncheckedResources_.find(uri) != uncheckedResources_.end())
       {
         return 1;
@@ -106,6 +107,9 @@
       }
     }
 
+    // Extract auth tokens from headers and url get arguments
+    ////////////////////////////////////////////////////////////////
+
     OrthancPlugins::AssociativeArray headers(headersCount, headersKeys, headersValues, false);
     OrthancPlugins::AssociativeArray getArguments(getArgumentsCount, getArgumentsKeys, getArgumentsValues, true);
 
@@ -136,10 +140,11 @@
       }
     }
 
-    // check if the user permissions grants him access
+    // Based on the tokens, check if the user has access based on its permissions and the mapping between urls and permissions
+    ////////////////////////////////////////////////////////////////
+
     if (permissionParser_.get() != NULL &&
       authorizationService_.get() != NULL) 
-      // && uncheckedLevels_.find(OrthancPlugins::AccessLevel_UserPermissions) == uncheckedLevels_.end())
     {
       std::set<std::string> requiredPermissions;
       std::string matchedPattern;
@@ -168,6 +173,7 @@
             LOG(INFO) << msg;
             if (authorizationService_->HasUserPermission(validity, requiredPermissions, authTokens[i].GetToken(), authTokens[i].GetValue()))
             {
+              // TODO: check labels permissions
               LOG(INFO) << msg << " -> granted";
               return 1;
             }
@@ -179,6 +185,10 @@
         }
       }
     }
+
+
+    // 
+
     if (authorizationParser_.get() != NULL &&
         authorizationService_.get() != NULL)
     {
@@ -508,7 +518,7 @@
     for (std::set<OrthancPlugins::Token>::const_iterator
             token = tokens_.begin(); token != tokens_.end(); ++token)
     {
-      Json::Value profile;
+      OrthancPlugins::IAuthorizationService::UserProfile profile;
 
       std::string value;
 
@@ -532,7 +542,23 @@
         unsigned int validity; // not used
         if (authorizationService_->GetUserProfile(validity, profile, *token, value))
         {
-          OrthancPlugins::AnswerJson(profile, output);
+          Json::Value jsonProfile;
+          jsonProfile["name"] = profile.name;
+          jsonProfile["permissions"] = Json::arrayValue;
+          for (std::set<std::string>::const_iterator it = profile.permissions.begin(); it != profile.permissions.end(); ++it)
+          {
+            jsonProfile["permissions"].append(*it);
+          }
+          for (std::set<std::string>::const_iterator it = profile.authorizedLabels.begin(); it != profile.authorizedLabels.end(); ++it)
+          {
+            jsonProfile["authorized-labels"].append(*it);
+          }
+          for (std::set<std::string>::const_iterator it = profile.forbiddenLabels.begin(); it != profile.forbiddenLabels.end(); ++it)
+          {
+            jsonProfile["forbidden-labels"].append(*it);
+          }
+
+          OrthancPlugins::AnswerJson(jsonProfile, output);
           return;
         }
       }
--- a/Plugin/ResourceHierarchyCache.cpp	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/ResourceHierarchyCache.cpp	Fri Aug 18 12:08:49 2023 +0200
@@ -22,6 +22,7 @@
 #include <OrthancException.h>
 
 #include <boost/lexical_cast.hpp>
+#include <Toolbox.h>
 
 namespace OrthancPlugins
 {
@@ -43,6 +44,63 @@
     cache_->Store(ComputeKey(child), parent.GetIdentifier(), 0 /* no expiration */);
   }
 
+  void ResourceHierarchyCache::GetLabels(std::set<std::string>& labels,
+                                         const OrthancResource& resource)
+  {
+    labels.clear();
+    
+    std::string key = ComputeKey(resource);
+    
+    std::string serializedLabels;
+    if (!labels_->Retrieve(serializedLabels, key))
+    {
+      // The labels were not already stored in the cache or they have expired
+      OrthancResource parent;
+      UpdateResourceFromOrthanc(parent, labels, resource);
+    }
+    else
+    {
+      Orthanc::Toolbox::SplitString(labels, serializedLabels, ',');
+    }
+  }
+
+
+  void ResourceHierarchyCache::UpdateResourceFromOrthanc(OrthancResource& parent,
+                                                         std::set<std::string>& labels,
+                                                         const OrthancResource& resource)
+  {
+    std::string key = ComputeKey(resource);
+
+    // Not in the cache, reading the resource from the Orthanc store
+    std::string dicomUid;
+    std::list<OrthancResource> children;
+
+    if (!resource.GetHierarchy(dicomUid, parent, children, labels))
+    {
+      // The resource is non-existing (*)
+      return;
+    }
+
+    orthancToDicom_->Store(key, dicomUid, 0 /* no expiration */);
+    dicomToOrthanc_->Store(ComputeKey(resource.GetLevel(), dicomUid),
+                           resource.GetIdentifier(), 0 /* no expiration */);
+    std::string serializedLabels;
+    Orthanc::Toolbox::JoinStrings(serializedLabels, labels, ",");
+    labels_->Store(key, serializedLabels, 60);
+
+    for (std::list<OrthancResource>::const_iterator
+           it = children.begin(); it != children.end(); ++it)
+    {
+      // Cache the relation of the resource with its children
+      LinkParent(*it, resource);
+    }
+
+    if (parent.IsValid())
+    {
+      LinkParent(resource, parent);
+    }
+  }
+
 
   bool ResourceHierarchyCache::LookupParent(std::string& target,
                                             const OrthancResource& resource)
@@ -55,31 +113,12 @@
       return true;
     }
 
-    // Not in the cache, reading the resource from the Orthanc store
-    std::string dicomUid;
     OrthancResource parent;
-    std::list<OrthancResource> children;
-
-    if (!resource.GetHierarchy(dicomUid, parent, children))
-    {
-      // The resource is non-existing (*)
-      return false;
-    }
-
-    orthancToDicom_->Store(key, dicomUid, 0 /* no expiration */);
-    dicomToOrthanc_->Store(ComputeKey(resource.GetLevel(), dicomUid),
-                           resource.GetIdentifier(), 0 /* no expiration */);
-
-    for (std::list<OrthancResource>::const_iterator
-           it = children.begin(); it != children.end(); ++it)
-    {
-      // Cache the relation of the resource with its children
-      LinkParent(*it, resource);
-    }
+    std::set<std::string> labels;
+    UpdateResourceFromOrthanc(parent, labels, resource);
 
     if (parent.IsValid())
     {
-      LinkParent(resource, parent);
       target = parent.GetIdentifier();
       return true;
     }
@@ -95,7 +134,8 @@
   ResourceHierarchyCache::ResourceHierarchyCache(ICacheFactory& factory) :
     cache_(factory.Create()),
     orthancToDicom_(factory.Create()),
-    dicomToOrthanc_(factory.Create())
+    dicomToOrthanc_(factory.Create()),
+    labels_(factory.Create())
   {
     if (cache_.get() == NULL)
     {
@@ -113,6 +153,7 @@
     std::string key = ComputeKey(level, identifier);
     cache_->Invalidate(key);
     orthancToDicom_->Invalidate(key);
+    labels_->Invalidate(key);
   }
 
 
--- a/Plugin/ResourceHierarchyCache.h	Mon Aug 14 10:25:40 2023 +0200
+++ b/Plugin/ResourceHierarchyCache.h	Fri Aug 18 12:08:49 2023 +0200
@@ -38,6 +38,7 @@
     std::unique_ptr<ICache>   cache_;   // Maps resources to their parents
     std::unique_ptr<ICache>   orthancToDicom_;
     std::unique_ptr<ICache>   dicomToOrthanc_;
+    std::unique_ptr<ICache>   labels_;
 
     std::string ComputeKey(Orthanc::ResourceType level,
                            const std::string& identifier) const;
@@ -60,6 +61,10 @@
       return LookupParent(target, OrthancResource(level, identifier));
     }
 
+    void UpdateResourceFromOrthanc(OrthancResource& parent,
+                                   std::set<std::string>& labels,
+                                   const OrthancResource& resource);
+
   public:
     explicit ResourceHierarchyCache(ICacheFactory& factory);
 
@@ -89,6 +94,9 @@
                          Orthanc::ResourceType level,
                          const std::string& dicomUid);
 
+    void GetLabels(std::set<std::string>& labels,
+                   const OrthancResource& resource);
+
 #if BUILD_UNIT_TESTS == 1
     FRIEND_TEST(DefaultAuthorizationParser, Parse);
   protected:
--- a/Resources/Orthanc/CMake/Compiler.cmake	Mon Aug 14 10:25:40 2023 +0200
+++ b/Resources/Orthanc/CMake/Compiler.cmake	Fri Aug 18 12:08:49 2023 +0200
@@ -1,8 +1,8 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2022 Osimis S.A., Belgium
-# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public License
@@ -237,7 +237,8 @@
 
 
 if (DEFINED ENABLE_PROFILING AND ENABLE_PROFILING)
-  if (CMAKE_COMPILER_IS_GNUCXX)
+  if (CMAKE_COMPILER_IS_GNUCXX OR
+      CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
     set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg")
     set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
@@ -262,3 +263,24 @@
   # preceding batches. https://cmake.org/Bug/view.php?id=14874
   set(CMAKE_CXX_ARCHIVE_APPEND "<CMAKE_AR> <LINK_FLAGS> q <TARGET> <OBJECTS>")
 endif()
+
+
+# This function defines macro "__ORTHANC_FILE__" as a replacement to
+# macro "__FILE__", as the latter leaks the full path of the source
+# files in the binaries
+# https://stackoverflow.com/questions/8487986/file-macro-shows-full-path
+# https://twitter.com/wget42/status/1676877802375634944?s=20
+function(DefineSourceBasenameForTarget targetname)
+  # Microsoft Visual Studio is extremely slow if using
+  # "set_property()", we only enable this feature for gcc and clang
+  if (CMAKE_COMPILER_IS_GNUCXX OR
+      CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+    get_target_property(source_files "${targetname}" SOURCES)
+    foreach(sourcefile ${source_files})
+      get_filename_component(basename "${sourcefile}" NAME)
+      set_property(
+        SOURCE "${sourcefile}" APPEND
+        PROPERTY COMPILE_DEFINITIONS "__ORTHANC_FILE__=\"${basename}\"")
+    endforeach()
+  endif()
+endfunction()
--- a/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake	Mon Aug 14 10:25:40 2023 +0200
+++ b/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake	Fri Aug 18 12:08:49 2023 +0200
@@ -1,8 +1,8 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2022 Osimis S.A., Belgium
-# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public License
@@ -154,6 +154,10 @@
         set(ORTHANC_FRAMEWORK_MD5 "ede3de356493a8868545f8cb4b8bc8b5")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.3")
         set(ORTHANC_FRAMEWORK_MD5 "f941c0f5771db7616e7b7961026a60e2")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.0")
+        set(ORTHANC_FRAMEWORK_MD5 "d32a0cde03b6eb603d8dd2b33d38bf1b")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.1")
+        set(ORTHANC_FRAMEWORK_MD5 "8a435140efc8ff4a01d8242f092f21de")
 
       # Below this point are development snapshots that were used to
       # release some plugin, before an official release of the Orthanc
@@ -313,7 +317,7 @@
   else()
     # Default case: Download from the official Web site
     set(ORTHANC_FRAMEMORK_FILENAME Orthanc-${ORTHANC_FRAMEWORK_VERSION}.tar.gz)
-    set(ORTHANC_FRAMEWORK_URL "http://orthanc.osimis.io/ThirdPartyDownloads/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}")
+    set(ORTHANC_FRAMEWORK_URL "https://orthanc.uclouvain.be/third-party-downloads/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}")
   endif()
 
   set(ORTHANC_FRAMEWORK_ARCHIVE "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${ORTHANC_FRAMEMORK_FILENAME}")