Mercurial > hg > orthanc-education
view Sources/EducationConfiguration.cpp @ 77:80b663d5f8fe default tip
replaced boost::math::iround() by Orthanc::Math::llround()
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Tue, 27 Jan 2026 17:05:03 +0100 |
| parents | 0f8c46d755e2 |
| children |
line wrap: on
line source
/** * SPDX-FileCopyrightText: 2024-2026 Sebastien Jodogne, EPL UCLouvain, Belgium * SPDX-License-Identifier: AGPL-3.0-or-later */ /** * Orthanc for Education * Copyright (C) 2024-2026 Sebastien Jodogne, EPL UCLouvain, 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 <http://www.gnu.org/licenses/>. **/ #include "EducationConfiguration.h" #include "HttpToolbox.h" #include "ProjectPermissionContext.h" #include <OrthancException.h> #include <SerializationToolbox.h> #include <SystemToolbox.h> #include <boost/algorithm/string/predicate.hpp> static const int32_t GLOBAL_PROPERTY_EDUCATION_SETTINGS = 9522; static const char* const FIELD_LTI_PLATFORM_KEYS_URL = "lti-platform-keys-url"; static const char* const FIELD_LTI_PLATFORM_REDIRECTION_URL = "lti-platform-redirection-url"; static const char* const FIELD_LTI_CLIENT_ID = "lti-client-id"; static const char* const FIELD_LTI_PRIVATE_KEY = "lti-private-key"; static const char* const FIELD_LTI_PRIVATE_KEY_ID = "lti-private-key-id"; static const char* const FIELD_LTI_SEQUENCE_PROJECT_IDS = "sequence-project-ids"; EducationConfiguration::EducationConfiguration() : maxLoginAge_(60 * 60 /* by default, 1 hour */), listProjectsAsLearner_(true), secureCookies_(true), ltiEnabled_(false), administratorsMode_(AuthenticationMode_None), standardUsersMode_(AuthenticationMode_None), hasPluginOrthancExplorer2_(false), hasPluginStoneWebViewer_(false), hasPluginVolView_(false), hasPluginWholeSlideImaging_(false), hasPluginOhif_(false), sequenceProjectIds_(0) { } EducationConfiguration& EducationConfiguration::GetInstance() { static EducationConfiguration instance; return instance; } void EducationConfiguration::SaveToGlobalPropertyUnsafe() { Json::Value value; value[FIELD_LTI_CLIENT_ID] = ltiClientId_; { LTIContext::Lock lock(ltiContext_); std::string pem; lock.GetPrivateKey().SerializePrivate(pem); value[FIELD_LTI_PRIVATE_KEY] = pem; value[FIELD_LTI_PRIVATE_KEY_ID] = lock.GetKeyId(); } value[FIELD_LTI_PLATFORM_KEYS_URL] = ltiPlatformKeysUrlFromRegistration_; value[FIELD_LTI_PLATFORM_REDIRECTION_URL] = ltiPlatformRedirectionUrlFromRegistration_; value[FIELD_LTI_SEQUENCE_PROJECT_IDS] = sequenceProjectIds_; std::string property = value.toStyledString(); OrthancPluginSetGlobalProperty(OrthancPlugins::GetGlobalContext(), GLOBAL_PROPERTY_EDUCATION_SETTINGS, property.c_str()); } void EducationConfiguration::LoadFromGlobalProperty() { boost::unique_lock<boost::shared_mutex> lock(mutex_); OrthancPlugins::OrthancString property; property.Assign(OrthancPluginGetGlobalProperty(OrthancPlugins::GetGlobalContext(), GLOBAL_PROPERTY_EDUCATION_SETTINGS, "")); bool createKey = true; Json::Value config; if (!property.IsNullOrEmpty() && OrthancPlugins::ReadJson(config, property.GetContent())) { ltiClientId_ = Orthanc::SerializationToolbox::ReadString(config, FIELD_LTI_CLIENT_ID, ""); const std::string pem = Orthanc::SerializationToolbox::ReadString(config, FIELD_LTI_PRIVATE_KEY, ""); const std::string id = Orthanc::SerializationToolbox::ReadString(config, FIELD_LTI_PRIVATE_KEY_ID, ""); if (!pem.empty() && !id.empty()) { ltiContext_.LoadPrivateKey(id, pem); createKey = false; } ltiPlatformKeysUrlFromRegistration_ = Orthanc::SerializationToolbox::ReadString(config, FIELD_LTI_PLATFORM_KEYS_URL, ""); ltiPlatformRedirectionUrlFromRegistration_ = Orthanc::SerializationToolbox::ReadString(config, FIELD_LTI_PLATFORM_REDIRECTION_URL, ""); sequenceProjectIds_ = Orthanc::SerializationToolbox::ReadUnsignedInteger(config, FIELD_LTI_SEQUENCE_PROJECT_IDS, 0); } if (createKey) { ltiContext_.CreatePrivateKey(); SaveToGlobalPropertyUnsafe(); } } void EducationConfiguration::SetAuthenticationHttpHeader(const std::string& header) { if (header.empty()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } else { // The Orthanc core converts keys of HTTP headers to lower case std::string lower; Orthanc::Toolbox::ToLowerCase(lower, header); boost::unique_lock<boost::shared_mutex> lock(mutex_); authenticationHttpHeader_ = lower; } } void EducationConfiguration::AddPublicRoot(const std::string& url) { HttpToolbox::CheckUrlScheme(url); { boost::unique_lock<boost::shared_mutex> lock(mutex_); publicRoots_.push_back(HttpToolbox::RemoveTrailingSlashes(url)); } } bool EducationConfiguration::StartsWithPublicRoot(std::string& path /* out */, const std::string& url) { boost::shared_lock<boost::shared_mutex> lock(mutex_); for (std::list<std::string>::const_iterator it = publicRoots_.begin(); it != publicRoots_.end(); ++it) { if (boost::starts_with(url, *it)) { path = url.substr(it->size()); return true; } } return false; } bool EducationConfiguration::GetAbsoluteUrl(std::string& target /* out */, const std::string& path) { if (publicRoots_.empty()) { LOG(WARNING) << "No \"PublicRoot\" is available in the configuration file " << "of the education plugin, cannot create an absolute URL"; return false; } else { target = Orthanc::Toolbox::JoinUri(*publicRoots_.begin(), path); return true; } } void EducationConfiguration::SetMaxLoginAgeSeconds(unsigned int seconds) { boost::unique_lock<boost::shared_mutex> lock(mutex_); maxLoginAge_ = seconds; } unsigned int EducationConfiguration::GetMaxLoginAgeSeconds() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return maxLoginAge_; } void EducationConfiguration::SetListProjectsAsLearner(bool show) { boost::unique_lock<boost::shared_mutex> lock(mutex_); listProjectsAsLearner_ = show; } bool EducationConfiguration::IsListProjectsAsLearner() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return listProjectsAsLearner_; } void EducationConfiguration::SetSecureCookies(bool secure) { boost::unique_lock<boost::shared_mutex> lock(mutex_); secureCookies_ = secure; } bool EducationConfiguration::IsSecureCookies() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return secureCookies_; } void EducationConfiguration::SetLtiEnabled(bool enabled) { boost::unique_lock<boost::shared_mutex> lock(mutex_); ltiEnabled_ = enabled; } bool EducationConfiguration::IsLtiEnabled() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return ltiEnabled_; } void EducationConfiguration::SetLtiOrthancUrl(const std::string& url) { HttpToolbox::CheckUrlScheme(url); { boost::unique_lock<boost::shared_mutex> lock(mutex_); ltiOrthancUrl_ = HttpToolbox::RemoveTrailingSlashes(url); } } std::string EducationConfiguration::GetLtiOrthancUrl() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return ltiOrthancUrl_; } std::string EducationConfiguration::GetLtiOrthancDomain() { std::vector<std::string> components; { boost::shared_lock<boost::shared_mutex> lock(mutex_); Orthanc::Toolbox::TokenizeString(components, ltiOrthancUrl_, '/'); } if (components.size() < 3 || (components[0] != "http:" && components[0] != "https:") || components[2].empty()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } else { return components[2]; } } void EducationConfiguration::SetLtiPlatformUrl(const std::string& url) { HttpToolbox::CheckUrlScheme(url); { boost::unique_lock<boost::shared_mutex> lock(mutex_); ltiPlatformUrl_ = HttpToolbox::RemoveTrailingSlashes(url); } } std::string EducationConfiguration::GetLtiPlatformUrl() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return ltiPlatformUrl_; } void EducationConfiguration::SetLtiPlatformKeysUrlFromFile(const std::string& url) { HttpToolbox::CheckUrlScheme(url); { boost::unique_lock<boost::shared_mutex> lock(mutex_); ltiPlatformKeysUrlFromFile_ = HttpToolbox::RemoveTrailingSlashes(url); } } void EducationConfiguration::SetLtiPlatformKeysUrlFromRegistration(const std::string& url) { HttpToolbox::CheckUrlScheme(url); { boost::unique_lock<boost::shared_mutex> lock(mutex_); ltiPlatformKeysUrlFromRegistration_ = HttpToolbox::RemoveTrailingSlashes(url); SaveToGlobalPropertyUnsafe(); } } std::string EducationConfiguration::GetLtiPlatformKeysUrl() { boost::shared_lock<boost::shared_mutex> lock(mutex_); if (!ltiPlatformKeysUrlFromFile_.empty()) { // The highest priority is the configuration file return ltiPlatformKeysUrlFromFile_; } else if (!ltiPlatformKeysUrlFromRegistration_.empty()) { return ltiPlatformKeysUrlFromRegistration_; } else if (ltiPlatformUrl_.empty()) { return ""; } else { // The following default URL corresponds to Moodle return Orthanc::Toolbox::JoinUri(ltiPlatformUrl_, "/mod/lti/certs.php"); } } void EducationConfiguration::SetLtiPlatformRedirectionUrlFromFile(const std::string& url) { HttpToolbox::CheckUrlScheme(url); { boost::unique_lock<boost::shared_mutex> lock(mutex_); ltiPlatformRedirectionUrlFromFile_ = HttpToolbox::RemoveTrailingSlashes(url); } } void EducationConfiguration::SetLtiPlatformRedirectionUrlFromRegistration(const std::string& url) { HttpToolbox::CheckUrlScheme(url); { boost::unique_lock<boost::shared_mutex> lock(mutex_); ltiPlatformRedirectionUrlFromRegistration_ = HttpToolbox::RemoveTrailingSlashes(url); SaveToGlobalPropertyUnsafe(); } } std::string EducationConfiguration::GetLtiPlatformRedirectionUrl() { boost::shared_lock<boost::shared_mutex> lock(mutex_); if (!ltiPlatformRedirectionUrlFromFile_.empty()) { // The highest priority is the configuration file return ltiPlatformRedirectionUrlFromFile_; } else if (!ltiPlatformRedirectionUrlFromRegistration_.empty()) { return ltiPlatformRedirectionUrlFromRegistration_; } else if (ltiPlatformUrl_.empty()) { return ""; } else { // The following default URL corresponds to Moodle return Orthanc::Toolbox::JoinUri(ltiPlatformUrl_, "/mod/lti/certs.php"); } } void EducationConfiguration::SetLtiClientId(const std::string& id) { boost::unique_lock<boost::shared_mutex> lock(mutex_); ltiClientId_ = id; SaveToGlobalPropertyUnsafe(); } std::string EducationConfiguration::GetLtiClientId() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return ltiClientId_; } void EducationConfiguration::SetAdministratorsAuthenticationMode(AuthenticationMode mode) { boost::unique_lock<boost::shared_mutex> lock(mutex_); administratorsMode_ = mode; } void EducationConfiguration::AddAdministratorCredentials(const std::string& username, const std::string& password) { if (username.empty() || password.empty()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } else { boost::unique_lock<boost::shared_mutex> lock(mutex_); administratorsCredentials_[username] = password; } } void EducationConfiguration::AddAdministratorRestrictedHttpHeaderValue(const std::string& value) { if (value.empty()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } else { boost::unique_lock<boost::shared_mutex> lock(mutex_); administratorsHeaders_.insert(value); } } void EducationConfiguration::SetStandardUsersAuthenticationMode(AuthenticationMode mode) { boost::unique_lock<boost::shared_mutex> lock(mutex_); standardUsersMode_ = mode; } void EducationConfiguration::AddStandardUserCredentials(const std::string& username, const std::string& password) { if (username.empty() || password.empty()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } else { boost::unique_lock<boost::shared_mutex> lock(mutex_); standardUsersCredentials_[username] = password; } } void EducationConfiguration::AddStandardUserRestrictedHttpHeaderValue(const std::string& value) { if (value.empty()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } else { boost::unique_lock<boost::shared_mutex> lock(mutex_); standardUsersHeaders_.insert(value); } } static bool CheckLoginAuthentication(const std::map<std::string, std::string>& users, const std::string& username, const std::string& password) { std::map<std::string, std::string>::const_iterator found = users.find(username); if (found != users.end()) { if (found->second == password) { return true; } else { throw Orthanc::OrthancException(Orthanc::ErrorCode_Unauthorized); } } else { return false; } } bool EducationConfiguration::HasPluginOrthancExplorer2() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return hasPluginOrthancExplorer2_; } void EducationConfiguration::SetPluginOrthancExplorer2(bool present) { boost::unique_lock<boost::shared_mutex> lock(mutex_); hasPluginOrthancExplorer2_ = present; } bool EducationConfiguration::HasPluginStoneWebViewer() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return hasPluginStoneWebViewer_; } void EducationConfiguration::SetPluginStoneWebViewer(bool present) { boost::unique_lock<boost::shared_mutex> lock(mutex_); hasPluginStoneWebViewer_ = present; } bool EducationConfiguration::HasPluginVolView() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return hasPluginVolView_; } void EducationConfiguration::SetPluginVolView(bool present) { boost::unique_lock<boost::shared_mutex> lock(mutex_); hasPluginVolView_ = present; } bool EducationConfiguration::HasPluginWholeSlideImaging() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return hasPluginWholeSlideImaging_; } void EducationConfiguration::SetPluginWholeSlideImaging(bool present) { boost::unique_lock<boost::shared_mutex> lock(mutex_); hasPluginWholeSlideImaging_ = present; } bool EducationConfiguration::HasPluginOhif() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return hasPluginOhif_; } void EducationConfiguration::SetPluginOhif(bool present) { boost::unique_lock<boost::shared_mutex> lock(mutex_); hasPluginOhif_ = present; } void EducationConfiguration::ListAvailableViewers(std::set<ViewerType>& target) { target.clear(); { boost::shared_lock<boost::shared_mutex> lock(mutex_); if (hasPluginStoneWebViewer_) { target.insert(ViewerType_StoneWebViewer); } if (hasPluginVolView_) { target.insert(ViewerType_VolView); } if (hasPluginWholeSlideImaging_) { target.insert(ViewerType_WholeSlideImaging); } if (hasPluginOhif_) { target.insert(ViewerType_OHIF_Basic); target.insert(ViewerType_OHIF_VolumeRendering); target.insert(ViewerType_OHIF_TumorVolume); target.insert(ViewerType_OHIF_Segmentation); } } } std::string EducationConfiguration::GenerateProjectId() { unsigned int generated; { boost::unique_lock<boost::shared_mutex> lock(mutex_); generated = sequenceProjectIds_; sequenceProjectIds_ ++; SaveToGlobalPropertyUnsafe(); } return boost::lexical_cast<std::string>(generated); } void EducationConfiguration::SetPathToWsiDicomizer(const std::string& path) { boost::unique_lock<boost::shared_mutex> lock(mutex_); pathWsiDicomizer_ = path; } std::string EducationConfiguration::GetPathToWsiDicomizer() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return pathWsiDicomizer_; } void EducationConfiguration::SetPathToOpenSlide(const std::string& path) { boost::unique_lock<boost::shared_mutex> lock(mutex_); pathOpenSlide_ = path; } std::string EducationConfiguration::GetPathToOpenSlide() { boost::shared_lock<boost::shared_mutex> lock(mutex_); return pathOpenSlide_; } AuthenticatedUser* EducationConfiguration::DoLoginAuthentication(const std::string& username, const std::string& password) { boost::shared_lock<boost::shared_mutex> lock(mutex_); if (administratorsMode_ == AuthenticationMode_Login && CheckLoginAuthentication(administratorsCredentials_, username, password)) { return AuthenticatedUser::CreateAdministrator(username); } if (standardUsersMode_ == AuthenticationMode_Login && CheckLoginAuthentication(standardUsersCredentials_, username, password)) { ProjectPermissionContext context; return AuthenticatedUser::CreateStandardUser(context, username); } return NULL; } static bool IsHttpHeaderAllowed(AuthenticationMode mode, const std::string& value, const std::set<std::string>& restrictedValues) { switch (mode) { case AuthenticationMode_RestrictedHttpHeader: return restrictedValues.find(value) != restrictedValues.end(); case AuthenticationMode_HttpHeader: return !value.empty(); default: return false; } } AuthenticatedUser* EducationConfiguration::DoHttpHeaderAuthentication(uint32_t headersCount, const char* const* headersKeys, const char* const* headersValues) { boost::shared_lock<boost::shared_mutex> lock(mutex_); std::string value; if (!authenticationHttpHeader_.empty() && HttpToolbox::LookupCDictionary(value, authenticationHttpHeader_, false /* no lowercase */, headersCount, headersKeys, headersValues)) { if (IsHttpHeaderAllowed(administratorsMode_, value, administratorsHeaders_)) { return AuthenticatedUser::CreateAdministrator(value); } if (IsHttpHeaderAllowed(standardUsersMode_, value, standardUsersHeaders_)) { ProjectPermissionContext context; return AuthenticatedUser::CreateStandardUser(context, value); } } return NULL; }
