Mercurial > hg > orthanc-authorization
changeset 246:26ca67fe0659 inbox
audit-logs first part
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Mon, 07 Jul 2025 10:55:00 +0200 |
parents | a56513c56d0d |
children | ff21632f3ab6 |
files | NEWS Plugin/AuthorizationWebService.cpp Plugin/Enumerations.cpp Plugin/Enumerations.h Plugin/IAuthorizationService.h Plugin/Plugin.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h |
diffstat | 8 files changed, 679 insertions(+), 53 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Tue Jul 01 15:29:03 2025 +0200 +++ b/NEWS Mon Jul 07 10:55:00 2025 +0200 @@ -13,6 +13,9 @@ HTTP request. * The User profile can now contain a "groups" field if the auth-service provides it. +* The User profile can now contain an "id" field if the auth-service + provides it. +* New experimental feature: audit-logs (TODO) 2025-06-11 - v 0.9.3
--- a/Plugin/AuthorizationWebService.cpp Tue Jul 01 15:29:03 2025 +0200 +++ b/Plugin/AuthorizationWebService.cpp Mon Jul 07 10:55:00 2025 +0200 @@ -35,6 +35,7 @@ static const char* AUTHORIZED_LABELS = "authorized-labels"; static const char* USER_NAME = "name"; static const char* GROUPS = "groups"; + static const char* USER_ID = "user-id"; @@ -341,6 +342,7 @@ { jsonProfile = Json::objectValue; jsonProfile[USER_NAME] = profile.name; + jsonProfile[USER_ID] = profile.userId; Orthanc::SerializationToolbox::WriteSetOfStrings(jsonProfile, profile.authorizedLabels, AUTHORIZED_LABELS); Orthanc::SerializationToolbox::WriteSetOfStrings(jsonProfile, profile.permissions, PERMISSIONS); Orthanc::SerializationToolbox::WriteSetOfStrings(jsonProfile, profile.groups, GROUPS); @@ -379,6 +381,11 @@ profile.groups.insert(jsonProfile[GROUPS][i].asString()); } } + + if (jsonProfile.isMember(USER_ID) && jsonProfile[USER_ID].isString()) + { + profile.userId = jsonProfile[USER_ID].asString(); + } }
--- a/Plugin/Enumerations.cpp Tue Jul 01 15:29:03 2025 +0200 +++ b/Plugin/Enumerations.cpp Mon Jul 07 10:55:00 2025 +0200 @@ -84,4 +84,31 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, std::string("Invalid access level: ") + tmp); } } + + OrthancPluginResourceType StringToResourceType(const char* type) + { + std::string s(type); + Orthanc::Toolbox::ToUpperCase(s); + + if (s == "PATIENT" || s == "PATIENTS") + { + return OrthancPluginResourceType_Patient; + } + else if (s == "STUDY" || s == "STUDIES") + { + return OrthancPluginResourceType_Study; + } + else if (s == "SERIES") + { + return OrthancPluginResourceType_Series; + } + else if (s == "INSTANCE" || s == "IMAGE" || + s == "INSTANCES" || s == "IMAGES") + { + return OrthancPluginResourceType_Instance; + } + + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, std::string("Invalid resource type '") + type + "'"); + } + }
--- a/Plugin/Enumerations.h Tue Jul 01 15:29:03 2025 +0200 +++ b/Plugin/Enumerations.h Mon Jul 07 10:55:00 2025 +0200 @@ -21,6 +21,7 @@ #pragma once #include <Enumerations.h> +#include <orthanc/OrthancCPlugin.h> namespace OrthancPlugins { @@ -46,4 +47,6 @@ std::string EnumerationToString(AccessLevel level); AccessLevel StringToAccessLevel(const std::string& level); + + OrthancPluginResourceType StringToResourceType(const char* type); }
--- a/Plugin/IAuthorizationService.h Tue Jul 01 15:29:03 2025 +0200 +++ b/Plugin/IAuthorizationService.h Mon Jul 07 10:55:00 2025 +0200 @@ -61,6 +61,7 @@ struct UserProfile { std::string name; + std::string userId; std::set<std::string> permissions; std::set<std::string> authorizedLabels; std::set<std::string> groups;
--- a/Plugin/Plugin.cpp Tue Jul 01 15:29:03 2025 +0200 +++ b/Plugin/Plugin.cpp Mon Jul 07 10:55:00 2025 +0200 @@ -38,6 +38,7 @@ // Configuration of the authorization plugin static bool resourceTokensEnabled_ = false; static bool userTokensEnabled_ = false; +static bool enableAuditLogs_ = true; static std::unique_ptr<OrthancPlugins::IAuthorizationParser> authorizationParser_; static std::unique_ptr<OrthancPlugins::IAuthorizationService> authorizationService_; static std::unique_ptr<OrthancPlugins::PermissionParser> permissionParser_; @@ -64,6 +65,37 @@ OrthancPluginSendHttpStatus(context, output, 403, message, strlen(message)); } +static const char* KEY_USER_DATA = "UserData"; +static const char* KEY_USER_ID = "AuditLogsUserId"; + +static bool GetUserIdFromUserData(std::string& userId, const Json::Value& payload) +{ + if (payload.isMember(KEY_USER_DATA) && payload[KEY_USER_DATA].isObject() + && payload[KEY_USER_DATA].isMember(KEY_USER_ID) && payload[KEY_USER_DATA][KEY_USER_ID].isString()) + { + userId = payload[KEY_USER_DATA][KEY_USER_ID].asString(); + return true; + } + return false; +} + +static void SetUserIdInUserdata(Json::Value& payload, const std::string& userId) +{ + if (!payload.isMember(KEY_USER_DATA)) + { + payload[KEY_USER_DATA] = Json::objectValue; + } + payload[KEY_USER_DATA][KEY_USER_ID] = userId; +} + +static void RecordAuditLog(const std::string& userId, + const OrthancPluginResourceType& resourceType, + const std::string& resourceId, + const std::string& action, + const Json::Value& logData) +{ + LOG(WARNING) << "AUDIT-LOG: " << userId << " / " << action << " on " << resourceType << ":" << resourceId << ", " << logData.toStyledString(); +} class TokenAndValue @@ -495,34 +527,96 @@ { try { - if (authorizationParser_.get() == NULL) + if (authorizationParser_.get() == NULL || !enableAuditLogs_) { return OrthancPluginErrorCode_Success; } - - if (changeType == OrthancPluginChangeType_Deleted) + + switch(changeType) { - switch (resourceType) + case OrthancPluginChangeType_JobSuccess: { - case OrthancPluginResourceType_Patient: - authorizationParser_->Invalidate(Orthanc::ResourceType_Patient, resourceId); - break; + Json::Value job; + if (OrthancPlugins::RestApiGet(job, std::string("/jobs/") + resourceId, false)) + { + if (job["Type"].asString() == "ResourceModification") + { + Json::Value jobContent = job["Content"]; + std::string sourceResourceId = jobContent["ParentResources"][0].asString(); + std::string modifiedResourceId = jobContent["ID"].asString(); + OrthancPluginResourceType resourceType = OrthancPlugins::StringToResourceType(jobContent["Type"].asString().c_str()); + + bool isAnonymization = jobContent.isMember("IsAnonymization") && jobContent["IsAnonymization"].asBool(); + LOG(WARNING) << jobContent.toStyledString(); + + if (isAnonymization) + { + std::string userId; + if (GetUserIdFromUserData(userId, job)) + { + // attach a log to the source study + Json::Value logData; + logData["ModifiedResourceId"] = modifiedResourceId; + logData["ModifiedResourceType"] = resourceType; + + RecordAuditLog(userId, + resourceType, + sourceResourceId, + (isAnonymization ? "success-anonymization" : "success-modification-job"), + logData); + + // attach a log to the modified study + if (sourceResourceId != modifiedResourceId) + { + Json::Value logData; + logData["SourceResourceId"] = sourceResourceId; + logData["SourceResourceType"] = resourceType; - case OrthancPluginResourceType_Study: - authorizationParser_->Invalidate(Orthanc::ResourceType_Study, resourceId); - break; + RecordAuditLog(userId, + resourceType, + modifiedResourceId, + (isAnonymization ? "new-study-from-anonymization-job" : "new-study-from-modification-job"), + logData); + } + } + } + } - case OrthancPluginResourceType_Series: - authorizationParser_->Invalidate(Orthanc::ResourceType_Series, resourceId); - break; + } + + return OrthancPluginErrorCode_Success; + } + case OrthancPluginChangeType_JobFailure: + { + return OrthancPluginErrorCode_Success; + } - case OrthancPluginResourceType_Instance: - authorizationParser_->Invalidate(Orthanc::ResourceType_Instance, resourceId); - break; + case OrthancPluginChangeType_Deleted: + { + switch (resourceType) + { + case OrthancPluginResourceType_Patient: + authorizationParser_->Invalidate(Orthanc::ResourceType_Patient, resourceId); + break; + + case OrthancPluginResourceType_Study: + authorizationParser_->Invalidate(Orthanc::ResourceType_Study, resourceId); + break; - default: - break; + case OrthancPluginResourceType_Series: + authorizationParser_->Invalidate(Orthanc::ResourceType_Series, resourceId); + break; + + case OrthancPluginResourceType_Instance: + authorizationParser_->Invalidate(Orthanc::ResourceType_Instance, resourceId); + break; + + default: + break; + } } + default: + return OrthancPluginErrorCode_Success; } return OrthancPluginErrorCode_Success; @@ -952,6 +1046,109 @@ ToolsFindOrCountResources(output, url, request, "/tools/count-resources", false); } +void UploadInstancesWithAuditLogs(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + + // always forward to core + OrthancPlugins::RestApiClient coreApi(url, request); + coreApi.Forward(context, output); + + if (request->method == OrthancPluginHttpMethod_Post) + { + OrthancPlugins::IAuthorizationService::UserProfile profile; + Json::Value coreResponse; + + if (GetUserProfileInternal(profile, request) && coreApi.GetAnswerJson(coreResponse)) + { + RecordAuditLog(profile.userId, OrthancPluginResourceType_Study, coreResponse["ParentStudy"].asString(), "uploaded-instance", std::string()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess, "Auth plugin: no user profile found, unable to handle POST to /instances with audit logs enabled."); + } + } +} + + +void ModifyAnonymizeWithAuditLogs(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request, + bool isModification) +{ + OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + OrthancPluginResourceType resourceType = OrthancPlugins::StringToResourceType(request->groups[0]); + std::string resourceId = request->groups[1]; + + OrthancPlugins::RestApiClient coreApi(url, request); + + Json::Value payload; + if (!OrthancPlugins::ReadJson(payload, request->body, request->bodySize)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "A JSON payload was expected"); + } + + // Either there is a userId in UserData or the request comes from a user with profile + OrthancPlugins::IAuthorizationService::UserProfile profile; + std::string userId; + + LOG(WARNING) << payload.toStyledString(); + + if (GetUserProfileInternal(profile, request) && !profile.userId.empty()) + { + userId = profile.userId; + } + else if (!GetUserIdFromUserData(userId, payload)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess, "Auth plugin: no user profile or UserData found, unable to handle anonymize/modify with audit logs enabled."); + } + + if ((payload.isMember("Synchronous") && !payload["Synchronous"].asBool()) + || (payload.isMember("Asynchronous") && !payload["Asynchronous"].asBool())) + { + // add UserData to the job payload to know who has modified the data. The handling of the log will then happen in the OnChange handler + SetUserIdInUserdata(payload, userId); + + // in any case, record that this resource is being modified/anonymized and record the payload + RecordAuditLog(userId, + resourceType, + resourceId, + (isModification ? "start-modification-job" : "start-anonymization-job"), + payload); + + coreApi.Forward(context, output); + } + else + { + Json::Value coreResponse; + + // if it is synchronous, perform the modification and record the log directly + coreApi.Forward(context, output); + + if (coreApi.GetAnswerJson(coreResponse)) + { + LOG(WARNING) << "TODO AUDIT-LOG " << coreResponse.toStyledString(); // TODO + } + } +} + +void ModifyWithAuditLogs(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + ModifyAnonymizeWithAuditLogs(output, url, request, true); +} + +void AnonymizeWithAuditLogs(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + ModifyAnonymizeWithAuditLogs(output, url, request, false); +} + + void ToolsLabels(OrthancPluginRestOutput* output, const char* /*url*/, const OrthancPluginHttpRequest* request) @@ -1262,6 +1459,11 @@ jsonProfile["groups"].append(*it); } + if (!profile.userId.empty()) + { + jsonProfile["user-id"] = profile.userId; + } + OrthancPlugins::AnswerJson(jsonProfile, output); } } @@ -1706,7 +1908,7 @@ (new OrthancPlugins::CachedAuthorizationService (webService.release(), factory)); - if (!urlTokenValidation.empty()) + if (!urlTokenValidation.empty() || enableAuditLogs_) { OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); } @@ -1747,6 +1949,13 @@ OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceList>("/patients/([^/]*)/instances", true); OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceList>("/patients/([^/]*)/series", true); OrthancPlugins::RegisterRestCallback<FilterLabelsFromResourceList>("/patients/([^/]*)/studies", true); + + if (enableAuditLogs_) + { + OrthancPlugins::RegisterRestCallback<UploadInstancesWithAuditLogs>("/instances", true); + OrthancPlugins::RegisterRestCallback<AnonymizeWithAuditLogs>("/(patients|studies|series)/([^/]*)/anonymize", true); + OrthancPlugins::RegisterRestCallback<ModifyWithAuditLogs>("/(patients|studies|series)/([^/]*)/modify", true); + } } if (!urlTokenCreationBase.empty())
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Jul 01 15:29:03 2025 +0200 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Mon Jul 07 10:55:00 2025 +0200 @@ -220,28 +220,6 @@ } -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - MemoryBuffer::MemoryBuffer(const void* buffer, - size_t size) - { - uint32_t s = static_cast<uint32_t>(size); - if (static_cast<size_t>(s) != size) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); - } - else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) != - OrthancPluginErrorCode_Success) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); - } - else - { - memcpy(buffer_.data, buffer, size); - } - } -#endif - - void MemoryBuffer::Clear() { if (buffer_.data != NULL) @@ -253,6 +231,41 @@ } +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void MemoryBuffer::Assign(const void* buffer, + size_t size) + { + uint32_t s = static_cast<uint32_t>(size); + if (static_cast<size_t>(s) != size) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + + Clear(); + + if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) != + OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + else + { + if (size > 0) + { + memcpy(buffer_.data, buffer, size); + } + } + } +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void MemoryBuffer::Assign(const std::string& s) + { + Assign(s.empty() ? NULL : s.c_str(), s.size()); + } +#endif + + void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other) { Clear(); @@ -673,7 +686,7 @@ { OrthancString str; str.Assign(OrthancPluginDicomBufferToJson - (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength)); + (GetGlobalContext(), reinterpret_cast<const char*>(GetData()), GetSize(), format, flags, maxStringLength)); str.ToJson(target); } @@ -1566,7 +1579,7 @@ { if (!answer.IsEmpty()) { - result.assign(answer.GetData(), answer.GetSize()); + result.assign(reinterpret_cast<const char*>(answer.GetData()), answer.GetSize()); } return true; } @@ -2052,6 +2065,26 @@ DoPost(target, index, uri, body, headers)); } + bool OrthancPeers::DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const + { + MemoryBuffer buffer; + + if (DoPost(buffer, index, uri, body, headers, timeout)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + bool OrthancPeers::DoPost(Json::Value& target, size_t index, @@ -2099,6 +2132,17 @@ const std::string& body, const HttpHeaders& headers) const { + return DoPost(target, index, uri, body, headers, timeout_); + } + + + bool OrthancPeers::DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const + { if (index >= index_.size()) { ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); @@ -2117,7 +2161,7 @@ OrthancPluginErrorCode code = OrthancPluginCallPeerApi (GetGlobalContext(), *answer, NULL, &status, peers_, static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(), - pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_); + pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout); if (code == OrthancPluginErrorCode_Success) { @@ -4168,6 +4212,11 @@ { path_ += "?" + getArguments; } + + if (request->bodySize > 0 && request->body != NULL) + { + requestBody_.assign(reinterpret_cast<const char*>(request->body), request->bodySize); + } } #endif @@ -4316,4 +4365,209 @@ } } #endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + KeyValueStore::Iterator::Iterator(OrthancPluginKeysValuesIterator *iterator) : + iterator_(iterator) + { + if (iterator_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + KeyValueStore::Iterator::~Iterator() + { + OrthancPluginFreeKeysValuesIterator(OrthancPlugins::GetGlobalContext(), iterator_); + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + bool KeyValueStore::Iterator::Next() + { + uint8_t done; + OrthancPluginErrorCode code = OrthancPluginKeysValuesIteratorNext(OrthancPlugins::GetGlobalContext(), &done, iterator_); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + else + { + return (done != 0); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + std::string KeyValueStore::Iterator::GetKey() const + { + const char* s = OrthancPluginKeysValuesIteratorGetKey(OrthancPlugins::GetGlobalContext(), iterator_); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + else + { + return s; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + void KeyValueStore::Iterator::GetValue(std::string& value) const + { + OrthancPlugins::MemoryBuffer valueBuffer; + OrthancPluginErrorCode code = OrthancPluginKeysValuesIteratorGetValue(OrthancPlugins::GetGlobalContext(), *valueBuffer, iterator_); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + else + { + valueBuffer.ToString(value); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + void KeyValueStore::Store(const std::string& key, + const void* value, + size_t valueSize) + { + if (static_cast<size_t>(static_cast<uint32_t>(valueSize)) != valueSize) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + + OrthancPluginErrorCode code = OrthancPluginStoreKeyValue(OrthancPlugins::GetGlobalContext(), storeId_.c_str(), + key.c_str(), value, static_cast<uint32_t>(valueSize)); + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + bool KeyValueStore::GetValue(std::string& value, + const std::string& key) + { + uint8_t found = false; + OrthancPlugins::MemoryBuffer valueBuffer; + OrthancPluginErrorCode code = OrthancPluginGetKeyValue(OrthancPlugins::GetGlobalContext(), &found, + *valueBuffer, storeId_.c_str(), key.c_str()); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + else if (found) + { + valueBuffer.ToString(value); + return true; + } + else + { + return false; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + void KeyValueStore::DeleteKey(const std::string& key) + { + OrthancPluginErrorCode code = OrthancPluginDeleteKeyValue(OrthancPlugins::GetGlobalContext(), + storeId_.c_str(), key.c_str()); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + KeyValueStore::Iterator* KeyValueStore::CreateIterator() + { + return new Iterator(OrthancPluginCreateKeysValuesIterator(OrthancPlugins::GetGlobalContext(), storeId_.c_str())); + } +#endif + + +#if HAS_ORTHANC_PLUGIN_QUEUES == 1 + void Queue::Enqueue(const void* value, + size_t valueSize) + { + if (static_cast<size_t>(static_cast<uint32_t>(valueSize)) != valueSize) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + + OrthancPluginErrorCode code = OrthancPluginEnqueueValue(OrthancPlugins::GetGlobalContext(), + queueId_.c_str(), value, static_cast<uint32_t>(valueSize)); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_QUEUES == 1 + bool Queue::DequeueInternal(std::string& value, + OrthancPluginQueueOrigin origin) + { + uint8_t found = false; + OrthancPlugins::MemoryBuffer valueBuffer; + + OrthancPluginErrorCode code = OrthancPluginDequeueValue(OrthancPlugins::GetGlobalContext(), &found, + *valueBuffer, queueId_.c_str(), origin); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + else if (found) + { + valueBuffer.ToString(value); + return true; + } + else + { + return false; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_QUEUES == 1 + uint64_t Queue::GetSize() + { + uint64_t size = 0; + OrthancPluginErrorCode code = OrthancPluginGetQueueSize(OrthancPlugins::GetGlobalContext(), queueId_.c_str(), &size); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + else + { + return size; + } + } +#endif }
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Jul 01 15:29:03 2025 +0200 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Mon Jul 07 10:55:00 2025 +0200 @@ -134,6 +134,14 @@ # define HAS_ORTHANC_PLUGIN_LOG_MESSAGE 0 #endif +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 8) +# define HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES 1 +# define HAS_ORTHANC_PLUGIN_QUEUES 1 +#else +# define HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES 0 +# define HAS_ORTHANC_PLUGIN_QUEUES 0 +#endif + // Macro to tag a function as having been deprecated #if (__cplusplus >= 201402L) // C++14 @@ -203,13 +211,6 @@ public: MemoryBuffer(); -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - // This constructor makes a copy of the given buffer in the memory - // handled by the Orthanc core - MemoryBuffer(const void* buffer, - size_t size); -#endif - ~MemoryBuffer() { Clear(); @@ -220,6 +221,16 @@ return &buffer_; } +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + // Copy of the given buffer into the memory managed by the Orthanc core + void Assign(const void* buffer, + size_t size); +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void Assign(const std::string& s); +#endif + // This transfers ownership from "other" to "this" void Assign(OrthancPluginMemoryBuffer& other); @@ -227,11 +238,11 @@ OrthancPluginMemoryBuffer Release(); - const char* GetData() const + const void* GetData() const { if (buffer_.size > 0) { - return reinterpret_cast<const char*>(buffer_.data); + return buffer_.data; } else { @@ -855,6 +866,13 @@ const HttpHeaders& headers) const; bool DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const; + + bool DoPost(MemoryBuffer& target, const std::string& name, const std::string& uri, const std::string& body, @@ -867,6 +885,13 @@ const HttpHeaders& headers) const; bool DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const; + + bool DoPost(Json::Value& target, const std::string& name, const std::string& uri, const std::string& body, @@ -1604,4 +1629,101 @@ bool GetAnswerJson(Json::Value& output) const; }; #endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + class KeyValueStore : public boost::noncopyable + { + public: + class Iterator : public boost::noncopyable + { + private: + OrthancPluginKeysValuesIterator *iterator_; + + public: + Iterator(OrthancPluginKeysValuesIterator *iterator); + + ~Iterator(); + + bool Next(); + + std::string GetKey() const; + + void GetValue(std::string& target) const; + }; + + private: + std::string storeId_; + + public: + explicit KeyValueStore(const std::string& storeId) : + storeId_(storeId) + { + } + + const std::string& GetStoreId() const + { + return storeId_; + } + + void Store(const std::string& key, + const void* value, + size_t valueSize); + + void Store(const std::string& key, + const std::string& value) + { + Store(key, value.empty() ? NULL : value.c_str(), value.size()); + } + + bool GetValue(std::string& value, + const std::string& key); + + void DeleteKey(const std::string& key); + + Iterator* CreateIterator(); + }; +#endif + + +#if HAS_ORTHANC_PLUGIN_QUEUES == 1 + class Queue : public boost::noncopyable + { + private: + std::string queueId_; + + bool DequeueInternal(std::string& value, OrthancPluginQueueOrigin origin); + + public: + explicit Queue(const std::string& queueId) : + queueId_(queueId) + { + } + + const std::string& GetQueueId() const + { + return queueId_; + } + + void Enqueue(const void* value, + size_t valueSize); + + void Enqueue(const std::string& value) + { + Enqueue(value.empty() ? NULL : value.c_str(), value.size()); + } + + bool DequeueBack(std::string& value) + { + return DequeueInternal(value, OrthancPluginQueueOrigin_Back); + } + + bool DequeueFront(std::string& value) + { + return DequeueInternal(value, OrthancPluginQueueOrigin_Front); + } + + uint64_t GetSize(); + }; +#endif }