Mercurial > hg > orthanc
changeset 6228:c3fb276f8eba
merge
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Mon, 07 Jul 2025 19:34:45 +0200 |
parents | 71e404609278 (current diff) 3298fa589b27 (diff) |
children | a85ff09d94a0 |
files | NEWS OrthancFramework/Sources/HttpServer/HttpServer.cpp OrthancFramework/Sources/HttpServer/HttpServer.h OrthancServer/Sources/main.cpp |
diffstat | 11 files changed, 153 insertions(+), 64 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Mon Jul 07 19:28:34 2025 +0200 +++ b/NEWS Mon Jul 07 19:34:45 2025 +0200 @@ -4,14 +4,14 @@ General ------- -* Lua: new "SetStableStatus" function. +* Lua: new "SetStableStatus()" function. REST API -------- -* When creating a job, you can now add a "UserData" field in the payload. - This data travels along with the job and is available in the - /jobs/{jobId} route. +* When creating a job, a "UserData" field can be added to the payload. + This data will travel along with the job and will be available in the + "/jobs/{jobId}" route. * New metrics "orthanc_available_dicom_threads" that displays the minimum number of DICOM Threads that were available during the last 10 seconds. * New metrics "orthanc_available_http_threads" that displays the minimum @@ -23,22 +23,29 @@ * Fixed the "orthanc_rest_api_active_requests" metrix that was not reset after 10 seconds. - Plugin SDK ---------- -* Added new function OrthancPluginSetStableStatus to e.g force the - stabilization of a study from a plugin. - +* Added new primitives: + - "OrthancPluginSetStableStatus()" to force the stabilization of a + DICOM resource from a plugin. + - "OrthancPluginRedirectNotAuthenticatedToRoot()" to allow a plugin + to redirect unauthenticated users to a login page. Plugins ------- * Housekeeper plugin: - - new "ForceReconstructFiles": If "Force" is set to true, forces - the "ReconstructFiles" option when reconstructing resources even - if the plugin did not detect any changes in the configuration that - should trigger a Reconstruct. + - new "ForceReconstructFiles" option: If set to true, forces the + "ReconstructFiles" option when reconstructing resources, even if + the plugin did not detect any changes in the configuration that + should trigger a reconstruct. + +Maintenance +----------- + +* If the "RegisteredUsers" configuration option is present but empty, + Orthanc does not create the default user "orthanc" anymore.
--- a/OrthancFramework/Sources/HttpServer/HttpServer.cpp Mon Jul 07 19:28:34 2025 +0200 +++ b/OrthancFramework/Sources/HttpServer/HttpServer.cpp Mon Jul 07 19:34:45 2025 +0200 @@ -1246,8 +1246,10 @@ if (server.IsAuthenticationEnabled() && accessMode == AccessMode_NotAuthenticated) { - if (server.IsRedirectNotAuthenticatedToRoot() && - uri.size() > 0) + IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); + if (uri.size() > 0 && + filter != NULL && + filter->IsRedirectNotAuthenticatedToRoot()) { // This is new in Orthanc 1.12.9 std::string redirectionToRoot; @@ -1631,7 +1633,6 @@ threadsCount_(50), // Default value in mongoose/civetweb tcpNoDelay_(true), requestTimeout_(30), // Default value in mongoose/civetweb (30 seconds) - redirectNotAuthenticatedToRoot_(false), availableHttpThreadsMetrics_(metricsRegistry, "orthanc_available_http_threads_count", MetricsUpdatePolicy_MinOver10Seconds) @@ -2238,17 +2239,4 @@ } } #endif - - - bool HttpServer::IsRedirectNotAuthenticatedToRoot() const - { - return redirectNotAuthenticatedToRoot_; - } - - - void HttpServer::SetRedirectNotAuthenticatedToRoot(bool redirect) - { - Stop(); - redirectNotAuthenticatedToRoot_ = redirect; - } }
--- a/OrthancFramework/Sources/HttpServer/HttpServer.h Mon Jul 07 19:28:34 2025 +0200 +++ b/OrthancFramework/Sources/HttpServer/HttpServer.h Mon Jul 07 19:34:45 2025 +0200 @@ -115,7 +115,6 @@ unsigned int threadsCount_; bool tcpNoDelay_; unsigned int requestTimeout_; // In seconds - bool redirectNotAuthenticatedToRoot_; // New in Orthanc 1.12.9 MetricsRegistry::SharedMetrics availableHttpThreadsMetrics_; #if ORTHANC_ENABLE_PUGIXML == 1 @@ -230,10 +229,6 @@ const std::string& body, const std::string& boundary); - bool IsRedirectNotAuthenticatedToRoot() const; - - void SetRedirectNotAuthenticatedToRoot(bool redirect); - MetricsRegistry::SharedMetrics& GetAvailableHttpThreadsMetrics() { return availableHttpThreadsMetrics_;
--- a/OrthancFramework/Sources/HttpServer/IIncomingHttpRequestFilter.h Mon Jul 07 19:28:34 2025 +0200 +++ b/OrthancFramework/Sources/HttpServer/IIncomingHttpRequestFilter.h Mon Jul 07 19:34:45 2025 +0200 @@ -44,5 +44,7 @@ const char* username, const HttpToolbox::Arguments& httpHeaders, const HttpToolbox::GetArguments& getArguments) = 0; + + virtual bool IsRedirectNotAuthenticatedToRoot() const = 0; }; }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Mon Jul 07 19:28:34 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Mon Jul 07 19:34:45 2025 +0200 @@ -1763,6 +1763,7 @@ std::string databaseServerIdentifier_; // New in Orthanc 1.9.2 unsigned int maxDatabaseRetries_; // New in Orthanc 1.9.2 bool hasStorageAreaCustomData_; // New in Orthanc 1.12.8 + bool redirectNotAuthenticatedToRoot_; // New in Orthanc 1.12.9 explicit PImpl(const std::string& databaseServerIdentifier) : contextRefCount_(0), @@ -1774,7 +1775,8 @@ argv_(NULL), databaseServerIdentifier_(databaseServerIdentifier), maxDatabaseRetries_(0), - hasStorageAreaCustomData_(false) + hasStorageAreaCustomData_(false), + redirectNotAuthenticatedToRoot_(false) { memset(&moveCallbacks_, 0, sizeof(moveCallbacks_)); } @@ -5851,6 +5853,18 @@ return true; } + case _OrthancPluginService_RedirectNotAuthenticatedToRoot: + { + const _OrthancPluginRedirectNotAuthenticatedToRoot& p = *reinterpret_cast<const _OrthancPluginRedirectNotAuthenticatedToRoot*>(parameters); + + { + boost::unique_lock<boost::shared_mutex> lock(pimpl_->incomingHttpRequestFilterMutex_); + pimpl_->redirectNotAuthenticatedToRoot_ = (p.redirect == 1); + } + + return true; + } + default: return false; } @@ -6816,4 +6830,11 @@ pimpl_->webDavCollections_.pop_front(); } } + + + bool OrthancPlugins::IsRedirectNotAuthenticatedToRoot() const + { + boost::shared_lock<boost::shared_mutex> lock(pimpl_->incomingHttpRequestFilterMutex_); + return pimpl_->redirectNotAuthenticatedToRoot_; + } }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h Mon Jul 07 19:28:34 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h Mon Jul 07 19:34:45 2025 +0200 @@ -414,6 +414,8 @@ unsigned int GetMaxDatabaseRetries() const; void RegisterWebDavCollections(HttpServer& target); + + bool IsRedirectNotAuthenticatedToRoot() const; }; }
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Mon Jul 07 19:28:34 2025 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Mon Jul 07 19:34:45 2025 +0200 @@ -484,6 +484,7 @@ _OrthancPluginService_DequeueValue = 58, /* New in Orthanc 1.12.8 */ _OrthancPluginService_GetQueueSize = 59, /* New in Orthanc 1.12.8 */ _OrthancPluginService_SetStableStatus = 60, /* New in Orthanc 1.12.9 */ + _OrthancPluginService_RedirectNotAuthenticatedToRoot = 61, /* New in Orthanc 1.12.9 */ /* Registration of callbacks */ _OrthancPluginService_RegisterRestCallback = 1000, @@ -10339,10 +10340,44 @@ return context->InvokeService(context, _OrthancPluginService_SetStableStatus, ¶ms); } + + typedef struct + { + uint8_t redirect; + } _OrthancPluginRedirectNotAuthenticatedToRoot; + + /** + * @brief Whether to redirect unauthorized requests to the root of the Web server. + * + * If a request is non-authenticated (e.g., because of bad HTTP + * basic authentication credentials, or because of a plugin + * implementing a filtering over incoming HTTP requests), Orthanc by + * default returns a 401 "Unauthorized" HTTP status code. + * + * However, an Orthanc plugin providing a custom user interface + * could want to redirect the user to a login page. In such a + * situation, plugins can invoke + * "OrthancPluginRedirectNotAuthenticatedToRoot(context, 1)". This + * allows the plugin to redirect the user to its login page by + * overriding the REST callback for the "/" path. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param redirect Whether to redirect unauthorized (non-authenticated) requests. + * @return 0 if success, other value if error. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRedirectNotAuthenticatedToRoot( + OrthancPluginContext* context, + uint8_t redirect) + { + _OrthancPluginRedirectNotAuthenticatedToRoot params; + params.redirect = redirect; + + return context->InvokeService(context, _OrthancPluginService_RedirectNotAuthenticatedToRoot, ¶ms); + } + #ifdef __cplusplus } #endif /** @} */ -
--- a/OrthancServer/Resources/Configuration.json Mon Jul 07 19:28:34 2025 +0200 +++ b/OrthancServer/Resources/Configuration.json Mon Jul 07 19:34:45 2025 +0200 @@ -307,10 +307,11 @@ // The list of the registered users. Because Orthanc uses HTTP // Basic Authentication, the passwords are stored as plain text. - "RegisteredUsers" : { - // "alice" : "alicePassword" - }, - + /** + "RegisteredUsers" : { + // "alice" : "alicePassword" + }, + **/ /**
--- a/OrthancServer/Sources/OrthancConfiguration.cpp Mon Jul 07 19:28:34 2025 +0200 +++ b/OrthancServer/Sources/OrthancConfiguration.cpp Mon Jul 07 19:34:45 2025 +0200 @@ -707,32 +707,46 @@ } - bool OrthancConfiguration::SetupRegisteredUsers(HttpServer& httpServer) const + OrthancConfiguration::RegisteredUsersStatus OrthancConfiguration::SetupRegisteredUsers(HttpServer& httpServer) const { + static const char* const REGISTERED_USERS = "RegisteredUsers"; + httpServer.ClearUsers(); - if (!json_.isMember("RegisteredUsers")) + if (!json_.isMember(REGISTERED_USERS)) { - return false; + return RegisteredUsersStatus_NoConfiguration; } - - const Json::Value& users = json_["RegisteredUsers"]; - if (users.type() != Json::objectValue) + else { - throw OrthancException(ErrorCode_BadFileFormat, "Badly formatted list of users"); - } + const Json::Value& users = json_[REGISTERED_USERS]; + if (users.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat, "Badly formatted list of users"); + } - bool hasUser = false; - Json::Value::Members usernames = users.getMemberNames(); - for (size_t i = 0; i < usernames.size(); i++) - { - const std::string& username = usernames[i]; - std::string password = users[username].asString(); - httpServer.RegisterUser(username.c_str(), password.c_str()); - hasUser = true; + bool hasUser = false; + Json::Value::Members usernames = users.getMemberNames(); + for (size_t i = 0; i < usernames.size(); i++) + { + const std::string& username = usernames[i]; + + if (users[username].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat, "Badly formatted list of users"); + } + else + { + std::string password = users[username].asString(); + httpServer.RegisterUser(username.c_str(), password.c_str()); + hasUser = true; + } + } + + return (hasUser ? + RegisteredUsersStatus_HasUser : + RegisteredUsersStatus_NoUser); } - - return hasUser; }
--- a/OrthancServer/Sources/OrthancConfiguration.h Mon Jul 07 19:28:34 2025 +0200 +++ b/OrthancServer/Sources/OrthancConfiguration.h Mon Jul 07 19:34:45 2025 +0200 @@ -49,6 +49,14 @@ class OrthancConfiguration : public boost::noncopyable { + public: + enum RegisteredUsersStatus + { + RegisteredUsersStatus_NoConfiguration, // There is no "RegisteredUsers" section in the configuration file + RegisteredUsersStatus_NoUser, // The "RegisteredUsers" section is present, but declares no user + RegisteredUsersStatus_HasUser // The "RegisteredUsers" section is present and contains at least 1 user + }; + private: typedef std::map<std::string, RemoteModalityParameters> Modalities; typedef std::map<std::string, WebServiceParameters> Peers; @@ -198,9 +206,8 @@ void GetListOfOrthancPeers(std::set<std::string>& target) const; unsigned int GetDicomLossyTranscodingQuality() const; - - // Returns "true" iff. at least one user is registered - bool SetupRegisteredUsers(HttpServer& httpServer) const; + + RegisteredUsersStatus SetupRegisteredUsers(HttpServer& httpServer) const; std::string InterpretStringParameterAsPath(const std::string& parameter) const;
--- a/OrthancServer/Sources/main.cpp Mon Jul 07 19:28:34 2025 +0200 +++ b/OrthancServer/Sources/main.cpp Mon Jul 07 19:34:45 2025 +0200 @@ -604,6 +604,18 @@ return true; } + + virtual bool IsRedirectNotAuthenticatedToRoot() const ORTHANC_OVERRIDE + { +#if ORTHANC_ENABLE_PLUGINS == 1 + if (plugins_ != NULL) + { + return plugins_->IsRedirectNotAuthenticatedToRoot(); + } +#endif + + return false; + } }; @@ -1090,12 +1102,16 @@ httpServer.SetAuthenticationEnabled(false); } - bool hasUsers = lock.GetConfiguration().SetupRegisteredUsers(httpServer); + OrthancConfiguration::RegisteredUsersStatus status = lock.GetConfiguration().SetupRegisteredUsers(httpServer); + assert(status == OrthancConfiguration::RegisteredUsersStatus_NoConfiguration || + status == OrthancConfiguration::RegisteredUsersStatus_NoUser || + status == OrthancConfiguration::RegisteredUsersStatus_HasUser); if (httpServer.IsAuthenticationEnabled() && - !hasUsers) + status != OrthancConfiguration::RegisteredUsersStatus_HasUser) { - if (httpServer.IsRemoteAccessAllowed()) + if (httpServer.IsRemoteAccessAllowed() && + status == OrthancConfiguration::RegisteredUsersStatus_NoConfiguration) { /** * Starting with Orthanc 1.5.8, if no user is explicitly @@ -1117,7 +1133,8 @@ else { LOG(WARNING) << "HTTP authentication is enabled, but no user is declared, " - << "check the value of configuration option \"RegisteredUsers\""; + << "check the value of configuration option \"RegisteredUsers\" " + << "if you cannot access Orthanc as expected"; } }