changeset 194:85859ec3aa7e

added support for roles/permissions edition
author Alain Mazy <am@orthanc.team>
date Fri, 14 Jun 2024 16:26:53 +0200
parents c4b908970ae4
children 2f1e872e8eaa
files NEWS Plugin/AuthorizationWebService.cpp Plugin/AuthorizationWebService.h Plugin/CachedAuthorizationService.h Plugin/DefaultConfiguration.json Plugin/IAuthorizationService.h Plugin/PermissionParser.cpp Plugin/PermissionParser.h Plugin/Plugin.cpp
diffstat 9 files changed, 225 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu May 30 21:59:01 2024 +0200
+++ b/NEWS	Fri Jun 14 16:26:53 2024 +0200
@@ -1,3 +1,13 @@
+Pending changes in the mainline
+===============================
+
+* Added support for roles/permissions edition:
+  - new configuration "WebServiceSettingsRolesUrl"
+  - new API routes:
+    - /auth/settings/roles (GET/PUT)
+    - /auth/settings/permissions (GET)
+
+
 2024-05-16 - v 0.7.2
 ====================
 
--- a/Plugin/AuthorizationWebService.cpp	Thu May 30 21:59:01 2024 +0200
+++ b/Plugin/AuthorizationWebService.cpp	Fri Jun 14 16:26:53 2024 +0200
@@ -344,6 +344,7 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
                                       "Syntax error in the result of the Auth Web service, the format of the UserProfile is invalid");
     }
+    // LOG(INFO) << jsonProfile.toStyledString();
 
     profile.name = jsonProfile[USER_NAME].asString();
 
@@ -451,4 +452,76 @@
     return false;
   }
 
+  bool AuthorizationWebService::GetSettingsRoles(Json::Value& roles)
+  {
+    if (settingsRolesUrl_.empty())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "Can not get settings-roles if the 'WebServiceSettingsRolesUrl' is not configured");
+    }
+
+    Orthanc::WebServiceParameters authWebservice;
+
+    if (!username_.empty())
+    {
+      authWebservice.SetCredentials(username_, password_);
+    }
+
+    try
+    {
+      Orthanc::HttpClient authClient(authWebservice, "");
+      authClient.SetUrl(settingsRolesUrl_);
+      authClient.SetMethod(Orthanc::HttpMethod_Get);
+      authClient.AddHeader("Expect", "");
+      authClient.SetTimeout(10);
+
+      authClient.ApplyAndThrowException(roles);
+
+      return true;
+    }
+    catch (Orthanc::OrthancException& ex)
+    {
+      return false;
+    }
+
+  }
+
+  bool AuthorizationWebService::UpdateSettingsRoles(Json::Value& response, const Json::Value& roles)
+  {
+    if (settingsRolesUrl_.empty())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "Can not update settings-roles if the 'WebServiceSettingsRolesUrl' is not configured");
+    }
+
+    Orthanc::WebServiceParameters authWebservice;
+
+    if (!username_.empty())
+    {
+      authWebservice.SetCredentials(username_, password_);
+    }
+
+    try
+    {
+      std::string bodyAsString;
+      Orthanc::Toolbox::WriteFastJson(bodyAsString, roles);
+
+      Orthanc::HttpClient authClient(authWebservice, "");
+      authClient.SetUrl(settingsRolesUrl_);
+      authClient.AssignBody(bodyAsString);
+      authClient.SetMethod(Orthanc::HttpMethod_Put);
+      authClient.AddHeader("Content-Type", "application/json");
+      authClient.AddHeader("Expect", "");
+      authClient.SetTimeout(10);
+
+      authClient.ApplyAndThrowException(response);
+
+      return true;
+    }
+    catch (Orthanc::OrthancException& ex)
+    {
+      return false;
+    }
+
+  }
+
+
 }
--- a/Plugin/AuthorizationWebService.h	Thu May 30 21:59:01 2024 +0200
+++ b/Plugin/AuthorizationWebService.h	Fri Jun 14 16:26:53 2024 +0200
@@ -35,6 +35,7 @@
     std::string tokenValidationUrl_;
     std::string tokenDecoderUrl_;
     std::string tokenCreationBaseUrl_;
+    std::string settingsRolesUrl_;
 
   protected:
     virtual bool IsGrantedInternal(unsigned int& validity,
@@ -56,11 +57,13 @@
     AuthorizationWebService(const std::string& tokenValidationUrl, 
                             const std::string& tokenCreationBaseUrl, 
                             const std::string& userProfileUrl,
-                            const std::string& tokenDecoderUrl) :
+                            const std::string& tokenDecoderUrl,
+                            const std::string& settingsRolesUrl) :
       userProfileUrl_(userProfileUrl),
       tokenValidationUrl_(tokenValidationUrl),
       tokenDecoderUrl_(tokenDecoderUrl),
-      tokenCreationBaseUrl_(tokenCreationBaseUrl)
+      tokenCreationBaseUrl_(tokenCreationBaseUrl),
+      settingsRolesUrl_(settingsRolesUrl)
     {
     }
 
@@ -84,6 +87,11 @@
       return !tokenValidationUrl_.empty();
     }
 
+    virtual bool HasSettingsRoles() const
+    {
+      return !settingsRolesUrl_.empty();
+    }
+
     virtual bool CreateToken(IAuthorizationService::CreatedToken& response,
                              const std::string& tokenType, 
                              const std::string& id, 
@@ -95,6 +103,10 @@
                              const std::string& tokenKey, 
                              const std::string& tokenValue);
 
+    virtual bool GetSettingsRoles(Json::Value& roles);
+    virtual bool UpdateSettingsRoles(Json::Value& response,
+                                     const Json::Value& roles);
+
     static void ToJson(Json::Value& output, const UserProfile& profile);
     
     static void FromJson(UserProfile& profile, const Json::Value& input);
--- a/Plugin/CachedAuthorizationService.h	Thu May 30 21:59:01 2024 +0200
+++ b/Plugin/CachedAuthorizationService.h	Fri Jun 14 16:26:53 2024 +0200
@@ -106,5 +106,15 @@
                                      tokenValue);
     }
 
+    virtual bool GetSettingsRoles(Json::Value& roles)
+    {
+      return decorated_->GetSettingsRoles(roles);
+    }
+
+    virtual bool UpdateSettingsRoles(Json::Value& response, const Json::Value& roles)
+    {
+      return decorated_->UpdateSettingsRoles(response, roles);
+    }
+
  };
 }
--- a/Plugin/DefaultConfiguration.json	Thu May 30 21:59:01 2024 +0200
+++ b/Plugin/DefaultConfiguration.json	Fri Jun 14 16:26:53 2024 +0200
@@ -115,7 +115,13 @@
 
             // settings
             ["put", "^/tools/log-level$", "all|settings"],
-            ["get", "^/tools/log-level$", "all|settings"]
+            ["get", "^/tools/log-level$", "all|settings"],
+ 
+            // permission settings
+            ["put", "^/auth/settings/roles$", "admin-permissions"],
+            ["get", "^/auth/settings/roles$", "admin-permissions"],
+            ["get", "^/auth/settings/permissions$", "admin-permissions"]
+ 
         ]
     }
 }
\ No newline at end of file
--- a/Plugin/IAuthorizationService.h	Thu May 30 21:59:01 2024 +0200
+++ b/Plugin/IAuthorizationService.h	Fri Jun 14 16:26:53 2024 +0200
@@ -110,5 +110,10 @@
     virtual bool HasUserProfile() const = 0;
     virtual bool HasCreateToken() const = 0;
     virtual bool HasTokenValidation() const = 0;
+
+    virtual bool GetSettingsRoles(Json::Value& roles) = 0;
+    virtual bool UpdateSettingsRoles(Json::Value& response,
+                                     const Json::Value& roles) = 0;
+
   };
 }
--- a/Plugin/PermissionParser.cpp	Thu May 30 21:59:01 2024 +0200
+++ b/Plugin/PermissionParser.cpp	Fri Jun 14 16:26:53 2024 +0200
@@ -151,6 +151,20 @@
     LOG(WARNING) << "Authorization plugin: adding a new permission pattern: " << lowerCaseMethod << " " << regex << " - " << permission;
 
     permissionsPattern_.push_back(PermissionPattern(parsedMethod, regex, permission));
+    
+    { // extract individual permissions
+      std::set<std::string> permissions;
+      Orthanc::Toolbox::SplitString(permissions, permission, '|');
+
+      for (std::set<std::string>::const_iterator it = permissions.begin(); it != permissions.end(); ++it)
+      {
+        if (!it->empty())
+        {
+          permissionsList_.insert(*it);
+        }
+      }
+      
+    }
   }
 
   bool PermissionParser::Parse(std::set<std::string>& permissions,
--- a/Plugin/PermissionParser.h	Thu May 30 21:59:01 2024 +0200
+++ b/Plugin/PermissionParser.h	Fri Jun 14 16:26:53 2024 +0200
@@ -41,6 +41,7 @@
   private:
     mutable boost::mutex mutex_; 
     std::list<PermissionPattern> permissionsPattern_;
+    std::set<std::string> permissionsList_;
     std::string dicomWebRoot_;
     std::string oe2Root_;
 
@@ -58,5 +59,10 @@
                std::string& matchedPattern,
                const OrthancPluginHttpMethod& method,
                const std::string& uri) const;
+
+    const std::set<std::string>& GetPermissionsList() const
+    {
+      return permissionsList_;
+    }
   };
 }
--- a/Plugin/Plugin.cpp	Thu May 30 21:59:01 2024 +0200
+++ b/Plugin/Plugin.cpp	Fri Jun 14 16:26:53 2024 +0200
@@ -1054,6 +1054,75 @@
   }
 }
 
+
+void AuthSettingsRoles(OrthancPluginRestOutput* output,
+                       const char* /*url*/,
+                       const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (authorizationService_.get() == NULL) // this is not suppposed to happen
+  {
+    OrthancPlugins::AnswerHttpError(404, output);
+    return;
+  }
+
+  if (request->method == OrthancPluginHttpMethod_Get)
+  {
+    Json::Value roles;
+    
+    if (!authorizationService_->GetSettingsRoles(roles))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Could not retrieve roles from the auth-service", true);
+    }
+
+    OrthancPlugins::AnswerJson(roles, output);
+  }
+  else if (request->method == OrthancPluginHttpMethod_Put)
+  {
+    Json::Value roles;
+    Json::Value response;
+
+    if (!OrthancPlugins::ReadJson(roles, request->body, request->bodySize))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "A JSON payload was expected");
+    }
+
+    if (!authorizationService_->UpdateSettingsRoles(response, roles))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Could not update roles in the auth-service", true);
+    }
+    OrthancPlugins::AnswerJson(response, output);
+  }
+  else
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET,PUT");
+  }
+}
+
+
+void GetPermissionList(OrthancPluginRestOutput* output,
+                       const char* /*url*/,
+                       const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+  }
+  else
+  {
+    std::set<std::string> permissionsList = permissionParser_->GetPermissionsList();
+
+    Json::Value response = Json::arrayValue;
+    Orthanc::SerializationToolbox::WriteSetOfStrings(response, permissionsList);
+
+    OrthancPlugins::AnswerJson(response, output);
+  }
+}
+
+
 void MergeJson(Json::Value &a, const Json::Value &b) {                                                                        
                                                                                                                   
   if (!a.isObject() || !b.isObject())
@@ -1187,6 +1256,7 @@
         std::string urlTokenValidation;
         std::string urlTokenCreationBase;
         std::string urlUserProfile;
+        std::string urlSettingsRole;
         std::string urlRoot;
 
         static const char* WEB_SERVICE_ROOT = "WebServiceRootUrl";
@@ -1194,6 +1264,7 @@
         static const char* WEB_SERVICE_TOKEN_VALIDATION = "WebServiceTokenValidationUrl";
         static const char* WEB_SERVICE_TOKEN_CREATION_BASE = "WebServiceTokenCreationBaseUrl";
         static const char* WEB_SERVICE_USER_PROFILE = "WebServiceUserProfileUrl";
+        static const char* WEB_SERVICE_SETTINGS_ROLES = "WebServiceSettingsRolesUrl";
         static const char* WEB_SERVICE_TOKEN_VALIDATION_LEGACY = "WebService";
         if (pluginConfiguration.LookupStringValue(urlRoot, WEB_SERVICE_ROOT))
         {
@@ -1201,6 +1272,7 @@
           urlTokenValidation = Orthanc::Toolbox::JoinUri(urlRoot, "/tokens/validate");
           urlTokenCreationBase = Orthanc::Toolbox::JoinUri(urlRoot, "/tokens/");
           urlUserProfile = Orthanc::Toolbox::JoinUri(urlRoot, "/user/get-profile");
+          urlSettingsRole = Orthanc::Toolbox::JoinUri(urlRoot, "/settings/roles");
         }
         else 
         {
@@ -1213,6 +1285,7 @@
 
           pluginConfiguration.LookupStringValue(urlTokenCreationBase, WEB_SERVICE_TOKEN_CREATION_BASE);
           pluginConfiguration.LookupStringValue(urlUserProfile, WEB_SERVICE_USER_PROFILE);
+          pluginConfiguration.LookupStringValue(urlSettingsRole, WEB_SERVICE_SETTINGS_ROLES);
         }
 
         authorizationParser_.reset(new OrthancPlugins::DefaultAuthorizationParser(factory, dicomWebRoot));
@@ -1259,6 +1332,15 @@
           LOG(WARNING) << "Authorization plugin: no base url defined for Token Creation";
         }
 
+        if (!urlSettingsRole.empty())
+        {
+          LOG(WARNING) << "Authorization plugin: settings-roles url defined : " << urlSettingsRole;
+        }
+        else
+        {
+          LOG(WARNING) << "Authorization plugin: no settings-roles url defined";
+        }
+
         if (!resourceTokensEnabled_ && permissionParser_.get() == NULL)
         {
           if (hasBasicAuthEnabled)
@@ -1367,7 +1449,8 @@
         std::unique_ptr<OrthancPlugins::AuthorizationWebService> webService(new OrthancPlugins::AuthorizationWebService(urlTokenValidation,
                                                                                                                         urlTokenCreationBase,
                                                                                                                         urlUserProfile,
-                                                                                                                        urlTokenDecoder));
+                                                                                                                        urlTokenDecoder,
+                                                                                                                        urlSettingsRole));
 
         std::string webServiceIdentifier;
         if (pluginConfiguration.LookupStringValue(webServiceIdentifier, "WebServiceIdentifier"))
@@ -1401,6 +1484,8 @@
           OrthancPlugins::RegisterRestCallback<GetUserProfile>("/auth/user/profile", true);
           OrthancPlugins::RegisterRestCallback<ToolsFind>("/tools/find", true);
           OrthancPlugins::RegisterRestCallback<ToolsLabels>("/tools/labels", true);
+          OrthancPlugins::RegisterRestCallback<AuthSettingsRoles>("/auth/settings/roles", true);
+          OrthancPlugins::RegisterRestCallback<GetPermissionList>("/auth/settings/permissions", true);
         }
 
         if (!urlTokenCreationBase.empty())