Mercurial > hg > orthanc
changeset 6246:d70e4de0c847
OrthancPluginRecordAuditLog
line wrap: on
line diff
--- a/NEWS Fri Jul 11 17:33:28 2025 +0200 +++ b/NEWS Mon Jul 14 16:10:24 2025 +0200 @@ -32,6 +32,8 @@ DICOM resource from a plugin. - "OrthancPluginRegisterHttpAuthentication()" to install a custom callback to authenticate HTTP requests. + - "OrthancPluginRecordAuditLog()" to record an audit log in DB, provided + that the DB plugin supports it. * The OrthancPluginHttpRequest structure provides the payload of the possible HTTP authentication callback. * OrthancPluginCallRestApi() now also returns the body of DELETE requests:
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp Mon Jul 14 16:10:24 2025 +0200 @@ -1511,6 +1511,16 @@ { throw OrthancException(ErrorCode_NotImplemented); // Not supported } + + virtual void RecordAuditLog(const std::string& userId, + ResourceType resourceType, + const std::string& resourceId, + const std::string& action, + const void* logData, + size_t logDataSize) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); // Not supported + } };
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp Mon Jul 14 16:10:24 2025 +0200 @@ -1123,6 +1123,17 @@ { throw OrthancException(ErrorCode_NotImplemented); // Not supported } + + virtual void RecordAuditLog(const std::string& userId, + ResourceType resourceType, + const std::string& resourceId, + const std::string& action, + const void* logData, + size_t logDataSize) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); // Not supported + } + };
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Mon Jul 14 16:10:24 2025 +0200 @@ -2051,6 +2051,39 @@ throw OrthancException(ErrorCode_InternalError); } } + + virtual void RecordAuditLog(const std::string& userId, + ResourceType resourceType, + const std::string& resourceId, + const std::string& action, + const void* logData, + size_t logDataSize) ORTHANC_OVERRIDE + { + // In protobuf, bytes "may contain any arbitrary sequence of bytes no longer than 2^32" + // https://protobuf.dev/programming-guides/proto3/ + if (logDataSize > std::numeric_limits<uint32_t>::max()) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + if (database_.GetDatabaseCapabilities().HasAuditLogsSupport()) + { + DatabasePluginMessages::TransactionRequest request; + request.mutable_record_audit_log()->set_user_id(userId); + request.mutable_record_audit_log()->set_resource_type(Convert(resourceType)); + request.mutable_record_audit_log()->set_resource_id(resourceId); + request.mutable_record_audit_log()->set_action(action); + request.mutable_record_audit_log()->set_log_data(logData, logDataSize); + + ExecuteTransaction(DatabasePluginMessages::OPERATION_ENQUEUE_VALUE, request); + } + else + { + // This method shouldn't have been called + throw OrthancException(ErrorCode_InternalError); + } + } + }; @@ -2144,6 +2177,7 @@ dbCapabilities_.SetKeyValueStoresSupport(systemInfo.supports_key_value_stores()); dbCapabilities_.SetQueuesSupport(systemInfo.supports_queues()); dbCapabilities_.SetAttachmentCustomDataSupport(systemInfo.has_attachment_custom_data()); + dbCapabilities_.SetAuditLogsSupport(systemInfo.supports_audit_logs()); } open_ = true;
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Mon Jul 14 16:10:24 2025 +0200 @@ -4768,6 +4768,18 @@ } } + void OrthancPlugins::ApplyRecordAuditLog(const _OrthancPluginRecordAuditLog& parameters) + { + PImpl::ServerContextReference lock(*pimpl_); + + if (!lock.GetContext().GetIndex().HasAuditLogsSupport()) + { + throw OrthancException(ErrorCode_NotImplemented, "The database engine does not support audit logs"); + } + + lock.GetContext().GetIndex().RecordAuditLog(parameters.userId, Plugins::Convert(parameters.resourceType), parameters.resourceId, parameters.action, parameters.logData, parameters.logDataSize); + } + void OrthancPlugins::ApplyLoadDicomInstance(const _OrthancPluginLoadDicomInstance& params) { std::unique_ptr<IDicomInstance> target; @@ -5898,6 +5910,13 @@ return true; } + case _OrthancPluginService_RecordAuditLog: + { + const _OrthancPluginRecordAuditLog& p = *reinterpret_cast<const _OrthancPluginRecordAuditLog*>(parameters); + ApplyRecordAuditLog(p); + return true; + } + default: return false; }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h Mon Jul 14 16:10:24 2025 +0200 @@ -249,6 +249,8 @@ void ApplySetStableStatus(const _OrthancPluginSetStableStatus& parameters); + void ApplyRecordAuditLog(const _OrthancPluginRecordAuditLog& parameters); + void ComputeHash(_OrthancPluginService service, const void* parameters);
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Mon Jul 14 16:10:24 2025 +0200 @@ -502,6 +502,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_RecordAuditLog = 61, /* New in Orthanc 1.12.9 */ /* Registration of callbacks */ _OrthancPluginService_RegisterRestCallback = 1000, @@ -10470,6 +10471,50 @@ } + typedef struct + { + const char* userId; + OrthancPluginResourceType resourceType; + const char* resourceId; + const char* action; + const void* logData; + uint32_t logDataSize; + } _OrthancPluginRecordAuditLog; + + + /** + * @brief Record an audit log + * + * Record an audit log (provided that a database plugin provides the feature). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param userId A string uniquely identifying the user or entity that is executing the action on the resource. + * @param resourceType The type of the resource this log relates to. + * @param resourceId The resource this log relates to. + * @param action The action that is performed on the resource. + * @param logData A pointer to custom log data. + * @param logDataSize The size of custom log data. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRecordAuditLog( + OrthancPluginContext* context, + const char* userId, + OrthancPluginResourceType resourceType, + const char* resourceId, + const char* action, + const void* logData, + uint32_t logDataSize) + { + _OrthancPluginRecordAuditLog m; + m.userId = userId; + m.resourceType = resourceType; + m.resourceId = resourceId; + m.action = action; + m.logData = logData; + m.logDataSize = logDataSize; + context->InvokeService(context, _OrthancPluginService_RecordAuditLog, &m); + } + + #ifdef __cplusplus } #endif
--- a/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto Mon Jul 14 16:10:24 2025 +0200 @@ -175,6 +175,7 @@ bool supports_key_value_stores = 10; // New in Orthanc 1.12.8 bool supports_queues = 11; // New in Orthanc 1.12.8 bool has_attachment_custom_data = 12; // New in Orthanc 1.12.8 + bool supports_audit_logs = 13; // New in Orthanc 1.12.9 } } @@ -339,6 +340,7 @@ OPERATION_GET_QUEUE_SIZE = 59; // New in Orthanc 1.12.8 OPERATION_GET_ATTACHMENT_CUSTOM_DATA = 60; // New in Orthanc 1.12.8 OPERATION_SET_ATTACHMENT_CUSTOM_DATA = 61; // New in Orthanc 1.12.8 + OPERATION_RECORD_AUDIT_LOG = 62; // New in Orthanc 1.12.9 } @@ -1095,6 +1097,20 @@ } } +message RecordAuditLog { + message Request { + string user_id = 1; + ResourceType resource_type = 2; + string resource_id = 3; + string action = 4; + bytes log_data = 5; + } + + message Response { + } +} + + message TransactionRequest { sfixed64 transaction = 1; TransactionOperation operation = 2; @@ -1161,6 +1177,7 @@ GetQueueSize.Request get_queue_size = 159; GetAttachmentCustomData.Request get_attachment_custom_data = 160; SetAttachmentCustomData.Request set_attachment_custom_data = 161; + RecordAuditLog.Request record_audit_log = 162; } message TransactionResponse { @@ -1226,6 +1243,7 @@ GetQueueSize.Response get_queue_size = 159; GetAttachmentCustomData.Response get_attachment_custom_data = 160; SetAttachmentCustomData.Response set_attachment_custom_data = 161; + RecordAuditLog.Response record_audit_log = 162; } enum RequestType {
--- a/OrthancServer/Sources/Database/IDatabaseWrapper.h Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h Mon Jul 14 16:10:24 2025 +0200 @@ -59,6 +59,7 @@ bool hasAttachmentCustomDataSupport_; bool hasKeyValueStoresSupport_; bool hasQueuesSupport_; + bool hasAuditLogsSupport_; public: Capabilities() : @@ -72,7 +73,8 @@ hasExtendedChanges_(false), hasAttachmentCustomDataSupport_(false), hasKeyValueStoresSupport_(false), - hasQueuesSupport_(false) + hasQueuesSupport_(false), + hasAuditLogsSupport_(false) { } @@ -185,6 +187,17 @@ { return hasQueuesSupport_; } + + void SetAuditLogsSupport(bool value) + { + hasAuditLogsSupport_ = value; + } + + bool HasAuditLogsSupport() const + { + return hasAuditLogsSupport_; + } + }; @@ -469,6 +482,15 @@ // New in Orthanc 1.12.8, for statistics only virtual uint64_t GetQueueSize(const std::string& queueId) = 0; + + // New in Orthanc 1.12.9 + virtual void RecordAuditLog(const std::string& userId, + ResourceType resourceType, + const std::string& resourceId, + const std::string& action, + const void* logData, + size_t logDataSize) = 0; + };
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Mon Jul 14 16:10:24 2025 +0200 @@ -2340,6 +2340,17 @@ s.Step(); return s.ColumnInt64(0); } + + virtual void RecordAuditLog(const std::string& userId, + ResourceType resourceType, + const std::string& resourceId, + const std::string& action, + const void* logData, + size_t logDataSize) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); // Not supported + } + }; @@ -2587,6 +2598,7 @@ dbCapabilities_.SetKeyValueStoresSupport(true); dbCapabilities_.SetQueuesSupport(true); dbCapabilities_.SetAttachmentCustomDataSupport(true); + dbCapabilities_.SetAuditLogsSupport(false); db_.Open(path); } @@ -2604,6 +2616,7 @@ dbCapabilities_.SetKeyValueStoresSupport(true); dbCapabilities_.SetQueuesSupport(true); dbCapabilities_.SetAttachmentCustomDataSupport(true); + dbCapabilities_.SetAuditLogsSupport(false); db_.OpenInMemory(); }
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Mon Jul 14 16:10:24 2025 +0200 @@ -3226,6 +3226,12 @@ return db_.GetDatabaseCapabilities().HasQueuesSupport(); } + bool StatelessDatabaseOperations::HasAuditLogsSupport() + { + boost::shared_lock<boost::shared_mutex> lock(mutex_); + return db_.GetDatabaseCapabilities().HasAuditLogsSupport(); + } + void StatelessDatabaseOperations::ExecuteCount(uint64_t& count, const FindRequest& request) { @@ -3755,4 +3761,48 @@ throw OrthancException(ErrorCode_BadSequenceOfCalls); } } + + void StatelessDatabaseOperations::RecordAuditLog(const std::string& userId, + ResourceType resourceType, + const std::string& resourceId, + const std::string& action, + const void* logData, + size_t logDataSize) + { + class Operations : public IReadWriteOperations + { + private: + const std::string& userId_; + ResourceType resourceType_; + const std::string& resourceId_; + const std::string& action_; + const void* logData_; + size_t logDataSize_; + + public: + Operations(const std::string& userId, + ResourceType resourceType, + const std::string& resourceId, + const std::string& action, + const void* logData, + size_t logDataSize) : + userId_(userId), + resourceType_(resourceType), + resourceId_(resourceId), + action_(action), + logData_(logData), + logDataSize_(logDataSize) + { + } + + virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE + { + transaction.RecordAuditLog(userId_, resourceType_, resourceId_, action_, logData_, logDataSize_); + } + }; + + Operations operations(userId, resourceType, resourceId, action, logData, logDataSize); + Apply(operations); + } + }
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Mon Jul 14 16:10:24 2025 +0200 @@ -491,6 +491,17 @@ { return transaction_.SetAttachmentCustomData(attachmentUuid, customData, customDataSize); } + + void RecordAuditLog(const std::string& userId, + ResourceType resourceType, + const std::string& resourceId, + const std::string& action, + const void* logData, + size_t logDataSize) + { + return transaction_.RecordAuditLog(userId, resourceType, resourceId, action, logData, logDataSize); + } + }; @@ -620,7 +631,9 @@ bool HasKeyValueStoresSupport(); bool HasQueuesSupport(); - + + bool HasAuditLogsSupport(); + void GetExportedResources(Json::Value& target, int64_t since, uint32_t limit); @@ -879,5 +892,12 @@ const std::string& GetValue() const; }; + + void RecordAuditLog(const std::string& userId, + ResourceType resourceType, + const std::string& resourceId, + const std::string& action, + const void* logData, + size_t logDataSize); }; }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Fri Jul 11 17:33:28 2025 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Mon Jul 14 16:10:24 2025 +0200 @@ -97,6 +97,7 @@ static const char* const HAS_EXTENDED_CHANGES = "HasExtendedChanges"; static const char* const HAS_KEY_VALUE_STORES = "HasKeyValueStores"; static const char* const HAS_QUEUES = "HasQueues"; + static const char* const HAS_AUDITS_LOGS = "HasAuditLogs"; static const char* const HAS_EXTENDED_FIND = "HasExtendedFind"; static const char* const READ_ONLY = "ReadOnly"; @@ -215,6 +216,7 @@ result[CAPABILITIES][HAS_EXTENDED_FIND] = OrthancRestApi::GetIndex(call).HasFindSupport(); result[CAPABILITIES][HAS_KEY_VALUE_STORES] = OrthancRestApi::GetIndex(call).HasKeyValueStoresSupport(); result[CAPABILITIES][HAS_QUEUES] = OrthancRestApi::GetIndex(call).HasQueuesSupport(); + result[CAPABILITIES][HAS_AUDITS_LOGS] = OrthancRestApi::GetIndex(call).HasAuditLogsSupport(); call.GetOutput().AnswerJson(result); }