Mercurial > hg > orthanc-python
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)); + } };
