changeset 4343:e1e918e790e8

New function in the SDK: OrthancPluginGenerateRestApiAuthorizationToken()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 04 Dec 2020 18:28:23 +0100
parents 52166629239f
children 25112063257b
files NEWS OrthancFramework/Sources/HttpServer/HttpServer.cpp OrthancFramework/Sources/HttpServer/IIncomingHttpRequestFilter.h OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Engine/OrthancPlugins.h OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Sources/main.cpp
diffstat 7 files changed, 147 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Dec 03 18:48:06 2020 +0100
+++ b/NEWS	Fri Dec 04 18:28:23 2020 +0100
@@ -21,6 +21,11 @@
   body to complement C-GET SCU with C-FIND SCU ("DicomEchoChecksFind" on a per-connection basis)
 * Archive/media jobs report the size of the created ZIP file in content field "ArchiveSizeMB"
 
+Plugins
+-------
+
+* New function in the SDK: OrthancPluginGenerateRestApiAuthorizationToken()
+
 Maintenance
 -----------
 
@@ -36,9 +41,17 @@
 Version 1.8.0 (2020-10-16)
 ==========================
 
+General
+-------
+
 * Serving the content of Orthanc as a WebDAV network share
 * New config options: "WebDavEnabled", "WebDavDeleteAllowed" and "WebDavUploadAllowed"
 
+Plugins
+-------
+
+* New available origin for a DICOM instance: "OrthancPluginInstanceOrigin_WebDav"
+
 
 Version 1.7.4 (2020-09-18)
 ==========================
--- a/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Thu Dec 03 18:48:06 2020 +0100
+++ b/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Fri Dec 04 18:28:23 2020 +0100
@@ -545,24 +545,45 @@
   }
 
 
-  static bool IsAccessGranted(const HttpServer& that,
-                              const HttpToolbox::Arguments& headers)
+  enum AccessMode
   {
-    bool granted = false;
+    AccessMode_Forbidden,
+    AccessMode_AuthorizationToken,
+    AccessMode_RegisteredUser
+  };
+  
+
+  static AccessMode IsAccessGranted(const HttpServer& that,
+                                    const HttpToolbox::Arguments& headers)
+  {
+    static const std::string BASIC = "Basic ";
+    static const std::string BEARER = "Bearer ";
 
     HttpToolbox::Arguments::const_iterator auth = headers.find("authorization");
     if (auth != headers.end())
     {
       std::string s = auth->second;
-      if (s.size() > 6 &&
-          s.substr(0, 6) == "Basic ")
+      if (boost::starts_with(s, BASIC))
       {
-        std::string b64 = s.substr(6);
-        granted = that.IsValidBasicHttpAuthentication(b64);
+        std::string b64 = s.substr(BASIC.length());
+        if (that.IsValidBasicHttpAuthentication(b64))
+        {
+          return AccessMode_RegisteredUser;
+        }
+      }
+      else if (boost::starts_with(s, BEARER) &&
+               that.GetIncomingHttpRequestFilter() != NULL)
+      {
+        // New in Orthanc 1.8.1
+        std::string token = s.substr(BEARER.length());
+        if (that.GetIncomingHttpRequestFilter()->IsValidBearerToken(token))
+        {
+          return AccessMode_AuthorizationToken;
+        }
       }
     }
 
-    return granted;
+    return AccessMode_Forbidden;
   }
 
 
@@ -1075,7 +1096,7 @@
     if (!server.IsRemoteAccessAllowed() &&
         !localhost)
     {
-      output.SendUnauthorized(server.GetRealm());
+      output.SendUnauthorized(server.GetRealm());   // 401 error
       return;
     }
 
@@ -1105,12 +1126,14 @@
       HttpToolbox::ParseGetArguments(argumentsGET, request->query_string);
     }
 
+    
+    AccessMode accessMode = IsAccessGranted(server, headers);
 
     // Authenticate this connection
     if (server.IsAuthenticationEnabled() && 
-        !IsAccessGranted(server, headers))
+        accessMode == AccessMode_Forbidden)
     {
-      output.SendUnauthorized(server.GetRealm());
+      output.SendUnauthorized(server.GetRealm());   // 401 error
       return;
     }
 
@@ -1195,16 +1218,22 @@
     }
     
 
-    // Check that this connection is allowed by the user's authentication filter
     const std::string username = GetAuthenticatedUsername(headers);
 
-    IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter();
-    if (filter != NULL)
+    if (accessMode != AccessMode_AuthorizationToken)
     {
-      if (!filter->IsAllowed(filterMethod, requestUri, remoteIp,
+      // Check that this access is granted by the user's authorization
+      // filter. In the case of an authorization bearer token, grant
+      // full access to the API.
+
+      assert(accessMode == AccessMode_Forbidden ||  // Could be the case if "!server.IsAuthenticationEnabled()"
+             accessMode == AccessMode_RegisteredUser);
+      
+      IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter();
+      if (filter != NULL &&
+          !filter->IsAllowed(filterMethod, requestUri, remoteIp,
                              username.c_str(), headers, argumentsGET))
       {
-        //output.SendUnauthorized(server.GetRealm());
         output.SendStatus(HttpStatus_403_Forbidden);
         return;
       }
--- a/OrthancFramework/Sources/HttpServer/IIncomingHttpRequestFilter.h	Thu Dec 03 18:48:06 2020 +0100
+++ b/OrthancFramework/Sources/HttpServer/IIncomingHttpRequestFilter.h	Fri Dec 04 18:28:23 2020 +0100
@@ -33,6 +33,9 @@
     {
     }
 
+    // New in Orthanc 1.8.1
+    virtual bool IsValidBearerToken(const std::string& token) = 0;
+    
     virtual bool IsAllowed(HttpMethod method,
                            const char* uri,
                            const char* ip,
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Thu Dec 03 18:48:06 2020 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Fri Dec 04 18:28:23 2020 +0100
@@ -918,6 +918,7 @@
     RefreshMetricsCallbacks refreshMetricsCallbacks_;
     StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_;
     std::unique_ptr<StorageAreaFactory>  storageArea_;
+    std::set<std::string> authorizationTokens_;
 
     boost::recursive_mutex restCallbackMutex_;
     boost::recursive_mutex storedCallbackMutex_;
@@ -4647,6 +4648,18 @@
         return true;
       }
 
+      case _OrthancPluginService_GenerateRestApiAuthorizationToken:
+      {
+        const _OrthancPluginRetrieveDynamicString& p = 
+          *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters);
+        const std::string token = Toolbox::GenerateUuid();
+
+        pimpl_->authorizationTokens_.insert(token);
+        *p.result = CopyString("Bearer " + token);
+
+        return true;
+      }
+
       default:
       {
         // This service is unknown to the Orthanc plugin engine
@@ -5257,4 +5270,11 @@
 
     return false;
   }
+
+
+  bool OrthancPlugins::IsValidAuthorizationToken(const std::string& token) const
+  {
+    boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
+    return (pimpl_->authorizationTokens_.find(token) != pimpl_->authorizationTokens_.end());
+  }
 }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h	Thu Dec 03 18:48:06 2020 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h	Fri Dec 04 18:28:23 2020 +0100
@@ -80,7 +80,6 @@
     public IServerListener,
     public IWorklistRequestHandlerFactory,
     public IDicomImageDecoder,
-    public IIncomingHttpRequestFilter,
     public IFindRequestHandlerFactory,
     public IMoveRequestHandlerFactory,
     public IStorageCommitmentFactory,
@@ -348,12 +347,12 @@
                                   size_t size,
                                   unsigned int frame) ORTHANC_OVERRIDE;
 
-    virtual bool IsAllowed(HttpMethod method,
-                           const char* uri,
-                           const char* ip,
-                           const char* username,
-                           const HttpToolbox::Arguments& httpHeaders,
-                           const HttpToolbox::GetArguments& getArguments) ORTHANC_OVERRIDE;
+    bool IsAllowed(HttpMethod method,
+                   const char* uri,
+                   const char* ip,
+                   const char* username,
+                   const HttpToolbox::Arguments& httpHeaders,
+                   const HttpToolbox::GetArguments& getArguments);
 
     bool HasFindHandler();
 
@@ -385,6 +384,9 @@
       const std::vector<std::string>& sopInstanceUids,
       const std::string& remoteAet,
       const std::string& calledAet) ORTHANC_OVERRIDE;
+
+    // New in Orthanc 1.8.1 (cf. "OrthancPluginGenerateRestApiAuthorizationToken()")
+    bool IsValidAuthorizationToken(const std::string& token) const;
   };
 }
 
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Thu Dec 03 18:48:06 2020 +0100
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Fri Dec 04 18:28:23 2020 +0100
@@ -117,7 +117,7 @@
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     8
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  1
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -435,6 +435,7 @@
     _OrthancPluginService_EncodeDicomWebJson2 = 36,  /* New in Orthanc 1.7.0 */
     _OrthancPluginService_EncodeDicomWebXml2 = 37,   /* New in Orthanc 1.7.0 */
     _OrthancPluginService_CreateMemoryBuffer = 38,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GenerateRestApiAuthorizationToken = 39,   /* New in Orthanc 1.8.1 */
     
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
@@ -8197,6 +8198,51 @@
   }
   
 
+  /**
+   * @brief Generate a token to grant full access to the REST API of Orthanc
+   *
+   * This function generates a token that can be set in the HTTP
+   * header "Authorization" so as to grant full access to the REST API
+   * of Orthanc using an external HTTP client. Using this function
+   * avoids the need of adding a separate user in the
+   * "RegisteredUsers" configuration of Orthanc, which eases
+   * deployments.
+   *
+   * This feature is notably useful in multiprocess scenarios, where a
+   * subprocess created by a plugin has no access to the
+   * "OrthancPluginContext", and thus cannot call
+   * "OrthancPluginRestApi[Get|Post|Put|Delete]()".
+   *
+   * This situation is frequently encountered in Python plugins, where
+   * the "multiprocessing" package can be used to bypass the Global
+   * Interpreter Lock (GIL) and thus to improve performance and
+   * concurrency.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The authorization token, or NULL value in the case of an error.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateRestApiAuthorizationToken(
+    OrthancPluginContext*  context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GenerateRestApiAuthorizationToken,
+                               &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
 #ifdef  __cplusplus
 }
 #endif
--- a/OrthancServer/Sources/main.cpp	Thu Dec 03 18:48:06 2020 +0100
+++ b/OrthancServer/Sources/main.cpp	Fri Dec 04 18:28:23 2020 +0100
@@ -465,6 +465,16 @@
   {
   }
 
+  virtual bool IsValidBearerToken(const std::string& token) ORTHANC_OVERRIDE
+  {
+#if ORTHANC_ENABLE_PLUGINS == 1
+    return (plugins_ != NULL &&
+            plugins_->IsValidAuthorizationToken(token));
+#else
+    return false;
+#endif    
+  }
+  
   virtual bool IsAllowed(HttpMethod method,
                          const char* uri,
                          const char* ip,