changeset 253:b340ccd0b22a inbox

wip: audit-logs
author Alain Mazy <am@orthanc.team>
date Mon, 14 Jul 2025 18:23:36 +0200
parents f1c8f36e0b87
children dabd229db543
files NEWS Plugin/Plugin.cpp
diffstat 2 files changed, 71 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Jul 14 11:40:39 2025 +0200
+++ b/NEWS	Mon Jul 14 18:23:36 2025 +0200
@@ -1,9 +1,5 @@
-2025-07-14 - v 0.9.4
-====================
-
-=> Minimum Orthanc version: 1.11.3 <=
-=> Recommended SDK version: 1.12.4 <=
-=> Minimum SDK version: 1.11.3 <=
+Pending changes in the mainline
+===============================
 
 * New configuration "ExtraPermissions" to ADD new permissions to
   the default "Permissions" entries.
@@ -15,7 +11,16 @@
   provides it.
 * The User profile can now contain an "id" field if the auth-service
   provides it.
-* New experimental feature: audit-logs (TODO)
+* New experimental feature: audit-logs (TODO) - config "EnableAuditLogs"
+
+
+2025-07-14 - v 0.9.4
+====================
+
+=> Minimum Orthanc version: 1.11.3 <=
+=> Recommended SDK version: 1.12.4 <=
+=> Minimum SDK version: 1.11.3 <=
+
 * Fixed a security issue: the entries in the cache token->permissions were kept too long in the cache
   allowing users to have access to generic routes even with an expired token.
   These entries are now stored maximum for 10 seconds.
--- a/Plugin/Plugin.cpp	Mon Jul 14 11:40:39 2025 +0200
+++ b/Plugin/Plugin.cpp	Mon Jul 14 18:23:36 2025 +0200
@@ -31,6 +31,7 @@
 #include <Toolbox.h>
 #include <SerializationToolbox.h>
 #include <EmbeddedResources.h>
+#include "Enumerations.h"
 
 #define ORTHANC_PLUGIN_NAME  "authorization"
 
@@ -38,7 +39,7 @@
 // Configuration of the authorization plugin
 static bool resourceTokensEnabled_ = false;
 static bool userTokensEnabled_ = false;
-static bool enableAuditLogs_ = true;
+static bool enableAuditLogs_ = false;
 static std::unique_ptr<OrthancPlugins::IAuthorizationParser> authorizationParser_;
 static std::unique_ptr<OrthancPlugins::IAuthorizationService> authorizationService_;
 static std::unique_ptr<OrthancPlugins::PermissionParser> permissionParser_;
@@ -344,6 +345,43 @@
   return false;
 }
 
+static void RecordResourceAccessInternal(const OrthancPlugins::IAuthorizationService::UserProfile& profile,
+                                         const OrthancPlugins::IAuthorizationParser::AccessedResources& accesses,
+                                         const std::string& action,
+                                         const Json::Value& logData)
+{
+  for (OrthancPlugins::IAuthorizationParser::AccessedResources::const_iterator it = accesses.begin(); it != accesses.end(); ++it)
+  {
+    if (it->GetLevel() == OrthancPlugins::AccessLevel_Study)
+    {
+      RecordAuditLog(profile.userId, OrthancPluginResourceType_Study, it->GetOrthancId(), action, logData);
+    }
+  }
+}
+
+static void RecordResourceAccess(const OrthancPlugins::IAuthorizationService::UserProfile& profile,
+                                 const std::string& uri,
+                                 OrthancPluginHttpMethod method,
+                                 const OrthancPlugins::AssociativeArray& getArguments)
+{
+  // Identify the resource
+  OrthancPlugins::IAuthorizationParser::AccessedResources accesses;
+
+  if (authorizationParser_->Parse(accesses, uri, getArguments.GetMap()))
+  {
+    boost::smatch what;
+
+    // Identify the action
+    boost::regex archive("^/(patients|studies|series|instances)/([a-f0-9-]+)/(archive|media)$");
+    
+    if (boost::regex_match(uri, what, archive))
+    {
+      RecordResourceAccessInternal(profile, accesses, "download", Json::nullValue);
+    }
+  }
+
+}
+
 static bool TestRequiredPermissions(bool& hasUserRequiredPermissions, 
                                     const std::set<std::string>& requiredPermissions, 
                                     const OrthancPlugins::IAuthorizationService::UserProfile& profile,
@@ -367,6 +405,11 @@
       if (hasAuthorizedLabelsForResource)
       {
         LOG(INFO) << msg2 << " -> granted";
+
+        if (enableAuditLogs_)
+        {
+          RecordResourceAccess(profile, uri, method, getArguments);
+        }
       }
       else
       {
@@ -1185,6 +1228,14 @@
   }
 }
 
+void BulkModifyAnonymizeWithAuditLogs(OrthancPluginRestOutput* output,
+                                      const char* url,
+                                      const OrthancPluginHttpRequest* request)
+{
+  throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "Auth plugin: Not implemented: Currently unable to perform bulk modification/anonymization with audit logs enabled.");
+}
+
+
 void ModifyWithAuditLogs(OrthancPluginRestOutput* output,
                          const char* url,
                          const OrthancPluginHttpRequest* request)
@@ -1939,6 +1990,9 @@
         }
 #endif
 
+        enableAuditLogs_ = orthancFullConfiguration.GetBooleanValue("EnableAuditLogs", "false");
+
+
         pluginConfiguration.LookupSetOfStrings(uncheckedResources_, "UncheckedResources", false);
         pluginConfiguration.LookupListOfStrings(uncheckedFolders_, "UncheckedFolders", false);
 
@@ -2230,14 +2284,16 @@
             OrthancPlugins::RegisterRestCallback<ModifyWithAuditLogs>("/(patients|studies|series)/([^/]*)/modify", true);
             OrthancPlugins::RegisterRestCallback<LabelWithAuditLogs>("/(patients|studies|series)/([^/]*)/labels/([^/]*)", true);
             OrthancPlugins::RegisterRestCallback<BulkDeleteWithAuditLogs>("/tools/bulk-delete", true);
+            OrthancPlugins::RegisterRestCallback<BulkModifyAnonymizeWithAuditLogs>("/tools/bulk-modify", true);
+            OrthancPlugins::RegisterRestCallback<BulkModifyAnonymizeWithAuditLogs>("/tools/bulk-anonymize", true);
+
+            // Note: other "actions" that do not modify the data like download-archive are logged in the HTTP filter (see RecordResourceAccess())
 
             // TODO
-            // OrthancPlugins::RegisterRestCallback<BulkModifyWithAuditLogs>("/tools/bulk-modify", true);
             // /modalities/move
             // /modalities/store
             // /archive + create-archive
             // /media + create-media + create-media-extended
-            // /bulk-anonymize + bulkd-modify
 
           }
         }