From e87c41d322bbe8f8fa9930715c4a5c25138f3576 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 14 Dec 2020 13:56:43 +0100 Subject: [PATCH] PUT /v1/objects/...: respect permission filter ... by applying it to a virtual new config object. --- lib/config/configitem.cpp | 61 ++++++++++------- lib/config/configitem.hpp | 6 +- lib/remote/createobjecthandler.cpp | 106 ++++++++++++++++++++++++++++- 3 files changed, 144 insertions(+), 29 deletions(-) diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp index b154683cd..469a89c94 100644 --- a/lib/config/configitem.cpp +++ b/lib/config/configitem.cpp @@ -30,6 +30,7 @@ using namespace icinga; +thread_local std::function ConfigItem::m_OverrideRegistry; std::mutex ConfigItem::m_Mutex; ConfigItem::TypeMap ConfigItem::m_Items; ConfigItem::TypeMap ConfigItem::m_DefaultTemplates; @@ -260,41 +261,39 @@ ConfigObject::Ptr ConfigItem::Commit(bool discard) throw; } - try { - dobj->OnConfigLoaded(); - } catch (const std::exception& ex) { - if (m_IgnoreOnError) { - Log(LogNotice, "ConfigObject") - << "Ignoring config object '" << m_Name << "' of type '" << m_Type->GetName() << "' due to errors: " << DiagnosticInformation(ex); + if (!m_OverrideRegistry) { + try { + dobj->OnConfigLoaded(); + } catch (const std::exception& ex) { + if (m_IgnoreOnError) { + Log(LogNotice, "ConfigObject") + << "Ignoring config object '" << m_Name << "' of type '" << m_Type->GetName() << "' due to errors: " << DiagnosticInformation(ex); - { - std::unique_lock lock(m_Mutex); - m_IgnoredItems.push_back(m_DebugInfo.Path); + { + std::unique_lock lock(m_Mutex); + m_IgnoredItems.push_back(m_DebugInfo.Path); + } + + return nullptr; } - return nullptr; + throw; } - - throw; } - Value serializedObject; + if (!m_OverrideRegistry) { + Value serializedObject; - try { - if (ConfigCompilerContext::GetInstance()->IsOpen()) { + try { serializedObject = Serialize(dobj, FAConfig); - } else { - AssertNoCircularReferences(dobj); + } catch (const CircularReferenceError& ex) { + BOOST_THROW_EXCEPTION(ValidationError(dobj, ex.GetPath(), "Circular references are not allowed")); } - } catch (const CircularReferenceError& ex) { - BOOST_THROW_EXCEPTION(ValidationError(dobj, ex.GetPath(), "Circular references are not allowed")); - } - if (ConfigCompilerContext::GetInstance()->IsOpen()) { Dictionary::Ptr persistentItem = new Dictionary({ { "type", type->GetName() }, { "name", GetName() }, - { "properties", serializedObject }, + { "properties", Serialize(dobj, FAConfig) }, { "debug_hints", dhint }, { "debug_info", new Array({ m_DebugInfo.Path, @@ -305,13 +304,14 @@ ConfigObject::Ptr ConfigItem::Commit(bool discard) }) } }); + dhint.reset(); + ConfigCompilerContext::GetInstance()->WriteObject(persistentItem); + persistentItem.reset(); + + dobj->Register(); } - dhint.reset(); - - dobj->Register(); - m_Object = dobj; return dobj; @@ -322,6 +322,11 @@ ConfigObject::Ptr ConfigItem::Commit(bool discard) */ void ConfigItem::Register() { + if (m_OverrideRegistry) { + m_OverrideRegistry(this); + return; + } + m_ActivationContext = ActivationContext::GetCurrentContext(); std::unique_lock lock(m_Mutex); @@ -355,6 +360,10 @@ void ConfigItem::Register() */ void ConfigItem::Unregister() { + if (m_OverrideRegistry) { + return; + } + if (m_Object) { m_Object->Unregister(); m_Object.reset(); diff --git a/lib/config/configitem.hpp b/lib/config/configitem.hpp index 007a3c08a..a0e7f7f82 100644 --- a/lib/config/configitem.hpp +++ b/lib/config/configitem.hpp @@ -8,6 +8,7 @@ #include "config/activationcontext.hpp" #include "base/configobject.hpp" #include "base/workqueue.hpp" +#include namespace icinga { @@ -43,6 +44,7 @@ public: void Register(); void Unregister(); + ConfigObject::Ptr Commit(bool discard = true); DebugInfo GetDebugInfo() const; Dictionary::Ptr GetScope() const; @@ -63,6 +65,8 @@ public: static void RemoveIgnoredItems(const String& allowedConfigPath); + static thread_local std::function m_OverrideRegistry; + private: Type::Ptr m_Type; /**< The object type. */ String m_Name; /**< The name. */ @@ -96,8 +100,6 @@ private: static ConfigItem::Ptr GetObjectUnlocked(const String& type, const String& name); - ConfigObject::Ptr Commit(bool discard = true); - static bool CommitNewItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector& newItems); }; diff --git a/lib/remote/createobjecthandler.cpp b/lib/remote/createobjecthandler.cpp index beff9c987..3c533f0ea 100644 --- a/lib/remote/createobjecthandler.cpp +++ b/lib/remote/createobjecthandler.cpp @@ -9,6 +9,9 @@ #include "remote/apiaction.hpp" #include "remote/zone.hpp" #include "base/configtype.hpp" +#include "base/defer.hpp" +#include "config/configcompiler.hpp" +#include "config/configitem.hpp" #include using namespace icinga; @@ -41,7 +44,9 @@ bool CreateObjectHandler::HandleRequest( return true; } - FilterUtility::CheckPermission(user, "objects/create/" + type->GetName()); + auto requiredPermission ("objects/create/" + type->GetName()); + + FilterUtility::CheckPermission(user, requiredPermission); String name = url->GetPath()[3]; Array::Ptr templates = params->Get("templates"); @@ -133,6 +138,105 @@ bool CreateObjectHandler::HandleRequest( // Lock the object name of the given type to prevent from being created concurrently. ObjectNameLock objectNameLock(type, name); + { + auto permissions (user->GetPermissions()); + + if (permissions) { + std::unique_ptr filters; + + { + ObjectLock oLock (permissions); + + for (auto& item : permissions) { + Function::Ptr filter; + + if (item.IsObjectType()) { + Dictionary::Ptr dict = item; + filter = dict->Get("filter"); + + if (Utility::Match(dict->Get("permission"), requiredPermission)) { + if (!filter) { + filters.reset(); + break; + } + + std::vector> args; + args.emplace_back(new GetScopeExpression(ScopeThis)); + + std::unique_ptr indexer = (std::unique_ptr)new IndexerExpression( + std::unique_ptr(MakeLiteral(filter)), + std::unique_ptr(MakeLiteral("call")) + ); + + std::unique_ptr fexpr = (std::unique_ptr)new FunctionCallExpression( + std::move(indexer), std::move(args) + ); + + if (filters) { + filters = (std::unique_ptr)new LogicalOrExpression( + std::move(filters), std::move(fexpr) + ); + } else { + filters = std::move(fexpr); + } + } + } else if (Utility::Match(item, requiredPermission)) { + filters.reset(); + break; + } + } + } + + if (filters) { + try { + auto expr (ConfigCompiler::CompileText("", config, "", "_api")); + + ConfigItem::Ptr item; + ConfigItem::m_OverrideRegistry = [&item](ConfigItem *ci) { item = ci; }; + + Defer overrideRegistry ([]() { + ConfigItem::m_OverrideRegistry = decltype(ConfigItem::m_OverrideRegistry)(); + }); + + ActivationScope ascope; + + { + ScriptFrame frame(true); + expr->Evaluate(frame); + } + + expr.reset(); + + if (item) { + auto obj (item->Commit(false)); + + if (obj) { + ScriptFrame frame (false, new Namespace()); + + if (!FilterUtility::EvaluateFilter(frame, filters.get(), obj)) { + BOOST_THROW_EXCEPTION(ScriptError( + "Access denied to object '" + name + "' of type '" + type->GetName() + "'" + )); + } + } + } + } catch (const std::exception& ex) { + result1->Set("errors", new Array({ ex.what() })); + result1->Set("code", 500); + result1->Set("status", "Object could not be created."); + + if (verbose) + result1->Set("diagnostic_information", DiagnosticInformation(ex)); + + response.result(http::status::internal_server_error); + HttpUtility::SendJsonBody(response, params, result); + + return true; + } + } + } + } + if (!ConfigObjectUtility::CreateObject(type, name, config, errors, diagnosticInformation)) { result1->Set("errors", errors); result1->Set("code", 500);