changeset 332:35d09327ee25

wrapped orthanc.RegisterHttpAuthentication()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 21 Aug 2026 13:25:30 +0200
parents 4e1efa5fbc61
children 10bc736775b1
files CMakeLists.txt CodeAnalysis/CustomFunctions.json NEWS Sources/HttpAuthenticationCallback.cpp Sources/HttpAuthenticationCallback.h Sources/Plugin.cpp Sources/PythonLock.cpp Sources/PythonLock.h Sources/PythonObject.cpp Sources/PythonObject.h
diffstat 10 files changed, 451 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Aug 20 17:42:25 2026 +0200
+++ b/CMakeLists.txt	Fri Aug 21 13:25:30 2026 +0200
@@ -301,6 +301,7 @@
 add_library(OrthancPython SHARED
   ${AUTOGENERATED_SOURCES}
   Sources/DicomScpCallbacks.cpp
+  Sources/HttpAuthenticationCallback.cpp
   Sources/ICallbackRegistration.cpp
   Sources/IncomingHttpRequestFilter.cpp
   Sources/IncomingInstanceFilter.cpp
--- a/CodeAnalysis/CustomFunctions.json	Thu Aug 20 17:42:25 2026 +0200
+++ b/CodeAnalysis/CustomFunctions.json	Fri Aug 21 13:25:30 2026 +0200
@@ -701,6 +701,28 @@
     "return_sdk_type" : "void",
     "since_sdk" : [ 1, 12, 8 ],
     "sdk_functions" : [ "OrthancPluginRegisterStorageArea3" ]
+  },
+
+  {
+    "short_name" : "RegisterHttpAuthenticationCallback",
+    "implementation" : "RegisterHttpAuthenticationCallback",
+    "documentation" : {
+      "description" : [ "Register a callback to handle HTTP authentication (and possibly HTTP authorization)." ],
+      "args" : {
+        "callback" : "The callback function."
+      }
+    },
+    "args" : [
+      {
+        "sdk_name" : "callback",
+        "sdk_type" : "Callable",
+        "callable_type" : "HttpAuthenticationCallback",
+        "callable_protocol_args" : "uri: str, ip: str, headers: dict, getArguments: dict",
+        "callable_protocol_return" : "Tuple"
+      }
+    ],
+    "return_sdk_type" : "void",
+    "sdk_functions" : [ "OrthancPluginRegisterHttpAuthentication" ]
   }
 
 ]
--- a/NEWS	Thu Aug 20 17:42:25 2026 +0200
+++ b/NEWS	Fri Aug 21 13:25:30 2026 +0200
@@ -1,9 +1,12 @@
 Pending changes in the mainline
 ===============================
 
-Note: the next version of this plugin will require Orthanc SDK 1.13.1
-to be released
-* wrapped DicomInsante.GetInstanceRemoteIp() & DicomInsante.GetInstanceCalledAet()
+=> Maximum SDK version: 1.13.0 (default) <=
+=> Minimum SDK version: 1.7.2 <=
+
+* Wrapped OrthancPluginRegisterHttpAuthentication() as orthanc.RegisterHttpAuthentication()
+* Wrapped DicomInsante.GetInstanceRemoteIp() and DicomInsante.GetInstanceCalledAet(),
+  which requires Orthanc SDK 1.13.1 (not released yet)
 
 
 Version 7.1 (2026-04-07)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/HttpAuthenticationCallback.cpp	Fri Aug 21 13:25:30 2026 +0200
@@ -0,0 +1,180 @@
+/**
+ * SPDX-FileCopyrightText: 2020-2023 Osimis S.A., 2024-2026 Orthanc Team SRL, 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, 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 "HttpAuthenticationCallback.h"
+
+#include "PythonHeaderWrapper.h"
+
+#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
+#include "ICallbackRegistration.h"
+#include "PythonString.h"
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 9)
+
+static PyObject*   httpAuthenticationCallback_ = NULL;
+
+
+static OrthancPluginErrorCode HttpAuthenticationCallback(
+  OrthancPluginHttpAuthenticationStatus*  status,         /* out */
+  OrthancPluginMemoryBuffer*              customPayload,  /* out */
+  OrthancPluginMemoryBuffer*              redirection,    /* out */
+  const char*                             uri,
+  const char*                             ip,
+  uint32_t                                headersCount,
+  const char* const*                      headersKeys,
+  const char* const*                      headersValues,
+  uint32_t                                getCount,
+  const char* const*                      getKeys,
+  const char* const*                      getValues)
+{
+  try
+  {
+    PythonLock lock;
+
+    PythonObject args(lock, PyTuple_New(4));
+
+    {
+      PythonString s(lock, uri);
+      PyTuple_SetItem(args.GetPyObject(), 0, s.Release());
+    }
+
+    {
+      PythonString s(lock, ip);
+      PyTuple_SetItem(args.GetPyObject(), 1, s.Release());
+    }
+
+    {
+      PythonObject headers(lock, PyDict_New());
+
+      for (uint32_t i = 0; i < headersCount; i++)
+      {
+        PythonString value(lock, headersValues[i]);
+        PyDict_SetItemString(headers.GetPyObject(), headersKeys[i], value.GetPyObject());
+      }
+
+      PyTuple_SetItem(args.GetPyObject(), 2, headers.Release());
+    }
+
+    {
+      PythonObject getArguments(lock, PyDict_New());
+
+      for (uint32_t i = 0; i < getCount; i++)
+      {
+        PythonString value(lock, getValues[i]);
+        PyDict_SetItemString(getArguments.GetPyObject(), getKeys[i], value.GetPyObject());
+      }
+
+      PyTuple_SetItem(args.GetPyObject(), 3, getArguments.Release());
+    }
+
+    PythonObject result(lock, PyObject_CallObject(httpAuthenticationCallback_, args.GetPyObject()));
+
+    OrthancPluginErrorCode code = lock.CheckCallbackSuccess("Python HttpAuthentication() callback");
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      return code;
+    }
+    else
+    {
+      *status = static_cast<OrthancPluginHttpAuthenticationStatus>(result.GetEnumerationValueFromTuple(0));
+
+      if (result.GetTupleItem(1) == Py_None)
+      {
+        redirection->size = 0;
+        redirection->data = NULL;
+      }
+      else
+      {
+        result.GetMemoryBufferFromTuple(customPayload, 1);
+      }
+
+      if (result.GetTupleItem(2) == Py_None)
+      {
+        redirection->size = 0;
+        redirection->data = NULL;
+      }
+      else
+      {
+        std::string s;
+        result.GetUtf8StringFromTuple(s, 2);
+
+        OrthancPlugins::MemoryBuffer buffer;
+        buffer.Assign(s);
+        *redirection = buffer.Release();
+      }
+
+      return OrthancPluginErrorCode_Success;
+    }
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    return e.GetErrorCode();
+  }
+}
+
+
+PyObject* RegisterHttpAuthenticationCallback(PyObject* module, PyObject* args)
+{
+  // The GIL is locked at this point (no need to create "PythonLock")
+
+  class Registration : public ICallbackRegistration
+  {
+  public:
+    virtual void Register() ORTHANC_OVERRIDE
+    {
+      OrthancPluginRegisterHttpAuthentication(
+        OrthancPlugins::GetGlobalContext(), HttpAuthenticationCallback);
+    }
+  };
+
+  Registration registration;
+  return ICallbackRegistration::Apply(
+    registration, args, httpAuthenticationCallback_, "Python received instance callback");
+}
+
+
+void FinalizeHttpAuthenticationCallback()
+{
+  ICallbackRegistration::Unregister(httpAuthenticationCallback_);
+}
+
+#else
+
+#warning OrthancPluginRegisterHttpAuthenticationCallback() is not supported
+
+PyObject* RegisterHttpAuthenticationCallback(PyObject* module, PyObject* args)
+{
+  PyErr_SetString(PyExc_RuntimeError, "The version of your Orthanc SDK doesn't provide OrthancPluginRegisterHttpAuthenticationCallback()");
+  return NULL;
+}
+
+void FinalizeHttpAuthenticationCallback()
+{
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/HttpAuthenticationCallback.h	Fri Aug 21 13:25:30 2026 +0200
@@ -0,0 +1,29 @@
+/**
+ * SPDX-FileCopyrightText: 2020-2023 Osimis S.A., 2024-2026 Orthanc Team SRL, 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, 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/>.
+ **/
+
+
+#pragma once
+
+void FinalizeHttpAuthenticationCallback();
--- a/Sources/Plugin.cpp	Thu Aug 20 17:42:25 2026 +0200
+++ b/Sources/Plugin.cpp	Fri Aug 21 13:25:30 2026 +0200
@@ -32,10 +32,11 @@
 
 
 #include "DicomScpCallbacks.h"
+#include "HttpAuthenticationCallback.h"
 #include "IncomingHttpRequestFilter.h"
+#include "IncomingInstanceFilter.h"
 #include "OnChangeCallback.h"
 #include "OnStoredInstanceCallback.h"
-#include "IncomingInstanceFilter.h"
 #include "ReceivedInstanceCallback.h"
 #include "StorageArea.h"
 #include "StorageArea3.h"
@@ -760,6 +761,7 @@
       FinalizeDicomScpCallbacks();
       FinalizeStorageArea();
       FinalizeStorageArea3();
+      FinalizeHttpAuthenticationCallback();  // New in release 7.2
 
       displayMemoryUsageStopping_ = true;
 
--- a/Sources/PythonLock.cpp	Thu Aug 20 17:42:25 2026 +0200
+++ b/Sources/PythonLock.cpp	Fri Aug 21 13:25:30 2026 +0200
@@ -33,7 +33,9 @@
 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
 
 #include <Compatibility.h>  // For std::unique_ptr<>
+#include <Logging.h>
 
+#include <boost/lexical_cast.hpp>
 #include <boost/thread/mutex.hpp>
 
 static boost::mutex   mutex_;
@@ -309,6 +311,141 @@
 }
 
 
+bool PythonLock::ToUtf8String(std::string& target,
+                              PyObject* value)
+{
+  if (value == NULL)
+  {
+    ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
+  }
+  else if (PyUnicode_Check(value))
+  {
+    PythonObject encoded(*this, PyUnicode_AsEncodedString(value, "utf-8", "replace"));
+    if (encoded.IsValid())
+    {
+      target = PyBytes_AS_STRING(encoded.GetPyObject());
+      return true;
+    }
+    else
+    {
+      target.clear();
+      return false;
+    }
+  }
+#if PY_MAJOR_VERSION == 2
+  else if (PyString_Check(value))
+  {
+    target = PyString_AS_STRING(value);
+    return true;
+  }
+#endif
+  else
+  {
+    target.clear();
+    return false;
+  }
+}
+
+
+void PythonLock::GetUtf8String(std::string& target,
+                               PyObject* value)
+{
+  if (!ToUtf8String(target, value))
+  {
+    ORTHANC_PLUGINS_LOG_ERROR("The Python object should contain an UTF-8 string");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadParameterType);
+  }
+}
+
+
+int PythonLock::GetEnumerationValue(PyObject* value)
+{
+  if (!PyLong_Check(value))
+  {
+    ORTHANC_PLUGINS_LOG_ERROR("The Python object should contain an enumeration");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadParameterType);
+  }
+  else
+  {
+    return PyLong_AsLong(value);
+  }
+}
+
+
+void PythonLock::GetMemoryBuffer(std::string& target,
+                                 PyObject* value)
+{
+  if (!PyBytes_Check(value))
+  {
+    ORTHANC_PLUGINS_LOG_ERROR("The Python object should contain an array of bytes");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadParameterType);
+  }
+  else
+  {
+    char* pythonBuffer = NULL;
+    Py_ssize_t pythonSize = 0;
+    if (PyBytes_AsStringAndSize(value, &pythonBuffer, &pythonSize) == 0)
+    {
+      assert(pythonSize == 0 || pythonBuffer != NULL);
+      target.assign(pythonBuffer, pythonSize);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot access the byte buffer");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+  }
+}
+
+
+void PythonLock::GetMemoryBuffer(OrthancPluginMemoryBuffer* target,
+                                 PyObject* value)
+{
+  if (!PyBytes_Check(value))
+  {
+    ORTHANC_PLUGINS_LOG_ERROR("The Python object should contain an array of bytes");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadParameterType);
+  }
+  else
+  {
+    char* pythonBuffer = NULL;
+    Py_ssize_t pythonSize = 0;
+    if (PyBytes_AsStringAndSize(value, &pythonBuffer, &pythonSize) == 0)
+    {
+      assert(pythonSize == 0 || pythonBuffer != NULL);
+      OrthancPlugins::MemoryBuffer buffer;
+      buffer.Assign(pythonBuffer, pythonSize);
+      *target = buffer.Release();
+    }
+    else
+    {
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot access the byte buffer");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+  }
+}
+
+
+PyObject* PythonLock::GetTupleItem(PyObject* tuple,
+                                   size_t index)
+{
+  if (!PyTuple_Check(tuple))
+  {
+    ORTHANC_PLUGINS_LOG_ERROR("The Python object should contain a tuple");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadParameterType);
+  }
+  else if (PyTuple_Size(tuple) <= static_cast<Py_ssize_t>(index))
+  {
+    ORTHANC_PLUGINS_LOG_ERROR("The Python tuple does not contain enough items (at least " +
+                              boost::lexical_cast<std::string>(index + 1) + " items are needed)");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+  }
+  else
+  {
+    return PyTuple_GET_ITEM(tuple, index);
+  }
+}
+
 
 void PythonLock::GlobalInitialize(const std::string& moduleName,
                                   const std::string& exceptionName,
--- a/Sources/PythonLock.h	Thu Aug 20 17:42:25 2026 +0200
+++ b/Sources/PythonLock.h	Fri Aug 21 13:25:30 2026 +0200
@@ -55,6 +55,23 @@
 
   OrthancPluginErrorCode CheckCallbackSuccess(const std::string& callbackDetails);
 
+  int GetEnumerationValue(PyObject* value);
+
+  bool ToUtf8String(std::string& target,
+                    PyObject* value);
+
+  void GetUtf8String(std::string& target,
+                     PyObject* value);
+
+  void GetMemoryBuffer(std::string& target,
+                       PyObject* value);
+
+  void GetMemoryBuffer(OrthancPluginMemoryBuffer* target,
+                       PyObject* value);
+
+  PyObject* GetTupleItem(PyObject* tuple,
+                         size_t index);
+
   static void GlobalInitialize(const std::string& moduleName,
                                const std::string& exceptionName,
                                ModuleFunctionsInstaller moduleFunctions,
--- a/Sources/PythonObject.cpp	Thu Aug 20 17:42:25 2026 +0200
+++ b/Sources/PythonObject.cpp	Fri Aug 21 13:25:30 2026 +0200
@@ -70,42 +70,6 @@
 }
 
 
-bool PythonObject::ToUtf8String(std::string& target,
-                                PyObject* value)
-{
-  if (value == NULL)
-  {
-    ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
-  }
-  else if (PyUnicode_Check(value))
-  {
-    PythonObject encoded(lock_, PyUnicode_AsEncodedString(value, "utf-8", "replace"));
-    if (encoded.IsValid())
-    {
-      target = PyBytes_AS_STRING(encoded.GetPyObject());
-      return true;
-    }
-    else
-    {
-      target.clear();
-      return false;
-    }
-  }
-#if PY_MAJOR_VERSION == 2
-  else if (PyString_Check(value))
-  {
-    target = PyString_AS_STRING(value);
-    return true;
-  }
-#endif
-  else
-  {
-    target.clear();
-    return false;
-  }
-}
-
-
 void PythonObject::Format(std::ostream& os)
 {
   std::string s;
@@ -176,7 +140,7 @@
   else if (PyUnicode_Check(source))
   {
     std::string s;
-    if (ToUtf8String(s, source))
+    if (lock_.ToUtf8String(s, source))
     {
       target = s;
     }
@@ -226,7 +190,7 @@
       std::string key;
       Json::Value value;
       if (pair != NULL &&
-          ToUtf8String(key, PySequence_GetItem(pair, 0)))
+          lock_.ToUtf8String(key, PySequence_GetItem(pair, 0)))
       {
         ConvertToJson(value, PySequence_GetItem(pair, 1));
         target[key] = value;
--- a/Sources/PythonObject.h	Thu Aug 20 17:42:25 2026 +0200
+++ b/Sources/PythonObject.h	Fri Aug 21 13:25:30 2026 +0200
@@ -37,9 +37,6 @@
   PyObject    *object_;
   bool         borrowed_;
 
-  bool ToUtf8String(std::string& target,
-                    PyObject* value);
-
   void ConvertToJson(Json::Value& target,
                      PyObject* source);
   
@@ -61,7 +58,7 @@
 
   bool ToUtf8String(std::string& target)
   {
-    return ToUtf8String(target, GetPyObject());
+    return lock_.ToUtf8String(target, GetPyObject());
   }
 
   void Format(std::ostream& os);
@@ -72,4 +69,57 @@
   {
     ConvertToJson(target, GetPyObject());
   }
+
+  bool IsNone()
+  {
+    return object_ == Py_None;
+  }
+
+  int GetEnumerationValue()
+  {
+    return lock_.GetEnumerationValue(object_);
+  }
+
+  void GetUtf8String(std::string& target)
+  {
+    return lock_.GetUtf8String(target, object_);
+  }
+
+  void GetMemoryBuffer(std::string& target)
+  {
+    lock_.GetMemoryBuffer(target, object_);
+  }
+
+  void GetMemoryBuffer(OrthancPluginMemoryBuffer* target)
+  {
+    lock_.GetMemoryBuffer(target, object_);
+  }
+
+  PyObject* GetTupleItem(size_t index)
+  {
+    return lock_.GetTupleItem(object_, index);
+  }
+
+  int GetEnumerationValueFromTuple(size_t index)
+  {
+    return lock_.GetEnumerationValue(lock_.GetTupleItem(object_, index));
+  }
+
+  void GetUtf8StringFromTuple(std::string& target,
+                              size_t index)
+  {
+    return lock_.GetUtf8String(target, lock_.GetTupleItem(object_, index));
+  }
+
+  void GetMemoryBufferFromTuple(std::string& target,
+                                size_t index)
+  {
+    return lock_.GetMemoryBuffer(target, lock_.GetTupleItem(object_, index));
+  }
+
+  void GetMemoryBufferFromTuple(OrthancPluginMemoryBuffer* target,
+                                size_t index)
+  {
+    return lock_.GetMemoryBuffer(target, lock_.GetTupleItem(object_, index));
+  }
 };