diff --git a/lib/remote/filterutility.cpp b/lib/remote/filterutility.cpp index 3bc1578e7..9bcc60f71 100644 --- a/lib/remote/filterutility.cpp +++ b/lib/remote/filterutility.cpp @@ -124,13 +124,27 @@ static void FilteredAddTarget(ScriptFrame& permissionFrame, Expression *permissi } } -void FilterUtility::CheckPermission(const ApiUser::Ptr& user, const String& permission, Expression **permissionFilter) +/** + * Checks whether the given API user is granted the given permission + * + * When you desire an exception to be raised when the given user doesn't have the given permission, + * you need to use FilterUtility::CheckPermission(). + * + * @param user ApiUser pointer to the user object you want to check the permission of + * @param permission The actual permission you want to check the user permission against + * @param permissionFilter Expression pointer that is used as an output buffer for all the filter expressions of the + * individual permissions of the given user to be evaluated. It's up to the caller to delete + * this pointer when it's not needed any more. + * + * @return bool + */ +bool FilterUtility::HasPermission(const ApiUser::Ptr& user, const String& permission, Expression **permissionFilter) { if (permissionFilter) *permissionFilter = nullptr; if (permission.IsEmpty()) - return; + return true; bool foundPermission = false; String requiredPermission = permission.ToLower(); @@ -172,8 +186,15 @@ void FilterUtility::CheckPermission(const ApiUser::Ptr& user, const String& perm if (!foundPermission) { Log(LogWarning, "FilterUtility") << "Missing permission: " << requiredPermission; + } - BOOST_THROW_EXCEPTION(ScriptError("Missing permission: " + requiredPermission)); + return foundPermission; +} + +void FilterUtility::CheckPermission(const ApiUser::Ptr& user, const String& permission, Expression **permissionFilter) +{ + if (!HasPermission(user, permission, permissionFilter)) { + BOOST_THROW_EXCEPTION(ScriptError("Missing permission: " + permission.ToLower())); } } diff --git a/lib/remote/filterutility.hpp b/lib/remote/filterutility.hpp index 70e5b7e6f..1cebffc0b 100644 --- a/lib/remote/filterutility.hpp +++ b/lib/remote/filterutility.hpp @@ -52,6 +52,7 @@ class FilterUtility public: static Type::Ptr TypeFromPluralName(const String& pluralName); static void CheckPermission(const ApiUser::Ptr& user, const String& permission, Expression **filter = nullptr); + static bool HasPermission(const ApiUser::Ptr& user, const String& permission, Expression **permissionFilter = nullptr); static std::vector GetFilterTargets(const QueryDescription& qd, const Dictionary::Ptr& query, const ApiUser::Ptr& user, const String& variableName = String()); static bool EvaluateFilter(ScriptFrame& frame, Expression *filter, diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp index 3f827037c..f059c0328 100644 --- a/lib/remote/objectqueryhandler.cpp +++ b/lib/remote/objectqueryhandler.cpp @@ -8,6 +8,7 @@ #include "base/configtype.hpp" #include #include +#include using namespace icinga; @@ -189,6 +190,9 @@ bool ObjectQueryHandler::HandleRequest( joinAttrs.insert(field.Name); } + std::unordered_map> typePermissions; + std::unordered_map objectAccessAllowed; + for (const ConfigObject::Ptr& obj : objs) { DictionaryData result1{ { "name", obj->GetName() }, @@ -257,6 +261,51 @@ bool ObjectQueryHandler::HandleRequest( if (!joinedObj) continue; + Type::Ptr reflectionType = joinedObj->GetReflectionType(); + Expression::Ptr permissionFilter; + + auto it = typePermissions.find(reflectionType.get()); + bool granted; + + if (it == typePermissions.end()) { + String permission = "objects/query/" + reflectionType->GetName(); + + Expression *filter = nullptr; + granted = FilterUtility::HasPermission(user, permission, &filter); + permissionFilter = filter; + + typePermissions.insert({reflectionType.get(), std::make_pair(granted, permissionFilter)}); + } else { + std::tie(granted, permissionFilter) = it->second; + } + + if (!granted) { + // Not authorized + continue; + } + + auto relation = objectAccessAllowed.find(joinedObj.get()); + bool accessAllowed; + + if (relation == objectAccessAllowed.end()) { + ScriptFrame permissionFrame(false, new Namespace()); + + try { + accessAllowed = FilterUtility::EvaluateFilter(permissionFrame, permissionFilter.get(), joinedObj); + } catch (const ScriptError& err) { + accessAllowed = false; + } + + objectAccessAllowed.insert({joinedObj.get(), accessAllowed}); + } else { + accessAllowed = relation->second; + } + + if (!accessAllowed) { + // Access denied + continue; + } + String prefix = field.NavigationName; try {