Mercurial > hg > orthanc-authorization
diff Plugin/Plugin.cpp @ 71:30fb3ce960d9
configurable user permissions
author | Alain Mazy <am@osimis.io> |
---|---|
date | Wed, 22 Feb 2023 13:13:38 +0100 |
parents | af44dce56328 |
children | e381ba725669 |
line wrap: on
line diff
--- a/Plugin/Plugin.cpp Tue Feb 21 09:23:47 2023 +0100 +++ b/Plugin/Plugin.cpp Wed Feb 22 13:13:38 2023 +0100 @@ -20,6 +20,7 @@ #include "DefaultAuthorizationParser.h" #include "CachedAuthorizationService.h" #include "AuthorizationWebService.h" +#include "PermissionParser.h" #include "MemoryCache.h" #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" @@ -27,17 +28,27 @@ #include <Compatibility.h> // For std::unique_ptr<> #include <Logging.h> #include <Toolbox.h> +#include <EmbeddedResources.h> // Configuration of the authorization plugin static std::unique_ptr<OrthancPlugins::IAuthorizationParser> authorizationParser_; static std::unique_ptr<OrthancPlugins::IAuthorizationService> authorizationService_; +static std::unique_ptr<OrthancPlugins::PermissionParser> permissionParser_; static std::set<std::string> uncheckedResources_; static std::list<std::string> uncheckedFolders_; static std::set<OrthancPlugins::Token> tokens_; static std::set<OrthancPlugins::AccessLevel> uncheckedLevels_; +static std::string JoinStrings(const std::set<std::string>& values) +{ + std::string out; + std::set<std::string> copy = values; // TODO: remove after upgrading to OrthancFramework 1.11.3+ + Orthanc::Toolbox::JoinStrings(out, copy, "|"); + return out; +} + static int32_t FilterHttpRequests(OrthancPluginHttpMethod method, const char *uri, const char *ip, @@ -68,6 +79,52 @@ } } + unsigned int validity; // ignored + + // check if the user permissions grants him access + if (permissionParser_.get() != NULL && + authorizationService_.get() != NULL) + // && uncheckedLevels_.find(OrthancPlugins::AccessLevel_UserPermissions) == uncheckedLevels_.end()) + { + std::set<std::string> requiredPermissions; + std::string matchedPattern; + if (permissionParser_->Parse(requiredPermissions, matchedPattern, method, uri)) + { + if (tokens_.empty()) + { + LOG(INFO) << "Testing whether anonymous user has any of the required permissions '" << JoinStrings(requiredPermissions) << "'"; + if (authorizationService_->HasAnonymousUserPermission(validity, requiredPermissions)) + { + return 1; + } + } + else + { + OrthancPlugins::AssociativeArray headers + (headersCount, headersKeys, headersValues, false); + + // Loop over all the authorization tokens stored in the HTTP + // headers, until finding one that is granted + for (std::set<OrthancPlugins::Token>::const_iterator + token = tokens_.begin(); token != tokens_.end(); ++token) + { + std::string value; + + // we consider that users only works with HTTP Header tokens, not tokens from GetArgument + if (token->GetType() == OrthancPlugins::TokenType_HttpHeader && + headers.GetValue(value, token->GetKey())) + { + LOG(INFO) << "Testing whether user has the required permission '" << JoinStrings(requiredPermissions) << "' based on the '" << token->GetKey() << "' HTTP header required to match '" << matchedPattern << "'"; + if (authorizationService_->HasUserPermission(validity, requiredPermissions, *token, value)) + { + return 1; + } + } + } + } + } + } + if (authorizationParser_.get() != NULL && authorizationService_.get() != NULL) { @@ -94,11 +151,10 @@ << " \"" << access->GetOrthancId() << "\" is allowed"; bool granted = false; - unsigned int validity; // ignored if (tokens_.empty()) { - granted = authorizationService_->IsGranted(validity, method, *access); + granted = authorizationService_->IsGrantedToAnonymousUser(validity, method, *access); } else { @@ -193,7 +249,7 @@ { if (authorizationParser_.get() == NULL) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + return OrthancPluginErrorCode_Success; } if (changeType == OrthancPluginChangeType_Deleted) @@ -285,7 +341,8 @@ if (hasValue) { - authorizationService_->GetUserProfile(profile, *token, value); + unsigned int validity; // not used + authorizationService_->GetUserProfile(validity, profile, *token, value); OrthancPlugins::AnswerJson(profile, output); break; @@ -295,6 +352,30 @@ } } +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 + { + a[key] = b[key]; + } + } +} + extern "C" { @@ -322,46 +403,60 @@ try { - OrthancPlugins::OrthancConfiguration general; + static const char* PLUGIN_SECTION = "Authorization"; + + OrthancPlugins::OrthancConfiguration orthancFullConfiguration; + + // read default configuration + std::string defaultConfigurationFileContent; + Orthanc::EmbeddedResources::GetFileResource(defaultConfigurationFileContent, Orthanc::EmbeddedResources::DEFAULT_CONFIGURATION); + Json::Value pluginJsonDefaultConfiguration; + OrthancPlugins::ReadJsonWithoutComments(pluginJsonDefaultConfiguration, defaultConfigurationFileContent); + Json::Value pluginJsonConfiguration = pluginJsonDefaultConfiguration[PLUGIN_SECTION]; - static const char* SECTION = "Authorization"; - if (general.IsSection(SECTION)) + OrthancPlugins::OrthancConfiguration pluginProvidedConfiguration; + + if (orthancFullConfiguration.IsSection(PLUGIN_SECTION)) { - OrthancPlugins::OrthancConfiguration configuration; - general.GetSection(configuration, "Authorization"); + // get the configuration provided by the user + orthancFullConfiguration.GetSection(pluginProvidedConfiguration, PLUGIN_SECTION); + + // merge it with the default configuration. This is a way to apply the all default values in a single step + MergeJson(pluginJsonConfiguration, pluginProvidedConfiguration.GetJson()); + + // recreate a OrthancConfiguration object from the merged configuration + OrthancPlugins::OrthancConfiguration pluginConfiguration(pluginJsonConfiguration, PLUGIN_SECTION); // TODO - The size of the caches is set to 10,000 items. Maybe add a configuration option? OrthancPlugins::MemoryCache::Factory factory(10000); - { - std::string root; + std::string dicomWebRoot = "/dicom-web/"; + std::string oe2Root = "/ui/"; - if (configuration.IsSection("DicomWeb")) - { - OrthancPlugins::OrthancConfiguration dicomWeb; - dicomWeb.GetSection(configuration, "DicomWeb"); - root = dicomWeb.GetStringValue("Root", ""); - } + if (orthancFullConfiguration.IsSection("DicomWeb")) + { + OrthancPlugins::OrthancConfiguration dicomWeb; + dicomWeb.GetSection(orthancFullConfiguration, "DicomWeb"); + dicomWebRoot = dicomWeb.GetStringValue("Root", "/dicom-web/"); + } - if (root.empty()) - { - root = "/dicom-web/"; - } - - authorizationParser_.reset - (new OrthancPlugins::DefaultAuthorizationParser(factory, root)); + if (orthancFullConfiguration.IsSection("OrthancExplorer2")) + { + OrthancPlugins::OrthancConfiguration oe2; + oe2.GetSection(orthancFullConfiguration, "OrthancExplorer2"); + oe2Root = oe2.GetStringValue("Root", "/ui/"); } std::list<std::string> tmp; - configuration.LookupListOfStrings(tmp, "TokenHttpHeaders", true); + pluginConfiguration.LookupListOfStrings(tmp, "TokenHttpHeaders", true); for (std::list<std::string>::const_iterator it = tmp.begin(); it != tmp.end(); ++it) { tokens_.insert(OrthancPlugins::Token(OrthancPlugins::TokenType_HttpHeader, *it)); } - configuration.LookupListOfStrings(tmp, "TokenGetArguments", true); + pluginConfiguration.LookupListOfStrings(tmp, "TokenGetArguments", true); #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 3, 0) for (std::list<std::string>::const_iterator @@ -379,22 +474,49 @@ } #endif - configuration.LookupSetOfStrings(uncheckedResources_, "UncheckedResources", false); - configuration.LookupListOfStrings(uncheckedFolders_, "UncheckedFolders", false); + pluginConfiguration.LookupSetOfStrings(uncheckedResources_, "UncheckedResources", false); + pluginConfiguration.LookupListOfStrings(uncheckedFolders_, "UncheckedFolders", false); std::string url; static const char* WEB_SERVICE = "WebService"; - if (!configuration.LookupStringValue(url, WEB_SERVICE)) + if (!pluginConfiguration.LookupStringValue(url, WEB_SERVICE)) + { + LOG(WARNING) << "Authorization plugin: no \"" << WEB_SERVICE << "\" configuration provided. Will not perform resource based authorization."; + } + else + { + authorizationParser_.reset + (new OrthancPlugins::DefaultAuthorizationParser(factory, dicomWebRoot)); + } + + static const char* WEB_SERVICE_USER_PROFILE = "WebServiceUserProfileUrl"; + static const char* PERMISSIONS = "Permissions"; + if (!pluginConfiguration.LookupStringValue(url, WEB_SERVICE_USER_PROFILE)) { - throw Orthanc::OrthancException( - Orthanc::ErrorCode_BadFileFormat, - "Missing mandatory option \"" + std::string(WEB_SERVICE) + - "\" for the authorization plugin"); + LOG(WARNING) << "Authorization plugin: no \"" << WEB_SERVICE_USER_PROFILE << "\" configuration provided. Will not perform user-permissions based authorization."; + } + else + { + if (!pluginConfiguration.GetJson().isMember(PERMISSIONS)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Authorization plugin: Missing required \"" + std::string(PERMISSIONS) + + "\" option since you have defined the \"" + std::string(WEB_SERVICE_USER_PROFILE) + "\" option"); + } + permissionParser_.reset + (new OrthancPlugins::PermissionParser(dicomWebRoot, oe2Root)); + + permissionParser_->Add(pluginConfiguration.GetJson()[PERMISSIONS]); + } + + if (authorizationParser_.get() == NULL && permissionParser_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Authorization plugin: Missing one of the mandatory option \"" + std::string(WEB_SERVICE) + + "\" or \"" + std::string(WEB_SERVICE_USER_PROFILE) + "\""); } std::set<std::string> standardConfigurations; - if (configuration.LookupSetOfStrings(standardConfigurations, "StandardConfigurations", false)) + if (pluginConfiguration.LookupSetOfStrings(standardConfigurations, "StandardConfigurations", false)) { if (standardConfigurations.find("osimis-web-viewer") != standardConfigurations.end()) { @@ -419,6 +541,7 @@ { uncheckedFolders_.push_back("/ui/app/"); uncheckedResources_.insert("/ui/api/pre-login-configuration"); // for the UI to know, i.e. if Keycloak is enabled or not + uncheckedResources_.insert("/ui/api/configuration"); uncheckedResources_.insert("/auth/user-profile"); tokens_.insert(OrthancPlugins::Token(OrthancPlugins::TokenType_HttpHeader, "Authorization")); // for basic-auth @@ -428,7 +551,7 @@ } std::string checkedLevelString; - if (configuration.LookupStringValue(checkedLevelString, "CheckedLevel")) + if (pluginConfiguration.LookupStringValue(checkedLevelString, "CheckedLevel")) { OrthancPlugins::AccessLevel checkedLevel = OrthancPlugins::StringToAccessLevel(checkedLevelString); if (checkedLevel == OrthancPlugins::AccessLevel_Instance) @@ -457,7 +580,7 @@ } } - if (configuration.LookupListOfStrings(tmp, "UncheckedLevels", false)) + if (pluginConfiguration.LookupListOfStrings(tmp, "UncheckedLevels", false)) { if (uncheckedLevels_.size() == 0) { @@ -477,20 +600,20 @@ std::unique_ptr<OrthancPlugins::AuthorizationWebService> webService(new OrthancPlugins::AuthorizationWebService(url)); std::string webServiceIdentifier; - if (configuration.LookupStringValue(webServiceIdentifier, "WebServiceIdentifier")) + if (pluginConfiguration.LookupStringValue(webServiceIdentifier, "WebServiceIdentifier")) { webService->SetIdentifier(webServiceIdentifier); } std::string webServiceUsername; std::string webServicePassword; - if (configuration.LookupStringValue(webServiceUsername, "WebServiceUsername") && configuration.LookupStringValue(webServicePassword, "WebServicePassword")) + if (pluginConfiguration.LookupStringValue(webServiceUsername, "WebServiceUsername") && pluginConfiguration.LookupStringValue(webServicePassword, "WebServicePassword")) { webService->SetCredentials(webServiceUsername, webServicePassword); } std::string webServiceUserProfileUrl; - if (configuration.LookupStringValue(webServiceUserProfileUrl, "WebServiceUserProfileUrl")) + if (pluginConfiguration.LookupStringValue(webServiceUserProfileUrl, "WebServiceUserProfileUrl")) { webService->SetUserProfileUrl(webServiceUserProfileUrl); } @@ -510,7 +633,7 @@ } else { - LOG(WARNING) << "No section \"" << SECTION << "\" in the configuration file, " + LOG(WARNING) << "No section \"" << PLUGIN_SECTION << "\" in the configuration file, " << "the authorization plugin is disabled"; } }