diff Plugin/Plugin.cpp @ 1:d5d3cb00556a

initial release
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 22 Mar 2017 16:13:52 +0100
parents
children 4362026afddf
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Plugin.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,389 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "AssociativeArray.h"
+#include "DefaultAuthorizationParser.h"
+#include "CachedAuthorizationService.h"
+#include "AuthorizationWebService.h"
+#include "MemoryCache.h"
+
+#include "../Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h"
+#include "../Resources/Orthanc/Core/Logging.h"
+#include "../Resources/Orthanc/Core/Toolbox.h"
+
+static OrthancPluginContext* context_ = NULL;
+
+
+// Configuration of the authorization plugin
+static std::auto_ptr<OrthancPlugins::IAuthorizationParser> authorizationParser_;
+static std::auto_ptr<OrthancPlugins::IAuthorizationService> authorizationService_;
+static std::set<std::string> uncheckedResources_;
+static std::list<std::string> uncheckedFolders_;
+static std::list<OrthancPlugins::Token> tokens_;
+static std::set<OrthancPlugins::AccessLevel> uncheckedLevels_;
+
+
+static int32_t FilterHttpRequests(OrthancPluginHttpMethod method,
+                                  const char *uri,
+                                  const char *ip,
+                                  uint32_t headersCount,
+                                  const char *const *headersKeys,
+                                  const char *const *headersValues,
+                                  uint32_t getArgumentsCount,
+                                  const char *const *getArgumentsKeys,
+                                  const char *const *getArgumentsValues)
+{
+  try
+  {
+    if (method == OrthancPluginHttpMethod_Get)
+    {
+      // Allow GET accesses to static resources
+      if (uncheckedResources_.find(uri) != uncheckedResources_.end())
+      {
+        return 1;
+      }
+
+      for (std::list<std::string>::const_iterator
+             it = uncheckedFolders_.begin(); it != uncheckedFolders_.end(); ++it)
+      {
+        if (Orthanc::Toolbox::StartsWith(uri, *it))
+        {
+          return 1;
+        }
+      }
+    }
+
+    if (authorizationParser_.get() != NULL &&
+        authorizationService_.get() != NULL)
+    {
+      // Parse the resources that are accessed through this URI
+      OrthancPlugins::IAuthorizationParser::AccessedResources accesses;
+      if (!authorizationParser_->Parse(accesses, uri))
+      {
+        return 0;  // Unable to parse this URI
+      }
+
+      // Loop over all the accessed resources to ensure access is
+      // granted to each of them
+      for (OrthancPlugins::IAuthorizationParser::AccessedResources::const_iterator
+             access = accesses.begin(); access != accesses.end(); ++access)
+      {
+        // Ignored the access levels that are unchecked
+        // (cf. "UncheckedLevels" option)
+        if (uncheckedLevels_.find(access->GetLevel()) == uncheckedLevels_.end())
+        {
+          LOG(INFO) << "Testing whether access to "
+                    << OrthancPlugins::EnumerationToString(access->GetLevel())
+                    << " \"" << access->GetOrthancId() << "\" is allowed";
+
+          bool granted = false;
+          unsigned int validity;  // ignored
+
+          if (tokens_.empty())
+          {
+            granted = authorizationService_->IsGranted(validity, method, *access);
+          }
+          else
+          {
+            OrthancPlugins::AssociativeArray headers
+              (headersCount, headersKeys, headersValues, false);
+
+            OrthancPlugins::AssociativeArray getArguments
+              (getArgumentsCount, getArgumentsKeys, getArgumentsValues, true);
+
+            // Loop over all the authorization tokens stored in the HTTP
+            // headers, until finding one that is granted
+            for (std::list<OrthancPlugins::Token>::const_iterator
+                   token = tokens_.begin(); token != tokens_.end(); ++token)
+            {
+              std::string value;
+
+              bool hasValue = false;
+              switch (token->GetType())
+              {
+                case OrthancPlugins::TokenType_HttpHeader:
+                  hasValue = headers.GetValue(value, token->GetKey());
+                  break;
+
+                case OrthancPlugins::TokenType_GetArgument:
+                  hasValue = getArguments.GetValue(value, token->GetKey());
+                  break;
+
+                default:
+                  throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+              }
+              
+              if (hasValue &&
+                  authorizationService_->IsGranted(validity, method, *access, *token, value))
+              {
+                granted = true;
+                break;
+              }
+            }
+          }
+
+          if (!granted)
+          {
+            return 0;
+          }
+        }
+      }
+
+      // Access is granted to all the resources
+      return 1;
+    }
+      
+    // By default, forbid access to all the resources
+    return 0;
+  }
+  catch (std::runtime_error& e)
+  {
+    LOG(ERROR) << e.what();
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << e.What();
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+  catch (...)
+  {
+    LOG(ERROR) << "Unhandled internal exception";
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+}
+
+  
+#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 1)
+static int32_t FilterHttpRequestsFallback(OrthancPluginHttpMethod method,
+                                          const char *uri,
+                                          const char *ip,
+                                          uint32_t headersCount,
+                                          const char *const *headersKeys,
+                                          const char *const *headersValues)
+{
+  // Fallback wrapper function for Orthanc <= 1.2.0, where the GET
+  // arguments were not available in the HTTP filters
+  return FilterHttpRequests(method, uri, ip,
+                            headersCount, headersKeys, headersValues,
+                            0, NULL, NULL);
+}
+#endif
+
+
+static OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType,
+                                               OrthancPluginResourceType resourceType,
+                                               const char* resourceId)
+{
+  try
+  {
+    if (authorizationParser_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    
+    if (changeType == OrthancPluginChangeType_Deleted)
+    {
+      switch (resourceType)
+      {
+        case OrthancPluginResourceType_Patient:
+          authorizationParser_->Invalidate(Orthanc::ResourceType_Patient, resourceId);
+          break;
+
+        case OrthancPluginResourceType_Study:
+          authorizationParser_->Invalidate(Orthanc::ResourceType_Study, resourceId);
+          break;
+
+        case OrthancPluginResourceType_Series:
+          authorizationParser_->Invalidate(Orthanc::ResourceType_Series, resourceId);
+          break;
+
+        case OrthancPluginResourceType_Instance:
+          authorizationParser_->Invalidate(Orthanc::ResourceType_Instance, resourceId);
+          break;
+
+        default:
+          break;
+      }
+    }
+       
+    return OrthancPluginErrorCode_Success;
+  }
+  catch (std::runtime_error& e)
+  {
+    LOG(ERROR) << e.what();
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << e.What();
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+  catch (...)
+  {
+    LOG(ERROR) << "Unhandled internal exception";
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    context_ = context;
+    OrthancPluginLogWarning(context_, "Initializing the authorization plugin");
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context_) == 0)
+    {
+      OrthancPlugins::ReportMinimalOrthancVersion(context_, 
+                                                  ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+                                                  ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+                                                  ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      return -1;
+    }
+
+    Orthanc::Logging::Initialize(context_);
+    OrthancPluginSetDescription(context_, "Advanced authorization plugin for Orthanc.");
+
+    try
+    {
+      OrthancPlugins::OrthancConfiguration general(context_);
+
+      static const char* SECTION = "Authorization";
+      if (general.IsSection(SECTION))
+      {
+        OrthancPlugins::OrthancConfiguration configuration;
+        general.GetSection(configuration, "Authorization");
+
+        // TODO - The size of the caches is set to 10,000 items. Maybe add a configuration option?
+        OrthancPlugins::MemoryCache::Factory factory(10000);
+
+        {
+          std::string root;
+
+          if (configuration.IsSection("DicomWeb"))
+          {
+            OrthancPlugins::OrthancConfiguration dicomWeb;
+            dicomWeb.GetSection(configuration, "DicomWeb");
+            root = dicomWeb.GetStringValue("Root", "");
+          }
+
+          if (root.empty())
+          {
+            root = "/dicom-web/";
+          } 
+
+          authorizationParser_.reset
+            (new OrthancPlugins::DefaultAuthorizationParser(context_, factory, root));
+        }
+
+        std::list<std::string> tmp;
+
+        configuration.LookupListOfStrings(tmp, "TokenHttpHeaders", true);
+        for (std::list<std::string>::const_iterator
+               it = tmp.begin(); it != tmp.end(); ++it)
+        {
+          tokens_.push_back(OrthancPlugins::Token(OrthancPlugins::TokenType_HttpHeader, *it));
+        }
+
+        configuration.LookupListOfStrings(tmp, "TokenGetArguments", true);
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 1)  
+        for (std::list<std::string>::const_iterator
+               it = tmp.begin(); it != tmp.end(); ++it)
+        {
+          tokens_.push_back(OrthancPlugins::Token(OrthancPlugins::TokenType_GetArgument, *it));
+        }
+#else
+        if (!tmp.empty())
+        {
+          LOG(ERROR) << "The option \"TokenGetArguments\" of the authorization plugin "
+                     << "is only valid if compiled against Orthanc >= 1.2.1";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+        }
+#endif
+
+        configuration.LookupSetOfStrings(uncheckedResources_, "UncheckedResources", false);
+        configuration.LookupListOfStrings(uncheckedFolders_, "UncheckedFolders", false);
+
+        std::string url;
+
+        static const char* WEB_SERVICE = "WebService";
+        if (!configuration.LookupStringValue(url, WEB_SERVICE))
+        {
+          LOG(ERROR) << "Missing mandatory option \"" << WEB_SERVICE
+                     << "\" for the authorization plugin";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
+        }
+
+        if (configuration.LookupListOfStrings(tmp, "UncheckedLevels", false))
+        {
+          for (std::list<std::string>::const_iterator
+                 it = tmp.begin(); it != tmp.end(); ++it)
+          {
+            uncheckedLevels_.insert(OrthancPlugins::StringToAccessLevel(*it));
+          }
+        }
+
+        authorizationService_.reset
+          (new OrthancPlugins::CachedAuthorizationService
+           (new OrthancPlugins::AuthorizationWebService(context_, url), factory));
+
+        OrthancPluginRegisterOnChangeCallback(context_, OnChangeCallback);
+        
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 1)
+        OrthancPluginRegisterIncomingHttpRequestFilter2(context_, FilterHttpRequests);
+#else
+        OrthancPluginRegisterIncomingHttpRequestFilter(context_, FilterHttpRequestsFallback);
+#endif
+      }
+      else
+      {
+        LOG(WARNING) << "No section \"" << SECTION << "\" in the configuration file, "
+                     << "the authorization plugin is disabled";
+      }
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << e.What();
+      return -1;
+    }
+    
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    authorizationParser_.reset(NULL);
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "authorization";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return ORTHANC_AUTHORIZATION_VERSION;
+  }
+}