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, &params);
   }
 
+
+  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, &params);
+  }
+
 #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";
         }
       }