From: Gunnar Beutner Date: Tue, 28 Jul 2015 11:57:59 +0000 (+0200) Subject: Implement support for filters X-Git-Tag: v2.4.0~475 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=890694e62964a5d23233b2c190a41569e85c0531;p=icinga2 Implement support for filters fixes #9077 --- diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt index 6d3827f02..f5aed5aa9 100644 --- a/lib/remote/CMakeLists.txt +++ b/lib/remote/CMakeLists.txt @@ -24,10 +24,10 @@ set(remote_SOURCES apifunction.cpp apilistener.cpp apilistener.thpp apilistener-sync.cpp apiuser.cpp apiuser.thpp authority.cpp base64.cpp configfileshandler.cpp configmoduleshandler.cpp configmoduleutility.cpp configstageshandler.cpp - endpoint.cpp endpoint.thpp - httpchunkedencoding.cpp httpconnection.cpp httpdemohandler.cpp httphandler.cpp httprequest.cpp httpresponse.cpp + endpoint.cpp endpoint.thpp filterutility.cpp + httpchunkedencoding.cpp httpconnection.cpp httphandler.cpp httprequest.cpp httpresponse.cpp httputility.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp - messageorigin.cpp zone.cpp zone.thpp + messageorigin.cpp statusqueryhandler.cpp zone.cpp zone.thpp url.cpp ) diff --git a/lib/remote/configfileshandler.cpp b/lib/remote/configfileshandler.cpp index a98ec276e..5d11cd090 100644 --- a/lib/remote/configfileshandler.cpp +++ b/lib/remote/configfileshandler.cpp @@ -28,16 +28,13 @@ using namespace icinga; REGISTER_URLHANDLER("/v1/config/files", ConfigFilesHandler); -void ConfigFilesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) +bool ConfigFilesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) { if (request.RequestMethod == "GET") HandleGet(user, request, response); else response.SetStatus(400, "Bad request"); -} -bool ConfigFilesHandler::CanAlsoHandleUrl(const Url::Ptr& url) const -{ return true; } diff --git a/lib/remote/configfileshandler.hpp b/lib/remote/configfileshandler.hpp index 4a9066439..9471bbb0d 100644 --- a/lib/remote/configfileshandler.hpp +++ b/lib/remote/configfileshandler.hpp @@ -30,8 +30,7 @@ class I2_REMOTE_API ConfigFilesHandler : public HttpHandler public: DECLARE_PTR_TYPEDEFS(ConfigFilesHandler); - virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const; - virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); + virtual bool HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); private: void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); diff --git a/lib/remote/configmoduleshandler.cpp b/lib/remote/configmoduleshandler.cpp index 736d97964..738f84f85 100644 --- a/lib/remote/configmoduleshandler.cpp +++ b/lib/remote/configmoduleshandler.cpp @@ -26,8 +26,11 @@ using namespace icinga; REGISTER_URLHANDLER("/v1/config/modules", ConfigModulesHandler); -void ConfigModulesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) +bool ConfigModulesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) { + if (request.RequestUrl->GetPath().size() > 4) + return false; + if (request.RequestMethod == "GET") HandleGet(user, request, response); else if (request.RequestMethod == "POST") @@ -36,14 +39,8 @@ void ConfigModulesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& HandleDelete(user, request, response); else response.SetStatus(400, "Bad request"); -} -bool ConfigModulesHandler::CanAlsoHandleUrl(const Url::Ptr& url) const -{ - if (url->GetPath().size() > 4) - return false; - else - return true; + return true; } void ConfigModulesHandler::HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) diff --git a/lib/remote/configmoduleshandler.hpp b/lib/remote/configmoduleshandler.hpp index 8e670b624..048305490 100644 --- a/lib/remote/configmoduleshandler.hpp +++ b/lib/remote/configmoduleshandler.hpp @@ -30,8 +30,7 @@ class I2_REMOTE_API ConfigModulesHandler : public HttpHandler public: DECLARE_PTR_TYPEDEFS(ConfigModulesHandler); - virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const; - virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); + virtual bool HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); private: void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp index 24b12ed69..ca7d26039 100644 --- a/lib/remote/configstageshandler.cpp +++ b/lib/remote/configstageshandler.cpp @@ -28,8 +28,11 @@ using namespace icinga; REGISTER_URLHANDLER("/v1/config/stages", ConfigStagesHandler); -void ConfigStagesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) +bool ConfigStagesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) { + if (request.RequestUrl->GetPath().size() > 5) + return false; + if (request.RequestMethod == "GET") HandleGet(user, request, response); else if (request.RequestMethod == "POST") @@ -38,14 +41,8 @@ void ConfigStagesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& r HandleDelete(user, request, response); else response.SetStatus(400, "Bad request"); -} -bool ConfigStagesHandler::CanAlsoHandleUrl(const Url::Ptr& url) const -{ - if (url->GetPath().size() > 5) - return false; - else - return true; + return true; } void ConfigStagesHandler::HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) diff --git a/lib/remote/configstageshandler.hpp b/lib/remote/configstageshandler.hpp index ccdb3b7b8..044bd0785 100644 --- a/lib/remote/configstageshandler.hpp +++ b/lib/remote/configstageshandler.hpp @@ -30,8 +30,7 @@ class I2_REMOTE_API ConfigStagesHandler : public HttpHandler public: DECLARE_PTR_TYPEDEFS(ConfigStagesHandler); - virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const; - virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); + virtual bool HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); private: void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); diff --git a/lib/remote/filterutility.cpp b/lib/remote/filterutility.cpp new file mode 100644 index 000000000..5da79384b --- /dev/null +++ b/lib/remote/filterutility.cpp @@ -0,0 +1,139 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "remote/filterutility.hpp" +#include "config/configcompiler.hpp" +#include "config/expression.hpp" +#include "base/json.hpp" +#include "base/dynamictype.hpp" +#include +#include + +using namespace icinga; + +Type::Ptr FilterUtility::TypeFromPluralName(const String& pluralName) +{ + String uname = pluralName; + boost::algorithm::to_lower(uname); + + BOOST_FOREACH(const DynamicType::Ptr& dtype, DynamicType::GetTypes()) { + Type::Ptr type = Type::GetByName(dtype->GetName()); + ASSERT(type); + + String pname = GetPluralName(type); + boost::algorithm::to_lower(pname); + + if (uname == pname) + return type; + } + + return Type::Ptr(); +} + +String FilterUtility::GetPluralName(const Type::Ptr& type) +{ + String name = type->GetName(); + + if (name[name.GetLength() - 1] == 'y') + return name.SubStr(0, name.GetLength() - 1) + "ies"; + else + return name + "s"; +} + +DynamicObject::Ptr FilterUtility::GetObjectByTypeAndName(const String& type, const String& name) +{ + DynamicType::Ptr dtype = DynamicType::GetByName(type); + ASSERT(dtype); + + return dtype->GetObject(name); +} + +std::vector FilterUtility::GetFilterTargets(const QueryDescription& qd, const Dictionary::Ptr& query) +{ + std::vector result; + + BOOST_FOREACH(const Type::Ptr& type, qd.Types) { + String attr = type->GetName(); + boost::algorithm::to_lower(attr); + + if (query->Contains(attr)) { + String name = query->Get(attr); + DynamicObject::Ptr obj = GetObjectByTypeAndName(type->GetName(), name); + if (!obj) + BOOST_THROW_EXCEPTION(std::invalid_argument("Object does not exist.")); + result.push_back(obj); + } + + attr = GetPluralName(type); + boost::algorithm::to_lower(attr); + + if (query->Contains(attr)) { + Array::Ptr names = query->Get(attr); + ObjectLock olock(names); + BOOST_FOREACH(const String& name, names) { + DynamicObject::Ptr obj = GetObjectByTypeAndName(type->GetName(), name); + if (!obj) + BOOST_THROW_EXCEPTION(std::invalid_argument("Object does not exist.")); + result.push_back(obj); + } + } + } + + if (query->Contains("filter")) { + if (!query->Contains("type")) + BOOST_THROW_EXCEPTION(std::invalid_argument("Type must be specified when using a filter.")); + + String filter = query->Get("filter"); + String type = query->Get("type"); + + Type::Ptr utype = Type::GetByName(type); + + if (!utype) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid type specified.")); + + if (qd.Types.find(utype) == qd.Types.end()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid type specified for this query.")); + + DynamicType::Ptr dtype = DynamicType::GetByName(type); + ASSERT(dtype); + + Expression *ufilter = ConfigCompiler::CompileText("", filter, false); + ScriptFrame frame; + frame.Sandboxed = true; + + String varName = utype->GetName(); + boost::algorithm::to_lower(varName); + + try { + BOOST_FOREACH(const DynamicObject::Ptr& object, dtype->GetObjects()) { + frame.Locals->Set(varName, object); + + if (Convert::ToBool(ufilter->Evaluate(frame))) + result.push_back(object); + } + } catch (const std::exception& ex) { + delete ufilter; + throw; + } + + delete ufilter; + } + + return result; +} diff --git a/lib/remote/httpdemohandler.cpp b/lib/remote/filterutility.hpp similarity index 61% rename from lib/remote/httpdemohandler.cpp rename to lib/remote/filterutility.hpp index 86e37d2fe..b7c28821d 100644 --- a/lib/remote/httpdemohandler.cpp +++ b/lib/remote/filterutility.hpp @@ -17,30 +17,36 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ -#include "remote/httpdemohandler.hpp" +#ifndef FILTERUTILITY_H +#define FILTERUTILITY_H -using namespace icinga; +#include "remote/i2-remote.hpp" +#include "base/dictionary.hpp" +#include "base/dynamicobject.hpp" +#include -REGISTER_URLHANDLER("/demo", HttpDemoHandler); +namespace icinga +{ + +struct QueryDescription +{ + std::set Types; +}; -void HttpDemoHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) +/** + * Filter utilities. + * + * @ingroup remote + */ +class I2_REMOTE_API FilterUtility { - if (request.RequestMethod == "GET") { - String form = "

Hallo " + user->GetName() + "

"; - response.SetStatus(200, "OK"); - response.AddHeader("Content-Type", "text/html"); - response.WriteBody(form.CStr(), form.GetLength()); - } else if (request.RequestMethod == "POST") { - response.SetStatus(200, "OK"); - String msg = "You sent: "; +public: + static String GetPluralName(const Type::Ptr& type); + static Type::Ptr TypeFromPluralName(const String& pluralName); + static DynamicObject::Ptr GetObjectByTypeAndName(const String& type, const String& name); + static std::vector GetFilterTargets(const QueryDescription& qd, const Dictionary::Ptr& query); +}; - char buffer[512]; - size_t count; - while ((count = request.ReadBody(buffer, sizeof(buffer))) > 0) - msg += String(buffer, buffer + count); - response.WriteBody(msg.CStr(), msg.GetLength()); - } else { - response.SetStatus(400, "Bad request"); - } } +#endif /* FILTERUTILITY_H */ diff --git a/lib/remote/httpconnection.cpp b/lib/remote/httpconnection.cpp index b1056c3e1..7c29c10f3 100644 --- a/lib/remote/httpconnection.cpp +++ b/lib/remote/httpconnection.cpp @@ -90,7 +90,7 @@ bool HttpConnection::ProcessMessage(void) } catch (const std::exception& ex) { HttpResponse response(m_Stream, m_CurrentRequest); response.SetStatus(400, "Bad request"); - String msg = "

Bad request

"; + String msg = "

Bad request

" + DiagnosticInformation(ex) + "

"; response.WriteBody(msg.CStr(), msg.GetLength()); response.Finish(); diff --git a/lib/remote/httphandler.cpp b/lib/remote/httphandler.cpp index 9716805d7..8e42df13c 100644 --- a/lib/remote/httphandler.cpp +++ b/lib/remote/httphandler.cpp @@ -48,48 +48,54 @@ void HttpHandler::Register(const Url::Ptr& url, const HttpHandler::Ptr& handler) node = sub_node; } - node->Set("handler", handler); -} + Array::Ptr handlers = node->Get("handlers"); -bool HttpHandler::CanAlsoHandleUrl(const Url::Ptr& url) const -{ - return false; + if (!handlers) { + handlers = new Array(); + node->Set("handlers", handlers); + } + + handlers->Add(handler); } void HttpHandler::ProcessRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) { Dictionary::Ptr node = m_UrlTree; - HttpHandler::Ptr current_handler, handler; - bool exact_match = true; + std::vector handlers; BOOST_FOREACH(const String& elem, request.RequestUrl->GetPath()) { - current_handler = node->Get("handler"); - if (current_handler) - handler = current_handler; + Array::Ptr current_handlers = node->Get("handlers"); + + if (current_handlers) { + ObjectLock olock(current_handlers); + BOOST_FOREACH(const HttpHandler::Ptr current_handler, current_handlers) { + handlers.push_back(current_handler); + } + } Dictionary::Ptr children = node->Get("children"); if (!children) { - exact_match = false; node.reset(); break; } node = children->Get(elem); - if (!node) { - exact_match = false; + if (!node) break; - } } - if (node) { - current_handler = node->Get("handler"); - if (current_handler) - handler = current_handler; - } + std::reverse(handlers.begin(), handlers.end()); - if (!handler || (!exact_match && !handler->CanAlsoHandleUrl(request.RequestUrl))) { + bool processed = false; + BOOST_FOREACH(const HttpHandler::Ptr& handler, handlers) { + if (handler->HandleRequest(user, request, response)) { + processed = true; + break; + } + } + if (!processed) { response.SetStatus(404, "Not found"); response.AddHeader("Content-Type", "text/html"); String msg = "

Not found

"; @@ -97,6 +103,4 @@ void HttpHandler::ProcessRequest(const ApiUser::Ptr& user, HttpRequest& request, response.Finish(); return; } - - handler->HandleRequest(user, request, response); } diff --git a/lib/remote/httphandler.hpp b/lib/remote/httphandler.hpp index b689a95f5..afeeff29a 100644 --- a/lib/remote/httphandler.hpp +++ b/lib/remote/httphandler.hpp @@ -40,8 +40,7 @@ class I2_REMOTE_API HttpHandler : public Object public: DECLARE_PTR_TYPEDEFS(HttpHandler); - virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const; - virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) = 0; + virtual bool HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) = 0; static void Register(const Url::Ptr& url, const HttpHandler::Ptr& handler); static void ProcessRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); diff --git a/lib/remote/statusqueryhandler.cpp b/lib/remote/statusqueryhandler.cpp new file mode 100644 index 000000000..9df8f704e --- /dev/null +++ b/lib/remote/statusqueryhandler.cpp @@ -0,0 +1,68 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "remote/statusqueryhandler.hpp" +#include "remote/httputility.hpp" +#include "remote/filterutility.hpp" +#include "base/serializer.hpp" +#include + +using namespace icinga; + +REGISTER_URLHANDLER("/", StatusQueryHandler); + +bool StatusQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) +{ + if (request.RequestUrl->GetPath().empty()) + return false; + + Type::Ptr type = FilterUtility::TypeFromPluralName(request.RequestUrl->GetPath()[0]); + + if (!type) + return false; + + QueryDescription qd; + qd.Types.insert(type); + + Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request); + + if (request.RequestUrl->GetPath().size() > 1) { + String attr = type->GetName(); + boost::algorithm::to_lower(attr); + params->Set(attr, request.RequestUrl->GetPath()[1]); + } + + std::vector objs = FilterUtility::GetFilterTargets(qd, params); + + Array::Ptr results = new Array(); + + BOOST_FOREACH(const DynamicObject::Ptr& obj, objs) { + Value result1 = Serialize(obj, FAConfig | FAState); + results->Add(result1); + } + + Dictionary::Ptr result = new Dictionary(); + result->Set("results", results); + + response.SetStatus(200, "OK"); + HttpUtility::SendJsonBody(response, result); + + return true; +} + diff --git a/lib/remote/httpdemohandler.hpp b/lib/remote/statusqueryhandler.hpp similarity index 85% rename from lib/remote/httpdemohandler.hpp rename to lib/remote/statusqueryhandler.hpp index dbece031d..64cf2bd0c 100644 --- a/lib/remote/httpdemohandler.hpp +++ b/lib/remote/statusqueryhandler.hpp @@ -17,22 +17,22 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ -#ifndef HTTPDEMOHANDLER_H -#define HTTPDEMOHANDLER_H +#ifndef STATUSQUERYHANDLER_H +#define STATUSQUERYHANDLER_H #include "remote/httphandler.hpp" namespace icinga { -class I2_REMOTE_API HttpDemoHandler : public HttpHandler +class I2_REMOTE_API StatusQueryHandler : public HttpHandler { public: - DECLARE_PTR_TYPEDEFS(HttpDemoHandler); + DECLARE_PTR_TYPEDEFS(StatusQueryHandler); - virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); + virtual bool HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); }; } -#endif /* HTTPDEMOHANDLER_H */ +#endif /* STATUSQUERYHANDLER_H */