changeset 74:aa73b10c2db9

new API route to decode tokens
author Alain Mazy <am@osimis.io>
date Fri, 03 Mar 2023 18:03:22 +0100
parents 512247750f0a
children 57e98fc07ab2
files NEWS Plugin/AuthorizationWebService.cpp Plugin/AuthorizationWebService.h Plugin/BaseAuthorizationService.h Plugin/CachedAuthorizationService.h Plugin/IAuthorizationService.h Plugin/PermissionParser.cpp Plugin/Plugin.cpp
diffstat 8 files changed, 179 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Mar 03 10:41:27 2023 +0100
+++ b/NEWS	Fri Mar 03 18:03:22 2023 +0100
@@ -6,6 +6,7 @@
 * new "orthanc-explorer-2" StandardConfigurations
 * new GET "auth/user/profile" Rest API route to retrieve user permissions
 * new PUT "auth/tokens/{token-type}" Rest API route to create tokens
+* new POST "auth/tokens/decode" Rest API route to decode tokens
 
 
 2022-11-16 - v 0.4.1
--- a/Plugin/AuthorizationWebService.cpp	Fri Mar 03 10:41:27 2023 +0100
+++ b/Plugin/AuthorizationWebService.cpp	Fri Mar 03 18:03:22 2023 +0100
@@ -163,6 +163,67 @@
     identifier_ = webServiceIdentifier;
   }
 
+
+  bool AuthorizationWebService::DecodeToken(DecodedToken& response,
+                                            const std::string& tokenKey, 
+                                            const std::string& tokenValue)
+  {
+    if (tokenDecoderUrl_.empty())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "Can not create tokens if the 'WebServiceTokenValidationUrl' is not configured");
+    }
+    Orthanc::WebServiceParameters authWebservice;
+
+    if (!username_.empty())
+    {
+      authWebservice.SetCredentials(username_, password_);
+    }
+
+    Json::Value body;
+
+    body["token-key"] = tokenKey;
+    body["token-value"] = tokenValue;
+
+    std::string bodyAsString;
+    Orthanc::Toolbox::WriteFastJson(bodyAsString, body);
+
+    Json::Value tokenResponse;
+    try
+    {
+      Orthanc::HttpClient authClient(authWebservice, "");
+      authClient.SetUrl(tokenDecoderUrl_);
+      authClient.AssignBody(bodyAsString);
+      authClient.SetMethod(Orthanc::HttpMethod_Post);
+      authClient.AddHeader("Content-Type", "application/json");
+      authClient.AddHeader("Expect", "");
+      authClient.SetTimeout(10);
+
+      authClient.ApplyAndThrowException(tokenResponse);
+
+      if (tokenResponse.isMember("redirect-url"))
+      {
+        response.redirectUrl = tokenResponse["redirect-url"].asString();
+      }
+
+      if (tokenResponse.isMember("error-code"))
+      {
+        response.errorCode = tokenResponse["error-code"].asString();
+      }
+
+      if (tokenResponse.isMember("token-type"))
+      {
+        response.tokenType = tokenResponse["token-type"].asString();
+      }
+
+      return true;
+    }
+    catch (Orthanc::OrthancException& ex)
+    {
+      return false;
+    }
+
+  }
+
   bool AuthorizationWebService::CreateToken(IAuthorizationService::CreatedToken& response,
                                             const std::string& tokenType, 
                                             const std::string& id, 
--- a/Plugin/AuthorizationWebService.h	Fri Mar 03 10:41:27 2023 +0100
+++ b/Plugin/AuthorizationWebService.h	Fri Mar 03 18:03:22 2023 +0100
@@ -31,6 +31,7 @@
     std::string identifier_;
     std::string userProfileUrl_;
     std::string tokenValidationUrl_;
+    std::string tokenDecoderUrl_;
     std::string tokenCreationBaseUrl_;
 
   protected:
@@ -53,9 +54,11 @@
   public:
     AuthorizationWebService(const std::string& tokenValidationUrl, 
                             const std::string& tokenCreationBaseUrl, 
-                            const std::string& userProfileUrl) :
+                            const std::string& userProfileUrl,
+                            const std::string& tokenDecoderUrl) :
       userProfileUrl_(userProfileUrl),
       tokenValidationUrl_(tokenValidationUrl),
+      tokenDecoderUrl_(tokenDecoderUrl),
       tokenCreationBaseUrl_(tokenCreationBaseUrl)
     {
     }
@@ -87,5 +90,9 @@
                              const std::string& expirationDateString,
                              const uint64_t& validityDuration) ORTHANC_OVERRIDE;
 
+    virtual bool DecodeToken(DecodedToken& response,
+                             const std::string& tokenKey, 
+                             const std::string& tokenValue);
+
   };
 }
--- a/Plugin/BaseAuthorizationService.h	Fri Mar 03 10:41:27 2023 +0100
+++ b/Plugin/BaseAuthorizationService.h	Fri Mar 03 18:03:22 2023 +0100
@@ -85,6 +85,11 @@
                                    const Token& token,
                                    const std::string& tokenValue)
     {
+      if (anyOfPermissions.size() == 0)
+      {
+        return true;
+      }
+
       for (std::set<std::string>::const_iterator it = anyOfPermissions.begin(); it != anyOfPermissions.end(); ++it)
       {
         if (HasUserPermissionInternal(validity, *it, &token, tokenValue))
@@ -98,6 +103,11 @@
     virtual bool HasAnonymousUserPermission(unsigned int& validity /* out */,
                                             const std::set<std::string>& anyOfPermissions)
     {
+      if (anyOfPermissions.size() == 0)
+      {
+        return true;
+      }
+
       for (std::set<std::string>::const_iterator it = anyOfPermissions.begin(); it != anyOfPermissions.end(); ++it)
       {
         if (HasUserPermissionInternal(validity, *it, NULL, ""))
--- a/Plugin/CachedAuthorizationService.h	Fri Mar 03 10:41:27 2023 +0100
+++ b/Plugin/CachedAuthorizationService.h	Fri Mar 03 18:03:22 2023 +0100
@@ -81,12 +81,12 @@
       return decorated_->HasTokenValidation();
     }
 
-    bool CreateToken(IAuthorizationService::CreatedToken& response,
-                     const std::string& tokenType, 
-                     const std::string& id, 
-                     const std::vector<IAuthorizationService::OrthancResource>& resources,
-                     const std::string& expirationDateString,
-                     const uint64_t& validityDuration)
+    virtual bool CreateToken(IAuthorizationService::CreatedToken& response,
+                             const std::string& tokenType, 
+                             const std::string& id, 
+                             const std::vector<IAuthorizationService::OrthancResource>& resources,
+                             const std::string& expirationDateString,
+                             const uint64_t& validityDuration)
     {
       return decorated_->CreateToken(response,
                                      tokenType,
@@ -96,5 +96,14 @@
                                      validityDuration);
     }
 
+    virtual bool DecodeToken(DecodedToken& response,
+                             const std::string& tokenKey, 
+                             const std::string& tokenValue)
+    {
+      return decorated_->DecodeToken(response,
+                                     tokenKey,
+                                     tokenValue);
+    }
+
  };
 }
--- a/Plugin/IAuthorizationService.h	Fri Mar 03 10:41:27 2023 +0100
+++ b/Plugin/IAuthorizationService.h	Fri Mar 03 18:03:22 2023 +0100
@@ -45,6 +45,13 @@
       std::string token;
     };
 
+    struct DecodedToken
+    {
+      std::string redirectUrl;
+      std::string errorCode;
+      std::string tokenType;
+    };
+
     virtual ~IAuthorizationService()
     {
     }
@@ -82,6 +89,10 @@
                              const std::string& expirationDateString,
                              const uint64_t& validityDuration) = 0;
 
+    virtual bool DecodeToken(DecodedToken& response,
+                             const std::string& tokenKey, 
+                             const std::string& tokenValue) = 0;
+
     virtual bool HasUserProfile() const = 0;
     virtual bool HasCreateToken() const = 0;
     virtual bool HasTokenValidation() const = 0;
--- a/Plugin/PermissionParser.cpp	Fri Mar 03 10:41:27 2023 +0100
+++ b/Plugin/PermissionParser.cpp	Fri Mar 03 18:03:22 2023 +0100
@@ -28,12 +28,15 @@
     method(method),
     pattern(patternRegex)
   {
-    std::vector<std::string> permissionsVector;
-    Orthanc::Toolbox::TokenizeString(permissionsVector, permissions, '|');
+    if (!permissions.empty())
+    {
+      std::vector<std::string> permissionsVector;
+      Orthanc::Toolbox::TokenizeString(permissionsVector, permissions, '|');
 
-    for (size_t i = 0; i < permissionsVector.size(); ++i)
-    {
-      this->permissions.insert(permissionsVector[i]);
+      for (size_t i = 0; i < permissionsVector.size(); ++i)
+      {
+        this->permissions.insert(permissionsVector[i]);
+      }
     }
   }
 
--- a/Plugin/Plugin.cpp	Fri Mar 03 10:41:27 2023 +0100
+++ b/Plugin/Plugin.cpp	Fri Mar 03 18:03:22 2023 +0100
@@ -419,6 +419,57 @@
   }
 }
 
+void DecodeToken(OrthancPluginRestOutput* output,
+                 const char* /*url*/,
+                 const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Post)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "POST");
+  }
+  else
+  {
+    // 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");
+    }
+
+    Json::Value authPayload;
+
+    authPayload["token-key"] = body["TokenKey"].asString();
+    authPayload["token-value"] = body["TokenValue"].asString();
+
+    OrthancPlugins::IAuthorizationService::DecodedToken decodedToken;
+    if (authorizationService_->DecodeToken(decodedToken,
+                                           body["TokenKey"].asString(),
+                                           body["TokenValue"].asString()))
+    {
+      Json::Value decodedJsonToken;
+      
+      if (!decodedToken.redirectUrl.empty())
+      {
+        decodedJsonToken["RedirectUrl"] = decodedToken.redirectUrl;
+      }
+
+      if (!decodedToken.errorCode.empty())
+      {
+        decodedJsonToken["ErrorCode"] = decodedToken.errorCode;
+      }
+
+      if (!decodedToken.tokenType.empty())
+      {
+        decodedJsonToken["TokenType"] = decodedToken.tokenType;
+      }
+
+      OrthancPlugins::AnswerJson(decodedJsonToken, output);
+    }
+  }
+}
+
 void GetUserProfile(OrthancPluginRestOutput* output,
                     const char* /*url*/,
                     const OrthancPluginHttpRequest* request)
@@ -599,18 +650,21 @@
         pluginConfiguration.LookupSetOfStrings(uncheckedResources_, "UncheckedResources", false);
         pluginConfiguration.LookupListOfStrings(uncheckedFolders_, "UncheckedFolders", false);
 
+        std::string urlTokenDecoder;
         std::string urlTokenValidation;
         std::string urlTokenCreationBase;
         std::string urlUserProfile;
         std::string urlRoot;
 
         static const char* WEB_SERVICE_ROOT = "WebServiceRootUrl";
+        static const char* WEB_SERVICE_TOKEN_DECODER = "WebServiceTokenDecoderUrl";
         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))
         {
+          urlTokenDecoder = Orthanc::Toolbox::JoinUri(urlRoot, "/tokens/decode");
           urlTokenValidation = Orthanc::Toolbox::JoinUri(urlRoot, "/tokens/validate");
           urlTokenCreationBase = Orthanc::Toolbox::JoinUri(urlRoot, "/tokens/");
           urlUserProfile = Orthanc::Toolbox::JoinUri(urlRoot, "/user/get-profile");
@@ -618,6 +672,7 @@
         else 
         {
           pluginConfiguration.LookupStringValue(urlTokenValidation, WEB_SERVICE_TOKEN_VALIDATION);
+          pluginConfiguration.LookupStringValue(urlTokenDecoder, WEB_SERVICE_TOKEN_DECODER);
           if (urlTokenValidation.empty())
           {
             pluginConfiguration.LookupStringValue(urlTokenValidation, WEB_SERVICE_TOKEN_VALIDATION_LEGACY);
@@ -698,6 +753,7 @@
           if (standardConfigurations.find("orthanc-explorer-2") != standardConfigurations.end())
           {
             uncheckedFolders_.push_back("/ui/app/");
+            uncheckedFolders_.push_back("/ui/landing/");
             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");
@@ -757,7 +813,8 @@
 
         std::unique_ptr<OrthancPlugins::AuthorizationWebService> webService(new OrthancPlugins::AuthorizationWebService(urlTokenValidation,
                                                                                                                         urlTokenCreationBase,
-                                                                                                                        urlUserProfile));
+                                                                                                                        urlUserProfile,
+                                                                                                                        urlTokenDecoder));
 
         std::string webServiceIdentifier;
         if (pluginConfiguration.LookupStringValue(webServiceIdentifier, "WebServiceIdentifier"))
@@ -781,6 +838,11 @@
           OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
         }
         
+        if (!urlTokenDecoder.empty())
+        {
+          OrthancPlugins::RegisterRestCallback<DecodeToken>("/auth/tokens/decode", true);
+        }
+
         if (!urlUserProfile.empty())
         {
           OrthancPlugins::RegisterRestCallback<GetUserProfile>("/auth/user/profile", true);
@@ -790,7 +852,8 @@
         {
           OrthancPlugins::RegisterRestCallback<CreateToken>("/auth/tokens/(.*)", true);
         }
-        
+
+
 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 1)
         OrthancPluginRegisterIncomingHttpRequestFilter2(context, FilterHttpRequests);
 #else