changeset 236:eb58d8793673

/dicom-web/studies: complement query with the StudyInstanceUIDs from the resource token
author Alain Mazy <am@orthanc.team>
date Mon, 26 May 2025 18:53:20 +0200
parents f970d8d7012b
children 855640287178
files NEWS Plugin/AuthorizationWebService.cpp Plugin/IAuthorizationService.h Plugin/Plugin.cpp
diffstat 4 files changed, 61 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri May 16 11:57:35 2025 +0200
+++ b/NEWS	Mon May 26 18:53:20 2025 +0200
@@ -8,6 +8,13 @@
 * New default permissions to Q&R remote modalities
 * Maintenance: Use Orthanc SDK 1.12.4 by default to benefit from more detailed logging.
 * Fix default permission for /dicom-web/servers/../stow
+* When calling /dicom-web/studies with a resource token when no StudyInstanceUID 
+  is specified in the query args, the plugin now adds a filter on StudyInstanceUID=X|Y where
+  X & Y are the StudyInstanceUIDs of the resource token.  
+  This will only work if the authorization service returns a "resources" field to the /tokens/decode route.  
+  This notably prevents OHIF to display errors when requesting 
+  prior studies while still preserving the security since only the authorized resources are returned.
+
 
 2025-04-29 - v 0.9.2
 ====================
--- a/Plugin/AuthorizationWebService.cpp	Fri May 16 11:57:35 2025 +0200
+++ b/Plugin/AuthorizationWebService.cpp	Mon May 26 18:53:20 2025 +0200
@@ -221,6 +221,24 @@
         response.tokenType = tokenResponse["token-type"].asString();
       }
 
+      // LOG(INFO) << tokenResponse.toStyledString();
+      
+      if (tokenResponse.isMember("resources") && tokenResponse["resources"].isArray())
+      {
+        for (Json::ArrayIndex i = 0; i < tokenResponse["resources"].size(); ++i)
+        {
+          const Json::Value& resource = tokenResponse["resources"][i];
+          if (resource.isMember("dicom-uid") && resource["dicom-uid"].isString() && !resource["dicom-uid"].asString().empty() )
+          {
+            response.resourcesDicomIds.insert(resource["dicom-uid"].asString());
+          }
+          if (resource.isMember("orthanc-id") && resource["orthanc-id"].isString() && !resource["orthanc-id"].asString().empty() )
+          {
+            response.resourcesOrthancIds.insert(resource["orthanc-id"].asString());
+          }
+        }
+      }
+
       return true;
     }
     catch (Orthanc::OrthancException& ex)
--- a/Plugin/IAuthorizationService.h	Fri May 16 11:57:35 2025 +0200
+++ b/Plugin/IAuthorizationService.h	Mon May 26 18:53:20 2025 +0200
@@ -53,6 +53,9 @@
       std::string redirectUrl;
       std::string errorCode;
       std::string tokenType;
+
+      std::set<std::string> resourcesDicomIds;
+      std::set<std::string> resourcesOrthancIds; 
     };
 
     struct UserProfile
--- a/Plugin/Plugin.cpp	Fri May 16 11:57:35 2025 +0200
+++ b/Plugin/Plugin.cpp	Mon May 26 18:53:20 2025 +0200
@@ -820,13 +820,41 @@
         if (!GetStudyInstanceUIDFromQuery(studyInstanceUID, query))
         {
           // If there is no StudyInstaceUID, this might still be a call to /dicom-web/studies?PatientID=... e.g. from OHIF
-          // in this case, let's return an empty list.  TODO: in the future, we may get the StudyInstanceUIDs from the resource token and
+          // in this case, let's complement the query to filter against the StudyInstanceUIDs from the resource token and
           // "add" &StudyInstanceUID=1.2|1.3|1.4 in the query if there are multiple studies in the resource token
-          Json::Value emptyArray = Json::arrayValue;
-          OrthancPlugins::AnswerJson(emptyArray, output);
-          return;
+
+          std::vector<TokenAndValue> authTokens;  // the tokens that are set in this request
+          GetAuthTokens(authTokens, request->headersCount, request->headersKeys, request->headersValues, request->getCount, request->getKeys, request->getValues);
 
-          // old code prior to 0.9.2: throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess, "Auth plugin: unable to call tools/find when the user does not have access to any labels and if there is no StudyInstanceUID in the query.");
+          for (std::vector<TokenAndValue>::const_iterator it = authTokens.begin(); it != authTokens.end(); ++it)
+          {
+            OrthancPlugins::IAuthorizationService::DecodedToken decodedToken;
+            if (authorizationService_->DecodeToken(decodedToken,
+                                                   it->GetToken().GetKey(),
+                                                   it->GetValue()))
+            {
+              if (decodedToken.resourcesDicomIds.size() > 0)
+              {
+                std::string joinedStudyInstanceUids;
+                Orthanc::Toolbox::JoinStrings(joinedStudyInstanceUids, decodedToken.resourcesDicomIds, "|");
+                
+                LOG(WARNING) << "Auth plugin: adding StudyInstanceUID constrains based on the resources/dicom-uid in the token.";
+                // LOG(INFO) << joinedStudyInstanceUids;
+
+                query["StudyInstanceUID"] = joinedStudyInstanceUids;
+
+                Json::Value result;
+                if (OrthancPlugins::RestApiPost(result, nativeUrl, query, false))
+                {
+                  OrthancPlugins::AnswerJson(result, output);
+                  return;
+                }
+              }
+            }
+          }
+
+          // old code prior to 0.9.2: 
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess, "Auth plugin: unable to call tools/find when the user does not have access to any labels and if there is no StudyInstanceUID in the query or in the resource token.");
         }
 
         std::vector<std::string> studyOrthancIds;