diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:32:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:32:39 +0000 |
commit | 56ae875861ab260b80a030f50c4aff9f9dc8fff0 (patch) | |
tree | 531412110fc901a5918c7f7442202804a83cada9 /lib/remote/filterutility.cpp | |
parent | Initial commit. (diff) | |
download | icinga2-e6c8b97d844e301093c7e2c03da489629676e2c4.tar.xz icinga2-e6c8b97d844e301093c7e2c03da489629676e2c4.zip |
Adding upstream version 2.14.2.upstream/2.14.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/remote/filterutility.cpp')
-rw-r--r-- | lib/remote/filterutility.cpp | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/lib/remote/filterutility.cpp b/lib/remote/filterutility.cpp new file mode 100644 index 0000000..468b91e --- /dev/null +++ b/lib/remote/filterutility.cpp @@ -0,0 +1,354 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "remote/filterutility.hpp" +#include "remote/httputility.hpp" +#include "config/applyrule.hpp" +#include "config/configcompiler.hpp" +#include "config/expression.hpp" +#include "base/namespace.hpp" +#include "base/json.hpp" +#include "base/configtype.hpp" +#include "base/logger.hpp" +#include "base/utility.hpp" +#include <boost/algorithm/string/case_conv.hpp> +#include <memory> + +using namespace icinga; + +Type::Ptr FilterUtility::TypeFromPluralName(const String& pluralName) +{ + String uname = pluralName; + boost::algorithm::to_lower(uname); + + for (const Type::Ptr& type : Type::GetAllTypes()) { + String pname = type->GetPluralName(); + boost::algorithm::to_lower(pname); + + if (uname == pname) + return type; + } + + return nullptr; +} + +void ConfigObjectTargetProvider::FindTargets(const String& type, const std::function<void (const Value&)>& addTarget) const +{ + Type::Ptr ptype = Type::GetByName(type); + auto *ctype = dynamic_cast<ConfigType *>(ptype.get()); + + if (ctype) { + for (const ConfigObject::Ptr& object : ctype->GetObjects()) { + addTarget(object); + } + } +} + +Value ConfigObjectTargetProvider::GetTargetByName(const String& type, const String& name) const +{ + ConfigObject::Ptr obj = ConfigObject::GetObject(type, name); + + if (!obj) + BOOST_THROW_EXCEPTION(std::invalid_argument("Object does not exist.")); + + return obj; +} + +bool ConfigObjectTargetProvider::IsValidType(const String& type) const +{ + Type::Ptr ptype = Type::GetByName(type); + + if (!ptype) + return false; + + return ConfigObject::TypeInstance->IsAssignableFrom(ptype); +} + +String ConfigObjectTargetProvider::GetPluralName(const String& type) const +{ + return Type::GetByName(type)->GetPluralName(); +} + +bool FilterUtility::EvaluateFilter(ScriptFrame& frame, Expression *filter, + const Object::Ptr& target, const String& variableName) +{ + if (!filter) + return true; + + Type::Ptr type = target->GetReflectionType(); + String varName; + + if (variableName.IsEmpty()) + varName = type->GetName().ToLower(); + else + varName = variableName; + + Namespace::Ptr frameNS; + + if (frame.Self.IsEmpty()) { + frameNS = new Namespace(); + frame.Self = frameNS; + } else { + /* Enforce a namespace object for 'frame.self'. */ + ASSERT(frame.Self.IsObjectType<Namespace>()); + + frameNS = frame.Self; + + ASSERT(frameNS != ScriptGlobal::GetGlobals()); + } + + frameNS->Set("obj", target); + frameNS->Set(varName, target); + + for (int fid = 0; fid < type->GetFieldCount(); fid++) { + Field field = type->GetFieldInfo(fid); + + if ((field.Attributes & FANavigation) == 0) + continue; + + Object::Ptr joinedObj = target->NavigateField(fid); + + if (field.NavigationName) + frameNS->Set(field.NavigationName, joinedObj); + else + frameNS->Set(field.Name, joinedObj); + } + + return Convert::ToBool(filter->Evaluate(frame)); +} + +static void FilteredAddTarget(ScriptFrame& permissionFrame, Expression *permissionFilter, + ScriptFrame& frame, Expression *ufilter, std::vector<Value>& result, const String& variableName, const Object::Ptr& target) +{ + if (FilterUtility::EvaluateFilter(permissionFrame, permissionFilter, target, variableName)) { + if (FilterUtility::EvaluateFilter(frame, ufilter, target, variableName)) { + result.emplace_back(std::move(target)); + } + } +} + +/** + * 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, std::unique_ptr<Expression>* permissionFilter) +{ + if (permissionFilter) + *permissionFilter = nullptr; + + if (permission.IsEmpty()) + return true; + + bool foundPermission = false; + String requiredPermission = permission.ToLower(); + + Array::Ptr permissions = user->GetPermissions(); + if (permissions) { + ObjectLock olock(permissions); + for (const Value& item : permissions) { + String permission; + Function::Ptr filter; + if (item.IsObjectType<Dictionary>()) { + Dictionary::Ptr dict = item; + permission = dict->Get("permission"); + filter = dict->Get("filter"); + } else + permission = item; + + permission = permission.ToLower(); + + if (!Utility::Match(permission, requiredPermission)) + continue; + + foundPermission = true; + + if (filter && permissionFilter) { + std::vector<std::unique_ptr<Expression> > args; + args.emplace_back(new GetScopeExpression(ScopeThis)); + std::unique_ptr<Expression> indexer{new IndexerExpression(std::unique_ptr<Expression>(MakeLiteral(filter)), std::unique_ptr<Expression>(MakeLiteral("call")))}; + FunctionCallExpression *fexpr = new FunctionCallExpression(std::move(indexer), std::move(args)); + + if (!*permissionFilter) + permissionFilter->reset(fexpr); + else + *permissionFilter = std::make_unique<LogicalOrExpression>(std::move(*permissionFilter), std::unique_ptr<Expression>(fexpr)); + } + } + } + + if (!foundPermission) { + Log(LogWarning, "FilterUtility") + << "Missing permission: " << requiredPermission; + } + + return foundPermission; +} + +void FilterUtility::CheckPermission(const ApiUser::Ptr& user, const String& permission, std::unique_ptr<Expression>* permissionFilter) +{ + if (!HasPermission(user, permission, permissionFilter)) { + BOOST_THROW_EXCEPTION(ScriptError("Missing permission: " + permission.ToLower())); + } +} + +std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, const Dictionary::Ptr& query, const ApiUser::Ptr& user, const String& variableName) +{ + std::vector<Value> result; + + TargetProvider::Ptr provider; + + if (qd.Provider) + provider = qd.Provider; + else + provider = new ConfigObjectTargetProvider(); + + std::unique_ptr<Expression> permissionFilter; + CheckPermission(user, qd.Permission, &permissionFilter); + + Namespace::Ptr permissionFrameNS = new Namespace(); + ScriptFrame permissionFrame(false, permissionFrameNS); + + for (const String& type : qd.Types) { + String attr = type; + boost::algorithm::to_lower(attr); + + if (attr == "type") + attr = "name"; + + if (query && query->Contains(attr)) { + String name = HttpUtility::GetLastParameter(query, attr); + Object::Ptr target = provider->GetTargetByName(type, name); + + if (!FilterUtility::EvaluateFilter(permissionFrame, permissionFilter.get(), target, variableName)) + BOOST_THROW_EXCEPTION(ScriptError("Access denied to object '" + name + "' of type '" + type + "'")); + + result.emplace_back(std::move(target)); + } + + attr = provider->GetPluralName(type); + boost::algorithm::to_lower(attr); + + if (query && query->Contains(attr)) { + Array::Ptr names = query->Get(attr); + if (names) { + ObjectLock olock(names); + for (const String& name : names) { + Object::Ptr target = provider->GetTargetByName(type, name); + + if (!FilterUtility::EvaluateFilter(permissionFrame, permissionFilter.get(), target, variableName)) + BOOST_THROW_EXCEPTION(ScriptError("Access denied to object '" + name + "' of type '" + type + "'")); + + result.emplace_back(std::move(target)); + } + } + } + } + + if ((query && query->Contains("filter")) || result.empty()) { + if (!query->Contains("type")) + BOOST_THROW_EXCEPTION(std::invalid_argument("Type must be specified when using a filter.")); + + String type = HttpUtility::GetLastParameter(query, "type"); + + if (!provider->IsValidType(type)) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid type specified.")); + + if (qd.Types.find(type) == qd.Types.end()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid type specified for this query.")); + + Namespace::Ptr frameNS = new Namespace(); + ScriptFrame frame(false, frameNS); + frame.Sandboxed = true; + + if (query->Contains("filter")) { + String filter = HttpUtility::GetLastParameter(query, "filter"); + std::unique_ptr<Expression> ufilter = ConfigCompiler::CompileText("<API query>", filter); + Dictionary::Ptr filter_vars = query->Get("filter_vars"); + bool targeted = false; + std::vector<ConfigObject::Ptr> targets; + + if (dynamic_cast<ConfigObjectTargetProvider*>(provider.get())) { + auto dict (dynamic_cast<DictExpression*>(ufilter.get())); + + if (dict) { + auto& subex (dict->GetExpressions()); + + if (subex.size() == 1u) { + if (type == "Host") { + std::vector<const String *> targetNames; + + if (ApplyRule::GetTargetHosts(subex.at(0).get(), targetNames, filter_vars)) { + static const auto typeHost (Type::GetByName("Host")); + static const auto ctypeHost (dynamic_cast<ConfigType*>(typeHost.get())); + targeted = true; + + for (auto name : targetNames) { + auto target (ctypeHost->GetObject(*name)); + + if (target) { + targets.emplace_back(target); + } + } + } + } else if (type == "Service") { + std::vector<std::pair<const String *, const String *>> targetNames; + + if (ApplyRule::GetTargetServices(subex.at(0).get(), targetNames, filter_vars)) { + static const auto typeService (Type::GetByName("Service")); + static const auto ctypeService (dynamic_cast<ConfigType*>(typeService.get())); + targeted = true; + + for (auto name : targetNames) { + auto target (ctypeService->GetObject(*name.first + "!" + *name.second)); + + if (target) { + targets.emplace_back(target); + } + } + } + } + } + } + } + + if (targeted) { + for (auto& target : targets) { + if (FilterUtility::EvaluateFilter(permissionFrame, permissionFilter.get(), target, variableName)) { + result.emplace_back(std::move(target)); + } + } + } else { + if (filter_vars) { + ObjectLock olock (filter_vars); + + for (auto& kv : filter_vars) { + frameNS->Set(kv.first, kv.second); + } + } + + provider->FindTargets(type, [&permissionFrame, &permissionFilter, &frame, &ufilter, &result, variableName](const Object::Ptr& target) { + FilteredAddTarget(permissionFrame, permissionFilter.get(), frame, &*ufilter, result, variableName, target); + }); + } + } else { + /* Ensure to pass a nullptr as filter expression. + * GCC 8.1.1 on F28 causes problems, see GH #6533. + */ + provider->FindTargets(type, [&permissionFrame, &permissionFilter, &frame, &result, variableName](const Object::Ptr& target) { + FilteredAddTarget(permissionFrame, permissionFilter.get(), frame, nullptr, result, variableName, target); + }); + } + } + + return result; +} + |