# HG changeset patch # User Alain Mazy # Date 1753379366 -7200 # Node ID 95b22ee07bc31457ce6799acc185d150e900a243 # Parent a2f7066fd932c887eea79652d9d0bedcab53b3f0 export audit-logs in csv diff -r a2f7066fd932 -r 95b22ee07bc3 Plugin/Plugin.cpp --- 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 lines; - // else - // { - // OrthancPlugins::IAuthorizationService::UserProfile profile; - // std::string userId; + std::vector 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 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); + } + } } diff -r a2f7066fd932 -r 95b22ee07bc3 Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp --- 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) diff -r a2f7066fd932 -r 95b22ee07bc3 Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h --- 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 HttpHeaders; + typedef std::map 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_;