Mercurial > hg > orthanc-authorization
changeset 259:a2f7066fd932 inbox
audit logs continued
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Thu, 24 Jul 2025 17:59:45 +0200 |
parents | 3f2a3192594a |
children | 95b22ee07bc3 |
files | NEWS Plugin/AuthorizationWebService.cpp Plugin/AuthorizationWebService.h Plugin/CachedAuthorizationService.cpp Plugin/CachedAuthorizationService.h Plugin/DefaultConfiguration.json Plugin/IAuthorizationService.h Plugin/Plugin.cpp |
diffstat | 8 files changed, 207 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Tue Jul 22 17:25:07 2025 +0200 +++ b/NEWS Thu Jul 24 17:59:45 2025 +0200 @@ -14,9 +14,9 @@ * New experimental feature: audit-logs - Enabled by the "EnableAuditLogs" configuration. - Audit-logs are currently handled by the PostgreSQL plugin and can be - browsed through the route /plugins/postgresql/audit-logs. + browsed through the route /auth/audit-logs. - New default permission "audit-logs" to grant access to the - "/plugins/postgresql/audit-logs" route. + "/auth/audit-logs" route.
--- a/Plugin/AuthorizationWebService.cpp Tue Jul 22 17:25:07 2025 +0200 +++ b/Plugin/AuthorizationWebService.cpp Thu Jul 24 17:59:45 2025 +0200 @@ -388,6 +388,71 @@ } } + bool AuthorizationWebService::GetUserProfileFromUserId(unsigned int& validity, + UserProfile& profile /* out */, + const std::string& userId) + { + if (userProfileUrl_.empty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "Can not get user profile if the 'WebServiceUserProfileUrl' is not configured"); + } + + Json::Value body; + + body["user-id"] = userId; + + if (!identifier_.empty()) + { + body["identifier"] = identifier_; + } + else + { + body["identifier"] = Json::nullValue; + } + + std::string bodyAsString; + Orthanc::Toolbox::WriteFastJson(bodyAsString, body); + + try + { + HttpClient authClient; + authClient.SetUrl(userProfileUrl_); + if (!username_.empty()) + { + authClient.SetCredentials(username_, password_); + } + authClient.SetBody(bodyAsString); + authClient.SetMethod(OrthancPluginHttpMethod_Post); + authClient.AddHeader("Content-Type", "application/json"); + authClient.AddHeader("Expect", ""); + authClient.SetTimeout(10); + + Json::Value jsonProfile; + OrthancPlugins::HttpHeaders answerHeaders; + authClient.Execute(answerHeaders, jsonProfile); + + if (jsonProfile.isNull()) + { + validity = 60; + return false; + } + else if (!jsonProfile.isMember(VALIDITY) || + jsonProfile[VALIDITY].type() != Json::intValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Syntax error in the result of the Auth Web service, the format of the UserProfile is invalid"); + } + validity = jsonProfile[VALIDITY].asUInt(); + + FromJson(profile, jsonProfile); + + return true; + } + catch (Orthanc::OrthancException& ex) + { + return false; + } + } bool AuthorizationWebService::GetUserProfileInternal(unsigned int& validity,
--- a/Plugin/AuthorizationWebService.h Tue Jul 22 17:25:07 2025 +0200 +++ b/Plugin/AuthorizationWebService.h Thu Jul 24 17:59:45 2025 +0200 @@ -110,5 +110,8 @@ static void FromJson(UserProfile& profile, const Json::Value& input); + virtual bool GetUserProfileFromUserId(unsigned int& validity, + UserProfile& profile /* out */, + const std::string& userId) ORTHANC_OVERRIDE; }; }
--- a/Plugin/CachedAuthorizationService.cpp Tue Jul 22 17:25:07 2025 +0200 +++ b/Plugin/CachedAuthorizationService.cpp Thu Jul 24 17:59:45 2025 +0200 @@ -66,7 +66,8 @@ CachedAuthorizationService::CachedAuthorizationService(BaseAuthorizationService* decorated /* takes ownership */, ICacheFactory& factory) : decorated_(decorated), - cache_(factory.Create()) + cache_(factory.Create()), + cacheUserId_(factory.Create()) { if (decorated_.get() == NULL) { @@ -114,7 +115,61 @@ } } - + bool CachedAuthorizationService::GetUserProfileFromUserId(unsigned int& validityNotUsed, + UserProfile& profile /* out */, + const std::string& userId) + { + assert(decorated_.get() != NULL); + + std::string key = "user-id-" + userId; + std::string serializedProfile; + + if (cacheUserId_->Retrieve(serializedProfile, key)) + { + // Return the previously cached profile + Json::Value jsonProfile; + + Orthanc::Toolbox::ReadJson(jsonProfile, serializedProfile); + + AuthorizationWebService::FromJson(profile, jsonProfile); + + return true; + } + else + { + unsigned int validity; + + if (decorated_->GetUserProfileFromUserId(validity, profile, userId)) + { + Json::Value jsonProfile; + + AuthorizationWebService::ToJson(jsonProfile, profile); + Orthanc::Toolbox::WriteFastJson(serializedProfile, jsonProfile); + + cacheUserId_->Store(key, serializedProfile, validity); + + return true; + } + else // if no user was found, store it as a profile where the user name is the user id + { + validity = 60; + profile.userId = userId; + profile.name = userId; + + Json::Value jsonProfile; + + AuthorizationWebService::ToJson(jsonProfile, profile); + Orthanc::Toolbox::WriteFastJson(serializedProfile, jsonProfile); + + cacheUserId_->Store(key, serializedProfile, validity); + + return true; + } + } + + return false; + } + bool CachedAuthorizationService::GetUserProfileInternal(unsigned int& validityNotUsed, UserProfile& profile /* out */, const Token* token,
--- a/Plugin/CachedAuthorizationService.h Tue Jul 22 17:25:07 2025 +0200 +++ b/Plugin/CachedAuthorizationService.h Thu Jul 24 17:59:45 2025 +0200 @@ -37,6 +37,7 @@ private: std::unique_ptr<BaseAuthorizationService> decorated_; std::unique_ptr<ICache> cache_; + std::unique_ptr<ICache> cacheUserId_; std::string ComputeKey(OrthancPluginHttpMethod method, const AccessedResource& access, @@ -58,6 +59,10 @@ const Token* token, const std::string& tokenValue) ORTHANC_OVERRIDE; + virtual bool GetUserProfileFromUserId(unsigned int& validity /* out */, + UserProfile& profile /* out */, + const std::string& userId) ORTHANC_OVERRIDE; + virtual bool HasUserPermissionInternal(const std::string& permission, const UserProfile& profile) ORTHANC_OVERRIDE;
--- a/Plugin/DefaultConfiguration.json Tue Jul 22 17:25:07 2025 +0200 +++ b/Plugin/DefaultConfiguration.json Thu Jul 24 17:59:45 2025 +0200 @@ -131,7 +131,7 @@ ["get", "^/auth/settings/permissions$", "admin-permissions"], // audit-logs - ["get", "^/plugins/postgresql/audit-logs$", "admin-permissions|audit-logs"] + ["get", "^/auth/audit-logs$", "admin-permissions|audit-logs"] ] } } \ No newline at end of file
--- a/Plugin/IAuthorizationService.h Tue Jul 22 17:25:07 2025 +0200 +++ b/Plugin/IAuthorizationService.h Thu Jul 24 17:59:45 2025 +0200 @@ -91,6 +91,10 @@ const Token& token, const std::string& tokenValue) = 0; + virtual bool GetUserProfileFromUserId(unsigned int& validity /* out */, + UserProfile& profile /* out */, + const std::string& userId) = 0; + virtual bool GetAnonymousUserProfile(unsigned int& validity /* out */, UserProfile& profile /* out */) = 0;
--- a/Plugin/Plugin.cpp Tue Jul 22 17:25:07 2025 +0200 +++ b/Plugin/Plugin.cpp Thu Jul 24 17:59:45 2025 +0200 @@ -746,6 +746,21 @@ } } +bool GetUserNameFromUserId(std::string& userName, + const std::string& userId) +{ + unsigned int validity; // not used + OrthancPlugins::IAuthorizationService::UserProfile profile; + + if (authorizationService_->GetUserProfileFromUserId(validity, profile, userId)) + { + userName = profile.name; + return true; + } + + return false; +} + bool GetUserProfileInternal_(OrthancPlugins::IAuthorizationService::UserProfile& profile, const OrthancPlugins::AssociativeArray& headers, const OrthancPlugins::AssociativeArray& getArguments, @@ -1346,6 +1361,60 @@ } +void GetAuditLogs(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + + OrthancPlugins::RestApiClient coreApi("/plugins/postgresql/audit-logs", request); + coreApi.SetAfterPlugins(true); + + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context, output, "GET"); + return; + } + + Json::Value response; + + if (coreApi.Execute() && coreApi.GetAnswerJson(response)) + { + // transform the response: replace user-id by user-name + for (Json::ArrayIndex i = 0; i < response.size(); ++i) + { + const std::string& userId = response[i]["UserId"].asString(); + std::string userName; + if (GetUserNameFromUserId(userName, userId)) + { + response[i]["UserName"] = userName; + } + else + { + response[i]["UserName"] = userId; + } + } + + OrthancPlugins::AnswerJson(response, output); + } + + // else + // { + // OrthancPlugins::IAuthorizationService::UserProfile profile; + // std::string userId; + + // if (GetUserProfileInternal(profile, request) && !profile.userId.empty()) + // { + // userId = profile.userId; + // } + // else + // { + // throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess, "Auth plugin: no user profile or UserData found, unable to delete/put label with audit logs enabled."); + // } + +} + + OrthancPluginResourceType IdentifyResourceType(const std::string& resourceId) { Json::Value v; @@ -2302,6 +2371,7 @@ OrthancPlugins::RegisterRestCallback<BulkDeleteWithAuditLogs>("/tools/bulk-delete", true); OrthancPlugins::RegisterRestCallback<BulkModifyAnonymizeWithAuditLogs>("/tools/bulk-modify", true); OrthancPlugins::RegisterRestCallback<BulkModifyAnonymizeWithAuditLogs>("/tools/bulk-anonymize", true); + OrthancPlugins::RegisterRestCallback<GetAuditLogs>("/auth/audit-logs", true); // Note: other "actions" that do not modify the data like download-archive are logged in the HTTP filter (see RecordResourceAccess())