changeset 260:95b22ee07bc3 inbox

export audit-logs in csv
author Alain Mazy <am@orthanc.team>
date Thu, 24 Jul 2025 19:49:26 +0200
parents a2f7066fd932
children 624172833314
files Plugin/Plugin.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h
diffstat 3 files changed, 109 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/Plugin/Plugin.cpp	Thu Jul 24 17:59:45 2025 +0200
+++ b/Plugin/Plugin.cpp	Thu Jul 24 19:49:26 2025 +0200
@@ -1367,8 +1367,30 @@
 {
   OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
+  bool isOutputCsv = false;
+
+  OrthancPlugins::HttpHeaders requestHeaders;
+  OrthancPlugins::GetHttpHeaders(requestHeaders, request);
+
+  OrthancPlugins::GetArguments getArguments;
+  OrthancPlugins::GetGetArguments(getArguments, request);
+
+  if (getArguments.find("format") != getArguments.end())
+  {
+    isOutputCsv = getArguments["format"] == "csv";
+  }
+  
+  if (!isOutputCsv && (requestHeaders.find("accept") != requestHeaders.end()))
+  {
+    std::string acceptHeader = requestHeaders["accept"];
+    Orthanc::Toolbox::ToLowerCase(acceptHeader);
+    
+    isOutputCsv = acceptHeader.find("text/csv") != std::string::npos;
+  }
+
   OrthancPlugins::RestApiClient coreApi("/plugins/postgresql/audit-logs", request);
   coreApi.SetAfterPlugins(true);
+  coreApi.SetRequestHeader("Accept", "application/json"); // the postgresql plugin only knows about the json format
 
   if (request->method != OrthancPluginHttpMethod_Get)
   {
@@ -1395,23 +1417,54 @@
       }
     }
 
-    OrthancPlugins::AnswerJson(response, output);
-  }
+    if (!isOutputCsv)
+    {
+      OrthancPlugins::AnswerJson(response, output);
+    }
+    else
+    {
+      std::vector<std::string> lines;
 
-  // else
-  // {
-  //   OrthancPlugins::IAuthorizationService::UserProfile profile;
-  //   std::string userId;
+      std::vector<std::string> firstLineColumns;
+      firstLineColumns.push_back("Timestamp");
+      firstLineColumns.push_back("UserId");
+      firstLineColumns.push_back("UserName");
+      firstLineColumns.push_back("ResourceId");
+      firstLineColumns.push_back("Action");
+      firstLineColumns.push_back("LogData");
+
+      std::string firstLine;
+      Orthanc::Toolbox::JoinStrings(firstLine, firstLineColumns,";");
+      lines.push_back(firstLine);
 
-  //   if (GetUserProfileInternal(profile, request) && !profile.userId.empty())
-  //   {
-  //     userId = profile.userId;  
-  //   }
-  //   else
-  //   {
-  //     throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess, "Auth plugin: no user profile or UserData found, unable to delete/put label with audit logs enabled.");
-  //   }
-    
+      for (Json::ArrayIndex i = 0; i < response.size(); ++i)
+      {
+        std::vector<std::string> lineColumns;
+        std::string line;
+
+        const Json::Value& log = response[i];
+        lineColumns.push_back(log["Timestamp"].asString());
+        lineColumns.push_back(log["UserId"].asString());
+        lineColumns.push_back(log["UserName"].asString());
+        lineColumns.push_back(log["ResourceId"].asString());
+        lineColumns.push_back(log["Action"].asString());
+        
+        std::string logData;
+        Orthanc::Toolbox::WriteFastJson(logData, log["LogData"]);
+        boost::replace_all(logData, "\n", "");
+        lineColumns.push_back(logData);
+
+        Orthanc::Toolbox::JoinStrings(line, lineColumns,";");
+        lines.push_back(line);
+      }
+      
+      std::string csv;
+      Orthanc::Toolbox::JoinStrings(csv, lines, "\n");
+
+      OrthancPluginSetHttpHeader(context, output, "Content-disposition", "filename=\"audit-logs.csv\"");
+      OrthancPlugins::AnswerString(csv, "text/csv", output);
+    }
+  }
 }
 
 
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Thu Jul 24 17:59:45 2025 +0200
+++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Thu Jul 24 19:49:26 2025 +0200
@@ -266,6 +266,16 @@
 #endif
 
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  void MemoryBuffer::AssignJson(const Json::Value& value)
+  {
+    std::string s;
+    WriteFastJson(s, value);
+    Assign(s);
+  }
+#endif
+
+
   void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other)
   {
     Clear();
@@ -4112,6 +4122,16 @@
   }
 #endif
 
+  void GetGetArguments(GetArguments& result, const OrthancPluginHttpRequest* request)
+  {
+    result.clear();
+
+    for (uint32_t i = 0; i < request->getCount; ++i)
+    {
+      result[request->getKeys[i]] = request->getValues[i];
+    }    
+  }
+
   void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request)
   {
     result.clear();
@@ -4238,6 +4258,15 @@
 
 
 #if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+  void RestApiClient::SetRequestHeader(const std::string& key,
+                                       const std::string& value)
+  {
+    requestHeaders_[key] = value;
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
   bool RestApiClient::Execute()
   {
     if (requestBody_.size() > 0xffffffffu)
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Thu Jul 24 17:59:45 2025 +0200
+++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Thu Jul 24 19:49:26 2025 +0200
@@ -180,6 +180,8 @@
 {
   typedef std::map<std::string, std::string>  HttpHeaders;
 
+  typedef std::map<std::string, std::string>  GetArguments;
+
   typedef void (*RestCallback) (OrthancPluginRestOutput* output,
                                 const char* url,
                                 const OrthancPluginHttpRequest* request);
@@ -231,6 +233,10 @@
     void Assign(const std::string& s);
 #endif
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void AssignJson(const Json::Value& value);
+#endif
+
     // This transfers ownership from "other" to "this"
     void Assign(OrthancPluginMemoryBuffer& other);
 
@@ -1427,6 +1433,9 @@
 // helper method to re-serialize the get arguments from the SDK into a string
 void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request);
 
+// helper method to convert Get arguments from the plugin SDK to a std::map
+void GetGetArguments(GetArguments& result, const OrthancPluginHttpRequest* request);
+
 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
   class IWebDavCollection : public boost::noncopyable
   {
@@ -1584,6 +1593,9 @@
     void AddRequestHeader(const std::string& key,
                           const std::string& value);
 
+    void SetRequestHeader(const std::string& key,
+                          const std::string& value);
+
     const HttpHeaders& GetRequestHeaders() const
     {
       return requestHeaders_;