/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2024 Osimis S.A., Belgium * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . **/ #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" #include #include #include #include #include #define ORTHANC_PLUGIN_NAME "orthanc-explorer-2" // we are using Orthanc 1.11.0 API (RequestedTags in tools/find) #define ORTHANC_CORE_MINIMAL_MAJOR 1 #define ORTHANC_CORE_MINIMAL_MINOR 11 #define ORTHANC_CORE_MINIMAL_REVISION 0 std::unique_ptr orthancFullConfiguration_; Json::Value pluginJsonConfiguration_; std::string oe2BaseUrl_; Json::Value pluginsConfiguration_; bool hasUserProfile_ = false; bool openInOhifV3IsExplicitelyDisabled = false; bool enableShares_ = false; bool isReadOnly_ = false; std::string customCssPath_; std::string theme_ = "light"; std::string customLogoPath_; std::string customLogoUrl_; std::string customFavIconPath_; std::string customTitle_; enum CustomFilesPath { CustomFilesPath_Logo, CustomFilesPath_FavIcon }; template void ServeEmbeddedFolder(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request) { OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); if (request->method != OrthancPluginHttpMethod_Get) { OrthancPluginSendMethodNotAllowed(context, output, "GET"); } else { std::string path = "/" + std::string(request->groups[0]); Orthanc::MimeType mimeType = Orthanc::SystemToolbox::AutodetectMimeType(path); const char* mime = Orthanc::EnumerationToString(mimeType); std::string fileContent; Orthanc::EmbeddedResources::GetDirectoryResource(fileContent, folder, path.c_str()); const char* resource = fileContent.size() ? fileContent.c_str() : NULL; OrthancPluginAnswerBuffer(context, output, resource, fileContent.size(), mime); } } template void ServeEmbeddedFile(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request) { OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); if (request->method != OrthancPluginHttpMethod_Get) { OrthancPluginSendMethodNotAllowed(context, output, "GET"); } else { std::string s; Orthanc::EmbeddedResources::GetFileResource(s, file); if (file == Orthanc::EmbeddedResources::WEB_APPLICATION_INDEX && theme_ != "light") { boost::replace_all(s, "data-bs-theme=\"light\"", "data-bs-theme=\"" + theme_ + "\""); } const char* resource = s.size() ? s.c_str() : NULL; OrthancPluginAnswerBuffer(context, output, resource, s.size(), Orthanc::EnumerationToString(mime)); } } template void ServeCustomFile(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request) { OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); if (request->method != OrthancPluginHttpMethod_Get) { OrthancPluginSendMethodNotAllowed(context, output, "GET"); } else { std::string fileContent; std::string customFileContent; std::string customFilePath; if (customFile == CustomFilesPath_FavIcon) { customFilePath = customFavIconPath_; } else if (customFile == CustomFilesPath_Logo) { customFilePath = customLogoPath_; } else { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } Orthanc::SystemToolbox::ReadFile(fileContent, customFilePath); Orthanc::MimeType mimeType = Orthanc::SystemToolbox::AutodetectMimeType(customFilePath); // include an ETag for correct cache handling OrthancPlugins::OrthancString md5; size_t size = fileContent.size(); md5.Assign(OrthancPluginComputeMd5(OrthancPlugins::GetGlobalContext(), fileContent.c_str(), size)); std::string etag = "\"" + std::string(md5.GetContent()) + "\""; OrthancPluginSetHttpHeader(OrthancPlugins::GetGlobalContext(), output, "ETag", etag.c_str()); OrthancPluginSetHttpHeader(OrthancPlugins::GetGlobalContext(), output, "Cache-Control", "no-cache"); OrthancPluginAnswerBuffer(context, output, fileContent.c_str(), size, Orthanc::EnumerationToString(mimeType)); } } // serves either the default CSS or a custom file CSS void ServeCustomCss(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request) { OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); if (request->method != OrthancPluginHttpMethod_Get) { OrthancPluginSendMethodNotAllowed(context, output, "GET"); } else { std::string cssFileContent; if (strstr(url, "custom.css") != NULL) { if (theme_ == "dark") { Orthanc::EmbeddedResources::GetFileResource(cssFileContent, Orthanc::EmbeddedResources::DEFAULT_CSS_DARK); } else { Orthanc::EmbeddedResources::GetFileResource(cssFileContent, Orthanc::EmbeddedResources::DEFAULT_CSS_LIGHT); } if (!customCssPath_.empty()) { // append the custom CSS std::string customCssFileContent; Orthanc::SystemToolbox::ReadFile(customCssFileContent, customCssPath_); cssFileContent += "\n/* Appending the custom CSS */\n" + customCssFileContent; } } const char* resource = cssFileContent.size() ? cssFileContent.c_str() : NULL; size_t size = cssFileContent.size(); // include an ETag for correct cache handling OrthancPlugins::OrthancString md5; md5.Assign(OrthancPluginComputeMd5(OrthancPlugins::GetGlobalContext(), resource, size)); std::string etag = "\"" + std::string(md5.GetContent()) + "\""; OrthancPluginSetHttpHeader(OrthancPlugins::GetGlobalContext(), output, "ETag", etag.c_str()); OrthancPluginSetHttpHeader(OrthancPlugins::GetGlobalContext(), output, "Cache-Control", "no-cache"); OrthancPluginAnswerBuffer(context, output, resource, size, Orthanc::EnumerationToString(Orthanc::MimeType_Css)); } } void RedirectRoot(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request) { OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); if (request->method != OrthancPluginHttpMethod_Get) { OrthancPluginSendMethodNotAllowed(context, output, "GET"); } else { for (uint32_t i = 0; i < request->headersCount; ++i) { OrthancPlugins::LogError(std::string(request->headersKeys[i]) + " : " + request->headersValues[i]); } std::string oe2BaseApp = oe2BaseUrl_ + "app/"; OrthancPluginRedirect(context, output, &(oe2BaseApp.c_str()[1])); // remove the first '/' to make a relative redirect ! } } void MergeJson(Json::Value &a, const Json::Value &b) { if (!a.isObject() || !b.isObject()) { return; } Json::Value::Members members = b.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { std::string key = members[i]; if (!a[key].isNull() && a[key].type() == Json::objectValue && b[key].type() == Json::objectValue) { MergeJson(a[key], b[key]); } else { // const std::string& val = b[key].asString(); a[key] = b[key]; } } } void ReadConfiguration() { orthancFullConfiguration_.reset(new OrthancPlugins::OrthancConfiguration); // read default configuration std::string defaultConfigurationFileContent; Orthanc::EmbeddedResources::GetFileResource(defaultConfigurationFileContent, Orthanc::EmbeddedResources::DEFAULT_CONFIGURATION); Json::Value defaultConfiguration; OrthancPlugins::ReadJsonWithoutComments(defaultConfiguration, defaultConfigurationFileContent); pluginJsonConfiguration_ = defaultConfiguration["OrthancExplorer2"]; if (orthancFullConfiguration_->IsSection("OrthancExplorer2")) { OrthancPlugins::OrthancConfiguration pluginConfiguration(false); orthancFullConfiguration_->GetSection(pluginConfiguration, "OrthancExplorer2"); Json::Value jsonConfig = pluginConfiguration.GetJson(); // backward compatibility if (jsonConfig.isMember("UiOptions")) { // fix typo from version 0.7.0 if (jsonConfig["UiOptions"].isMember("EnableAnonimization") && !jsonConfig["UiOptions"].isMember("EnableAnonymization")) { LOG(WARNING) << "You are still using the 'UiOptions.EnableAnonimization' configuration that has a typo. You should use 'UiOptions.EnableAnonymization' instead."; jsonConfig["UiOptions"]["EnableAnonymization"] = jsonConfig["UiOptions"]["EnableAnonimization"]; } if (jsonConfig["UiOptions"].isMember("StudyListEmptyIfNoSearch") && !jsonConfig["UiOptions"].isMember("StudyListContentIfNoSearch")) { if (jsonConfig["UiOptions"]["StudyListEmptyIfNoSearch"].asBool() == true) { LOG(WARNING) << "You are still using the 'UiOptions.StudyListEmptyIfNoSearch' configuration that is now deprecated a typo. You should use 'UiOptions.StudyListContentIfNoSearch' instead."; jsonConfig["UiOptions"]["StudyListContentIfNoSearch"] = "empty"; } } openInOhifV3IsExplicitelyDisabled = jsonConfig["UiOptions"].isMember("EnableOpenInOhifViewer3") && jsonConfig["UiOptions"]["EnableOpenInOhifViewer3"].asBool() == false; } MergeJson(pluginJsonConfiguration_, jsonConfig); if (jsonConfig.isMember("CustomCssPath") && jsonConfig["CustomCssPath"].isString()) { customCssPath_ = jsonConfig["CustomCssPath"].asString(); if (!Orthanc::SystemToolbox::IsExistingFile(customCssPath_)) { LOG(ERROR) << "Unable to accesss the 'CustomCssPath': " << customCssPath_; throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile); } } if (jsonConfig.isMember("CustomLogoPath") && jsonConfig["CustomLogoPath"].isString()) { customLogoPath_ = jsonConfig["CustomLogoPath"].asString(); if (!Orthanc::SystemToolbox::IsExistingFile(customLogoPath_)) { LOG(ERROR) << "Unable to accesss the 'CustomLogoPath': " << customLogoPath_; throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile); } } if (jsonConfig.isMember("CustomLogoUrl") && jsonConfig["CustomLogoUrl"].isString()) { customLogoUrl_ = jsonConfig["CustomLogoUrl"].asString(); } if (jsonConfig.isMember("Theme") && jsonConfig["Theme"].isString() && jsonConfig["Theme"].asString() == "dark") { theme_ = "dark"; } if (jsonConfig.isMember("CustomFavIconPath") && jsonConfig["CustomFavIconPath"].isString()) { customFavIconPath_ = jsonConfig["CustomFavIconPath"].asString(); if (!Orthanc::SystemToolbox::IsExistingFile(customFavIconPath_)) { LOG(ERROR) << "Unable to accesss the 'CustomFavIconPath': " << customFavIconPath_; throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile); } } if (jsonConfig.isMember("CustomTitle") && jsonConfig["CustomTitle"].isString()) { customTitle_ = jsonConfig["CustomTitle"].asString(); } } enableShares_ = pluginJsonConfiguration_["UiOptions"]["EnableShares"].asBool(); // we are sure that the value exists since it is in the default configuration file isReadOnly_ = orthancFullConfiguration_->GetBooleanValue("ReadOnly", false); } bool GetPluginConfiguration(Json::Value& jsonPluginConfiguration, const std::string& sectionName) { if (orthancFullConfiguration_->IsSection(sectionName)) { OrthancPlugins::OrthancConfiguration pluginConfiguration(false); orthancFullConfiguration_->GetSection(pluginConfiguration, sectionName); jsonPluginConfiguration = pluginConfiguration.GetJson(); return true; } return false; } bool IsPluginEnabledInConfiguration(const std::string& sectionName, const std::string& enableValueName, bool defaultValue) { if (orthancFullConfiguration_->IsSection(sectionName)) { OrthancPlugins::OrthancConfiguration pluginConfiguration(false); orthancFullConfiguration_->GetSection(pluginConfiguration, sectionName); return pluginConfiguration.GetBooleanValue(enableValueName, defaultValue); } return defaultValue; } Json::Value GetPluginInfo(const std::string& pluginName) { Json::Value pluginInfo; OrthancPlugins::RestApiGet(pluginInfo, "/plugins/" + pluginName, false); return pluginInfo; } Json::Value GetKeycloakConfiguration() { if (pluginJsonConfiguration_.isMember("Keycloak")) { const Json::Value& keyCloakSection = pluginJsonConfiguration_["Keycloak"]; if (keyCloakSection.isMember("Enable") && keyCloakSection["Enable"].asBool() == true) { return pluginJsonConfiguration_["Keycloak"]; } } return Json::nullValue; } Json::Value GetPluginsConfiguration(bool& hasUserProfile) { Json::Value pluginsConfiguration; Json::Value pluginList; Orthanc::UriComponents components; Orthanc::Toolbox::SplitUriComponents(components, oe2BaseUrl_); std::string pluginUriPrefix = ""; // the RootUri is provided relative to Orthanc Explorer /app/explorer.html -> we need to correct this ! for (size_t i = 0; i < components.size(); i++) { pluginUriPrefix += "../"; } OrthancPlugins::RestApiGet(pluginList, "/plugins", false); for (Json::Value::ArrayIndex i = 0; i < pluginList.size(); i++) { Json::Value pluginConfiguration; std::string pluginName = pluginList[i].asString(); if (pluginName == "explorer.js") { continue; } Json::Value pluginInfo = GetPluginInfo(pluginName); if (pluginInfo.isMember("RootUri") && pluginInfo["RootUri"].asString().size() > 0) { pluginInfo["RootUri"] = pluginUriPrefix + pluginInfo["RootUri"].asString(); } pluginsConfiguration[pluginName] = pluginInfo; pluginsConfiguration[pluginName]["Enabled"] = true; // we assume that unknown plugins are enabled (if they are loaded by Orthanc) if (pluginName == "authorization") { pluginConfiguration = Json::nullValue; pluginsConfiguration[pluginName]["Enabled"] = GetPluginConfiguration(pluginConfiguration, "Authorization") && (pluginConfiguration.isMember("WebService") || pluginConfiguration.isMember("WebServiceRootUrl") || pluginConfiguration.isMember("WebServiceUserProfileUrl") || pluginConfiguration.isMember("WebServiceTokenValidationUrl") || pluginConfiguration.isMember("WebServiceTokenCreationBaseUrl") || pluginConfiguration.isMember("WebServiceTokenDecoderUrl")); hasUserProfile = GetPluginConfiguration(pluginConfiguration, "Authorization") && (pluginConfiguration.isMember("WebServiceUserProfileUrl") || pluginConfiguration.isMember("WebServiceRootUrl")); if (!pluginConfiguration.isMember("CheckedLevel") || pluginConfiguration["CheckedLevel"].asString() != "studies") { LOG(WARNING) << "When using OE2 and the authorization plugin together, you must set 'Authorization.CheckedLevel' to 'studies'. Unless you are using this orthanc only to generate tokens."; } } else if (pluginName == "AWS S3 Storage") { pluginsConfiguration[pluginName]["Enabled"] = GetPluginConfiguration(pluginConfiguration, "AwsS3Storage"); } else if (pluginName == "Azure Blob Storage") { pluginsConfiguration[pluginName]["Enabled"] = GetPluginConfiguration(pluginConfiguration, "AzureBlobStorage"); } else if (pluginName == "connectivity-checks") { pluginsConfiguration[pluginName]["Enabled"] = true; } else if (pluginName == "ohif") { pluginsConfiguration[pluginName]["Enabled"] = true; std::string ohifDataSource = "dicom-web"; if (GetPluginConfiguration(pluginConfiguration, "OHIF")) { if (pluginConfiguration.isMember("DataSource") && pluginConfiguration["DataSource"].asString() == "dicom-json") { ohifDataSource = "dicom-json"; } } pluginsConfiguration[pluginName]["DataSource"] = ohifDataSource; } else if (pluginName == "delayed-deletion") { pluginsConfiguration[pluginName]["Enabled"] = IsPluginEnabledInConfiguration("DelayedDeletion", "Enable", false); } else if (pluginName == "dicom-web") { pluginsConfiguration[pluginName]["Enabled"] = IsPluginEnabledInConfiguration("DicomWeb", "Enable", false); } else if (pluginName == "gdcm") { pluginsConfiguration[pluginName]["Enabled"] = IsPluginEnabledInConfiguration("Gdcm", "Enable", true); } else if (pluginName == "Google Cloud Storage") { pluginsConfiguration[pluginName]["Enabled"] = GetPluginConfiguration(pluginConfiguration, "GoogleCloudStorage"); } else if (pluginName == "mysql-index") { pluginsConfiguration[pluginName]["Enabled"] = IsPluginEnabledInConfiguration("MySQL", "EnableIndex", false); } else if (pluginName == "mysql-storage") { pluginsConfiguration[pluginName]["Enabled"] = IsPluginEnabledInConfiguration("MySQL", "EnableStorage", false); } else if (pluginName == "odbc-index") { pluginsConfiguration[pluginName]["Enabled"] = IsPluginEnabledInConfiguration("Odbc", "EnableIndex", false); } else if (pluginName == "odbc-storage") { pluginsConfiguration[pluginName]["Enabled"] = IsPluginEnabledInConfiguration("Odbc", "EnableStorage", false); } else if (pluginName == "postgresql-index") { pluginsConfiguration[pluginName]["Enabled"] = IsPluginEnabledInConfiguration("PostgreSQL", "EnableIndex", false); } else if (pluginName == "postgresql-storage") { pluginsConfiguration[pluginName]["Enabled"] = IsPluginEnabledInConfiguration("PostgreSQL", "EnableStorage", false); } else if (pluginName == "osimis-web-viewer") { pluginsConfiguration[pluginName]["Enabled"] = GetPluginConfiguration(pluginConfiguration, "WebViewer"); } else if (pluginName == "python") { std::string notUsed; pluginsConfiguration[pluginName]["Enabled"] = orthancFullConfiguration_->LookupStringValue(notUsed, "PythonScript"); } else if (pluginName == "serve-folders") { pluginsConfiguration[pluginName]["Enabled"] = GetPluginConfiguration(pluginConfiguration, "ServeFolders"); } else if (pluginName == "stone-webviewer") { pluginsConfiguration[pluginName]["Enabled"] = true; } else if (pluginName == "volview") { pluginsConfiguration[pluginName]["Enabled"] = true; } else if (pluginName == "tcia") { pluginsConfiguration[pluginName]["Enabled"] = IsPluginEnabledInConfiguration("Tcia", "Enable", false); } else if (pluginName == "transfers") { pluginsConfiguration[pluginName]["Enabled"] = true; } else if (pluginName == "web-viewer") { pluginsConfiguration[pluginName]["Enabled"] = true; } else if (pluginName == "worklists") { pluginsConfiguration[pluginName]["Enabled"] = IsPluginEnabledInConfiguration("Worklists", "Enable", false); } else if (pluginName == "wsi") { pluginsConfiguration[pluginName]["Enabled"] = true; } else if (pluginName == "multitenant-dicom") { pluginsConfiguration[pluginName]["Enabled"] = false; Json::Value pluginConfiguration; if (GetPluginConfiguration(pluginConfiguration, "MultitenantDicom")) { pluginsConfiguration[pluginName]["Enabled"] = pluginConfiguration.isMember("Servers") && pluginConfiguration["Servers"].isArray() && pluginConfiguration["Servers"].size() > 0; } } } return pluginsConfiguration; } void UpdateUiOptions(Json::Value& uiOption, const std::list& permissions, const std::string& anyOfPermissions) { std::vector permissionsVector; Orthanc::Toolbox::TokenizeString(permissionsVector, anyOfPermissions, '|'); bool hasPermission = false; for (size_t i = 0; i < permissionsVector.size(); ++i) { hasPermission |= std::find(permissions.begin(), permissions.end(), permissionsVector[i]) != permissions.end(); } uiOption = uiOption.asBool() && hasPermission; } void GetOE2Configuration(OrthancPluginRestOutput* output, const char* /*url*/, const OrthancPluginHttpRequest* request) { OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); if (request->method != OrthancPluginHttpMethod_Get) { OrthancPluginSendMethodNotAllowed(context, output, "GET"); } else { Json::Value oe2Configuration; oe2Configuration["Plugins"] = pluginsConfiguration_; oe2Configuration["UiOptions"] = pluginJsonConfiguration_["UiOptions"]; // if OHIF has not been explicitely disabled in the config and if the plugin is loaded, enable it if (!openInOhifV3IsExplicitelyDisabled && pluginsConfiguration_.isMember("ohif")) { oe2Configuration["UiOptions"]["EnableOpenInOhifViewer3"] = true; } Json::Value tokens = pluginJsonConfiguration_["Tokens"]; if (!tokens.isMember("RequiredForLinks")) { tokens["RequiredForLinks"] = hasUserProfile_; } oe2Configuration["Tokens"] = tokens; oe2Configuration["HasCustomLogo"] = !customLogoPath_.empty() || !customLogoUrl_.empty(); if (!customLogoUrl_.empty()) { oe2Configuration["CustomLogoUrl"] = customLogoUrl_; } if (!customTitle_.empty()) { oe2Configuration["CustomTitle"] = customTitle_; } Json::Value& uiOptions = oe2Configuration["UiOptions"]; if (hasUserProfile_) { {// get the available-labels from the auth plugin (and the auth-service) std::map headers; OrthancPlugins::GetHttpHeaders(headers, request); uiOptions["EnablePermissionsEdition"] = false; Json::Value rolesConfig; if (OrthancPlugins::RestApiGet(rolesConfig, "/auth/settings/roles", headers, true)) { if (rolesConfig.isObject() && rolesConfig.isMember("available-labels")) { LOG(INFO) << "Overriding \"AvailableLabels\" in UiOptions with the values from the auth-service"; uiOptions["AvailableLabels"] = rolesConfig["available-labels"]; } LOG(INFO) << rolesConfig.toStyledString(); // if the auth-service is not fully configured, disable permissions edition if (rolesConfig.isObject() && rolesConfig.isMember("roles") && rolesConfig["roles"].isObject() && rolesConfig["roles"].size() > 0) { uiOptions["EnablePermissionsEdition"] = true; } } } {// get the user profile from the auth plugin (and the auth-service) std::map headers; OrthancPlugins::GetHttpHeaders(headers, request); Json::Value userProfile; OrthancPlugins::RestApiGet(userProfile, "/auth/user/profile", headers, true); // modify the UiOptions based on the user profile std::list permissions; Orthanc::SerializationToolbox::ReadListOfStrings(permissions, userProfile, "permissions"); LOG(INFO) << "Overriding \"Enable...\" in UiOptions with the permissions from the auth-service for this user-profile"; UpdateUiOptions(uiOptions["EnableStudyList"], permissions, "all|view"); UpdateUiOptions(uiOptions["EnableViewerQuickButton"], permissions, "all|view"); UpdateUiOptions(uiOptions["EnableReportQuickButton"], permissions, "all|view"); UpdateUiOptions(uiOptions["EnableUpload"], permissions, "all|upload"); UpdateUiOptions(uiOptions["EnableAddSeries"], permissions, "all|upload"); UpdateUiOptions(uiOptions["EnableDicomModalities"], permissions, "all|q-r-remote-modalities"); UpdateUiOptions(uiOptions["EnableDeleteResources"], permissions, "all|delete"); UpdateUiOptions(uiOptions["EnableDownloadZip"], permissions, "all|download"); UpdateUiOptions(uiOptions["EnableDownloadDicomDir"], permissions, "all|download"); UpdateUiOptions(uiOptions["EnableDownloadDicomFile"], permissions, "all|download"); UpdateUiOptions(uiOptions["EnableModification"], permissions, "all|modify"); UpdateUiOptions(uiOptions["EnableAnonymization"], permissions, "all|anonymize"); UpdateUiOptions(uiOptions["EnableSendTo"], permissions, "all|send"); UpdateUiOptions(uiOptions["EnableApiViewMenu"], permissions, "all|api-view"); UpdateUiOptions(uiOptions["EnableSettings"], permissions, "all|settings"); UpdateUiOptions(uiOptions["EnableShares"], permissions, "all|share"); UpdateUiOptions(uiOptions["EnableEditLabels"], permissions, "all|edit-labels"); UpdateUiOptions(uiOptions["EnablePermissionsEdition"], permissions, "admin-permissions"); // the Legacy UI is not available with user profile since it would not refresh the tokens uiOptions["EnableLinkToLegacyUi"] = false; oe2Configuration["Profile"] = userProfile; } } // disable operations on read only systems if (isReadOnly_) { uiOptions["EnableUpload"] = false; uiOptions["EnableAddSeries"] = false; uiOptions["EnableDeleteResources"] = false; uiOptions["EnableModification"] = false; uiOptions["EnableAnonymization"] = false; uiOptions["EnableEditLabels"] = false; uiOptions["EnablePermissionsEdition"] = false; } oe2Configuration["Keycloak"] = GetKeycloakConfiguration(); std::string answer = oe2Configuration.toStyledString(); OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json"); } } void GetOE2PreLoginConfiguration(OrthancPluginRestOutput* output, const char* /*url*/, const OrthancPluginHttpRequest* request) { OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); if (request->method != OrthancPluginHttpMethod_Get) { OrthancPluginSendMethodNotAllowed(context, output, "GET"); } else { Json::Value oe2Configuration; oe2Configuration["Keycloak"] = GetKeycloakConfiguration(); std::string answer = oe2Configuration.toStyledString(); OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json"); } } static bool DisplayPerformanceWarning(OrthancPluginContext* context) { (void) DisplayPerformanceWarning; // Disable warning about unused function OrthancPluginLogWarning(context, "Performance warning in Orthanc Explorer 2: " "Non-release build, runtime debug assertions are turned on"); return true; } static void CheckRootUrlIsValid(const std::string& value, const std::string& name, bool allowEmpty) { if (allowEmpty && value.size() == 0) { return; } if (value.size() < 1 || value[0] != '/' || value[value.size() - 1] != '/') { OrthancPlugins::LogError("Orthanc-Explorer 2: '" + name + "' configuration shall start with a '/' and end with a '/': " + value); throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } } OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, OrthancPluginResourceType resourceType, const char* resourceId) { try { if (changeType == OrthancPluginChangeType_OrthancStarted) { // this can not be performed during plugin initialization because it is accessing the DB -> must be done when Orthanc has just started pluginsConfiguration_ = GetPluginsConfiguration(hasUserProfile_); } } catch (Orthanc::OrthancException& e) { LOG(ERROR) << "Exception: " << e.What(); return static_cast(e.GetErrorCode()); } return OrthancPluginErrorCode_Success; } extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { assert(DisplayPerformanceWarning(context)); OrthancPlugins::SetGlobalContext(context); Orthanc::Logging::InitializePluginContext(context); Orthanc::Logging::EnableInfoLevel(true); /* Check the version of the Orthanc core */ if (!OrthancPlugins::CheckMinimalOrthancVersion(ORTHANC_CORE_MINIMAL_MAJOR, ORTHANC_CORE_MINIMAL_MINOR, ORTHANC_CORE_MINIMAL_REVISION)) { OrthancPlugins::ReportMinimalOrthancVersion(ORTHANC_CORE_MINIMAL_MAJOR, ORTHANC_CORE_MINIMAL_MINOR, ORTHANC_CORE_MINIMAL_REVISION); return -1; } OrthancPlugins::SetDescription(ORTHANC_PLUGIN_NAME, "Advanced User Interface for Orthanc"); try { ReadConfiguration(); if (pluginJsonConfiguration_["Enable"].asBool()) { oe2BaseUrl_ = pluginJsonConfiguration_["Root"].asString(); CheckRootUrlIsValid(oe2BaseUrl_, "Root", false); OrthancPlugins::LogWarning("Root URI to the Orthanc-Explorer 2 application: " + oe2BaseUrl_); OrthancPlugins::RegisterRestCallback (oe2BaseUrl_ + "app/customizable/custom.css", true); if (!customLogoPath_.empty()) { OrthancPlugins::RegisterRestCallback > (oe2BaseUrl_ + "app/customizable/custom-logo", true); } // we need to mix the "routing" between the server and the frontend (vue-router) // first part are the files that are 'static files' that must be served by the backend OrthancPlugins::RegisterRestCallback > (oe2BaseUrl_ + "app/assets/(.*)", true); OrthancPlugins::RegisterRestCallback > (oe2BaseUrl_ + "app/index.html", true); OrthancPlugins::RegisterRestCallback > (oe2BaseUrl_ + "app/token-landing.html", true); OrthancPlugins::RegisterRestCallback > (oe2BaseUrl_ + "app/retrieve-and-view.html", true); if (customFavIconPath_.empty()) { OrthancPlugins::RegisterRestCallback > (oe2BaseUrl_ + "app/favicon.ico", true); } else { OrthancPlugins::RegisterRestCallback > (oe2BaseUrl_ + "app/favicon.ico", true); } // second part are all the routes that are actually handled by vue-router and that are actually returning the same file (index.html) OrthancPlugins::RegisterRestCallback > (oe2BaseUrl_ + "app/(.*)", true); OrthancPlugins::RegisterRestCallback > (oe2BaseUrl_ + "app", true); OrthancPlugins::RegisterRestCallback(oe2BaseUrl_ + "api/configuration", true); OrthancPlugins::RegisterRestCallback(oe2BaseUrl_ + "api/pre-login-configuration", true); std::string pluginRootUri = oe2BaseUrl_ + "app/"; OrthancPlugins::SetRootUri(ORTHANC_PLUGIN_NAME, pluginRootUri.c_str()); if (pluginJsonConfiguration_["IsDefaultOrthancUI"].asBool()) { OrthancPlugins::RegisterRestCallback("/", true); } OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); { std::string explorer; Orthanc::EmbeddedResources::GetFileResource(explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER); std::map dictionary; dictionary["OE2_BASE_URL"] = oe2BaseUrl_.substr(1, oe2BaseUrl_.size() - 2); // Remove heading and trailing slashes std::string explorerConfigured = Orthanc::Toolbox::SubstituteVariables(explorer, dictionary); OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorerConfigured.c_str()); } } else { OrthancPlugins::LogWarning("Orthanc Explorer 2 plugin is disabled"); } } catch (Orthanc::OrthancException& e) { OrthancPlugins::LogError("Exception while initializing the Orthanc-Explorer 2 plugin: " + std::string(e.What())); return -1; } catch (...) { OrthancPlugins::LogError("Exception while initializing the Orthanc-Explorer 2 plugin"); return -1; } return 0; } ORTHANC_PLUGINS_API void OrthancPluginFinalize() { } ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { return ORTHANC_PLUGIN_NAME; } ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() { return ORTHANC_OE2_VERSION; } }