Mercurial > hg > orthanc-authorization
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