diff Plugin/Plugin.cpp @ 72:e381ba725669

new PUT auth/tokens/{token-type} API route + updated interface with WebService
author Alain Mazy <am@osimis.io>
date Fri, 24 Feb 2023 18:13:36 +0100
parents 30fb3ce960d9
children 512247750f0a
line wrap: on
line diff
--- a/Plugin/Plugin.cpp	Wed Feb 22 13:13:38 2023 +0100
+++ b/Plugin/Plugin.cpp	Fri Feb 24 18:13:36 2023 +0100
@@ -49,6 +49,19 @@
   return out;
 }
 
+struct TokenAndValue
+{
+  const OrthancPlugins::Token& token;
+  std::string value;
+
+  TokenAndValue(const OrthancPlugins::Token& token, const std::string& value) :
+    token(token),
+    value(value)
+  {
+  }
+};
+
+
 static int32_t FilterHttpRequests(OrthancPluginHttpMethod method,
                                   const char *uri,
                                   const char *ip,
@@ -61,6 +74,8 @@
 {
   try
   {
+    unsigned int validity;  // ignored
+
     if (method == OrthancPluginHttpMethod_Get)
     {
       // Allow GET accesses to static resources
@@ -79,7 +94,35 @@
       }
     }
 
-    unsigned int validity;  // ignored
+    OrthancPlugins::AssociativeArray headers(headersCount, headersKeys, headersValues, false);
+    OrthancPlugins::AssociativeArray getArguments(getArgumentsCount, getArgumentsKeys, getArgumentsValues, true);
+
+    std::vector<TokenAndValue> authTokens;  // the tokens that are set in this request
+
+    for (std::set<OrthancPlugins::Token>::const_iterator token = tokens_.begin(); token != tokens_.end(); ++token)
+    {
+      std::string value;
+
+      bool hasValue = false;
+      switch (token->GetType())
+      {
+        case OrthancPlugins::TokenType_HttpHeader:
+          hasValue = headers.GetValue(value, token->GetKey());
+          break;
+
+        case OrthancPlugins::TokenType_GetArgument:
+          hasValue = getArguments.GetValue(value, token->GetKey());
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      
+      if (hasValue)
+      {
+        authTokens.push_back(TokenAndValue(*token, value));
+      }
+    }
 
     // check if the user permissions grants him access
     if (permissionParser_.get() != NULL &&
@@ -90,7 +133,7 @@
       std::string matchedPattern;
       if (permissionParser_->Parse(requiredPermissions, matchedPattern, method, uri))
       {
-        if (tokens_.empty())
+        if (authTokens.empty())
         {
           LOG(INFO) << "Testing whether anonymous user has any of the required permissions '" << JoinStrings(requiredPermissions) << "'";
           if (authorizationService_->HasAnonymousUserPermission(validity, requiredPermissions))
@@ -100,25 +143,12 @@
         }
         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)
+          for (size_t i = 0; i < authTokens.size(); ++i)
           {
-            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 '" << authTokens[i].token.GetKey() << "' HTTP header required to match '" << matchedPattern << "'";
+            if (authorizationService_->HasUserPermission(validity, requiredPermissions, authTokens[i].token, authTokens[i].value))
             {
-              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;
-              }
+              return 1;
             }
           }
         }
@@ -130,7 +160,6 @@
     {
       // Parse the resources that are accessed through this URI
       OrthancPlugins::IAuthorizationParser::AccessedResources accesses;
-      OrthancPlugins::AssociativeArray getArguments(getArgumentsCount, getArgumentsKeys, getArgumentsValues, true);
 
       if (!authorizationParser_->Parse(accesses, uri, getArguments.GetMap()))
       {
@@ -152,39 +181,16 @@
 
           bool granted = false;
 
-          if (tokens_.empty())
+          if (authTokens.empty())
           {
             granted = authorizationService_->IsGrantedToAnonymousUser(validity, method, *access);
           }
           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)
+            // Loop over all the authorization tokens in the request until finding one that is granted
+            for (size_t i = 0; i < authTokens.size(); ++i)
             {
-              std::string value;
-
-              bool hasValue = false;
-              switch (token->GetType())
-              {
-                case OrthancPlugins::TokenType_HttpHeader:
-                  hasValue = headers.GetValue(value, token->GetKey());
-                  break;
-
-                case OrthancPlugins::TokenType_GetArgument:
-                  hasValue = getArguments.GetValue(value, token->GetKey());
-                  break;
-
-                default:
-                  throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-              }
-              
-              if (hasValue &&
-                  authorizationService_->IsGranted(validity, method, *access, *token, value))
+              if (authorizationService_->IsGranted(validity, method, *access, authTokens[i].token, authTokens[i].value))
               {
                 granted = true;
                 break;
@@ -296,6 +302,103 @@
   }
 }
 
+void CreateToken(OrthancPluginRestOutput* output,
+                 const char* /*url*/,
+                 const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Put)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "PUT");
+  }
+  else
+  {
+    // The filtering to this route is performed by this plugin as it is done for any other route before we get here.
+    // Since the route contains the tokenType, we can allow/forbid creating them based on the url
+
+    // simply forward the request to the auth-service
+    std::string tokenType;
+    if (request->groupsCount == 1)
+    {
+      tokenType = request->groups[0];
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    // convert from Orthanc flavored API to WebService API
+    Json::Value body;
+    if (!OrthancPlugins::ReadJson(body, request->body, request->bodySize))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "A JSON payload was expected");
+    }
+
+    std::string id;
+    std::vector<OrthancPlugins::IAuthorizationService::OrthancResource> resources;
+    std::string expirationDateString;
+
+    if (body.isMember("ID"))
+    {
+      id = body["ID"].asString();
+    }
+
+    for (Json::ArrayIndex i = 0; i < body["Resources"].size(); ++i)
+    {
+      const Json::Value& jsonResource = body["Resources"][i];
+      OrthancPlugins::IAuthorizationService::OrthancResource resource;
+
+      if (jsonResource.isMember("DicomUid"))
+      {
+        resource.dicomUid = jsonResource["DicomUid"].asString();
+      }
+
+      if (jsonResource.isMember("OrthancId"))
+      {
+        resource.orthancId = jsonResource["OrthancId"].asString();
+      }
+
+      if (jsonResource.isMember("Url"))
+      {
+        resource.url = jsonResource["Url"].asString();
+      }
+
+      resource.level = jsonResource["Level"].asString();
+      resources.push_back(resource);
+    }
+
+    if (body.isMember("ExpirationDate"))
+    {
+      expirationDateString = body["ExpirationDate"].asString();
+    }
+
+    OrthancPlugins::IAuthorizationService::CreatedToken createdToken;
+    if (authorizationService_->CreateToken(createdToken,
+                                           tokenType,
+                                           id,
+                                           resources,
+                                           expirationDateString))
+    {
+      Json::Value createdJsonToken;
+      createdJsonToken["Token"] = createdToken.token;
+      
+      if (!createdToken.url.empty())
+      {
+        createdJsonToken["Url"] = createdToken.url;
+      }
+      else
+      {
+        createdJsonToken["Url"] = Json::nullValue;
+      }
+
+      OrthancPlugins::AnswerJson(createdJsonToken, output);
+    }
+    
+
+  }
+}
+
 void GetUserProfile(OrthancPluginRestOutput* output,
                     const char* /*url*/,
                     const OrthancPluginHttpRequest* request)
@@ -314,7 +417,6 @@
     OrthancPlugins::AssociativeArray getArguments
       (request->getCount, request->getKeys, request->getValues, true);
 
-
     // Loop over all the authorization tokens stored in the HTTP
     // headers, until finding one that is granted
     for (std::set<OrthancPlugins::Token>::const_iterator
@@ -477,42 +579,78 @@
         pluginConfiguration.LookupSetOfStrings(uncheckedResources_, "UncheckedResources", false);
         pluginConfiguration.LookupListOfStrings(uncheckedFolders_, "UncheckedFolders", false);
 
-        std::string url;
+        std::string urlTokenValidation;
+        std::string urlTokenCreationBase;
+        std::string urlUserProfile;
+        std::string urlRoot;
 
-        static const char* WEB_SERVICE = "WebService";
-        if (!pluginConfiguration.LookupStringValue(url, WEB_SERVICE))
+        static const char* WEB_SERVICE_ROOT = "WebServiceRootUrl";
+        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_TOKEN_VALIDATION_LEGACY = "WebService";
+        if (pluginConfiguration.LookupStringValue(urlRoot, WEB_SERVICE_ROOT))
         {
-          LOG(WARNING) << "Authorization plugin: no \"" << WEB_SERVICE << "\" configuration provided.  Will not perform resource based authorization.";
+          urlTokenValidation = Orthanc::Toolbox::JoinUri(urlRoot, "/tokens/validate");
+          urlTokenCreationBase = Orthanc::Toolbox::JoinUri(urlRoot, "/tokens/");
+          urlUserProfile = Orthanc::Toolbox::JoinUri(urlRoot, "/user/get-profile");
         }
-        else
+        else 
         {
+          pluginConfiguration.LookupStringValue(urlTokenValidation, WEB_SERVICE_TOKEN_VALIDATION);
+          if (urlTokenValidation.empty())
+          {
+            pluginConfiguration.LookupStringValue(urlTokenValidation, WEB_SERVICE_TOKEN_VALIDATION_LEGACY);
+          }
+
+          pluginConfiguration.LookupStringValue(urlTokenCreationBase, WEB_SERVICE_TOKEN_CREATION_BASE);
+          pluginConfiguration.LookupStringValue(urlUserProfile, WEB_SERVICE_USER_PROFILE);
+        }
+
+        if (!urlTokenValidation.empty())
+        {
+          LOG(WARNING) << "Authorization plugin: url defined for Token Validation: " << urlTokenValidation;
           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))
-        {
-          LOG(WARNING) << "Authorization plugin: no \"" << WEB_SERVICE_USER_PROFILE << "\" configuration provided.  Will not perform user-permissions based authorization.";
-        }
         else
         {
+          LOG(WARNING) << "Authorization plugin: no url defined for Token Validation";
+        }
+
+        if (!urlUserProfile.empty())
+        {
+          LOG(WARNING) << "Authorization plugin: url defined for User Profile: " << urlUserProfile;
+          
+          static const char* PERMISSIONS = "Permissions";        
           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");
+              "\" option since you have defined the \"" + std::string(WEB_SERVICE_ROOT) + "\" option");
           }
           permissionParser_.reset
             (new OrthancPlugins::PermissionParser(dicomWebRoot, oe2Root));
 
           permissionParser_->Add(pluginConfiguration.GetJson()[PERMISSIONS]);
         }
+        else
+        {
+          LOG(WARNING) << "Authorization plugin: no url defined for User Profile";
+        }
+
+        if (!urlTokenCreationBase.empty())
+        {
+          LOG(WARNING) << "Authorization plugin: base url defined for Token Creation : " << urlTokenCreationBase;
+          // TODO Token Creation
+        }
+        else
+        {
+          LOG(WARNING) << "Authorization plugin: no base url defined for Token Creation";
+        }
 
         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) + "\"");
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Authorization plugin: No Token Validation or User Profile url defined");
         }
 
         std::set<std::string> standardConfigurations;
@@ -597,7 +735,9 @@
           }
         }
 
-        std::unique_ptr<OrthancPlugins::AuthorizationWebService> webService(new OrthancPlugins::AuthorizationWebService(url));
+        std::unique_ptr<OrthancPlugins::AuthorizationWebService> webService(new OrthancPlugins::AuthorizationWebService(urlTokenValidation,
+                                                                                                                        urlTokenCreationBase,
+                                                                                                                        urlUserProfile));
 
         std::string webServiceIdentifier;
         if (pluginConfiguration.LookupStringValue(webServiceIdentifier, "WebServiceIdentifier"))
@@ -612,18 +752,24 @@
           webService->SetCredentials(webServiceUsername, webServicePassword);
         }
 
-        std::string webServiceUserProfileUrl;
-        if (pluginConfiguration.LookupStringValue(webServiceUserProfileUrl, "WebServiceUserProfileUrl"))
-        {
-          webService->SetUserProfileUrl(webServiceUserProfileUrl);
-        }
-
         authorizationService_.reset
           (new OrthancPlugins::CachedAuthorizationService
            (webService.release(), factory));
 
-        OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
-        OrthancPlugins::RegisterRestCallback<GetUserProfile>("/auth/user-profile", true);
+        if (!urlTokenValidation.empty())
+        {
+          OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
+        }
+        
+        if (!urlUserProfile.empty())
+        {
+          OrthancPlugins::RegisterRestCallback<GetUserProfile>("/auth/user/profile", true);
+        }
+
+        if (!urlTokenCreationBase.empty())
+        {
+          OrthancPlugins::RegisterRestCallback<CreateToken>("/auth/tokens/(.*)", true);
+        }
         
 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 1)
         OrthancPluginRegisterIncomingHttpRequestFilter2(context, FilterHttpRequests);