Implement support for filters

fixes #9077
This commit is contained in:
Gunnar Beutner 2015-07-28 13:57:59 +02:00
parent 0a1dad0a8f
commit 890694e629
14 changed files with 286 additions and 82 deletions

View File

@ -24,10 +24,10 @@ set(remote_SOURCES
apifunction.cpp apilistener.cpp apilistener.thpp apilistener-sync.cpp apifunction.cpp apilistener.cpp apilistener.thpp apilistener-sync.cpp
apiuser.cpp apiuser.thpp authority.cpp base64.cpp configfileshandler.cpp apiuser.cpp apiuser.thpp authority.cpp base64.cpp configfileshandler.cpp
configmoduleshandler.cpp configmoduleutility.cpp configstageshandler.cpp configmoduleshandler.cpp configmoduleutility.cpp configstageshandler.cpp
endpoint.cpp endpoint.thpp endpoint.cpp endpoint.thpp filterutility.cpp
httpchunkedencoding.cpp httpconnection.cpp httpdemohandler.cpp httphandler.cpp httprequest.cpp httpresponse.cpp httpchunkedencoding.cpp httpconnection.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
httputility.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.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 url.cpp
) )

View File

@ -28,16 +28,13 @@ using namespace icinga;
REGISTER_URLHANDLER("/v1/config/files", ConfigFilesHandler); 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") if (request.RequestMethod == "GET")
HandleGet(user, request, response); HandleGet(user, request, response);
else else
response.SetStatus(400, "Bad request"); response.SetStatus(400, "Bad request");
}
bool ConfigFilesHandler::CanAlsoHandleUrl(const Url::Ptr& url) const
{
return true; return true;
} }

View File

@ -30,8 +30,7 @@ class I2_REMOTE_API ConfigFilesHandler : public HttpHandler
public: public:
DECLARE_PTR_TYPEDEFS(ConfigFilesHandler); DECLARE_PTR_TYPEDEFS(ConfigFilesHandler);
virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const; virtual bool HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
private: private:
void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);

View File

@ -26,8 +26,11 @@ using namespace icinga;
REGISTER_URLHANDLER("/v1/config/modules", ConfigModulesHandler); 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") if (request.RequestMethod == "GET")
HandleGet(user, request, response); HandleGet(user, request, response);
else if (request.RequestMethod == "POST") else if (request.RequestMethod == "POST")
@ -36,13 +39,7 @@ void ConfigModulesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest&
HandleDelete(user, request, response); HandleDelete(user, request, response);
else else
response.SetStatus(400, "Bad request"); 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;
} }

View File

@ -30,8 +30,7 @@ class I2_REMOTE_API ConfigModulesHandler : public HttpHandler
public: public:
DECLARE_PTR_TYPEDEFS(ConfigModulesHandler); DECLARE_PTR_TYPEDEFS(ConfigModulesHandler);
virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const; virtual bool HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
private: private:
void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);

View File

@ -28,8 +28,11 @@ using namespace icinga;
REGISTER_URLHANDLER("/v1/config/stages", ConfigStagesHandler); 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") if (request.RequestMethod == "GET")
HandleGet(user, request, response); HandleGet(user, request, response);
else if (request.RequestMethod == "POST") else if (request.RequestMethod == "POST")
@ -38,13 +41,7 @@ void ConfigStagesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& r
HandleDelete(user, request, response); HandleDelete(user, request, response);
else else
response.SetStatus(400, "Bad request"); 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;
} }

View File

@ -30,8 +30,7 @@ class I2_REMOTE_API ConfigStagesHandler : public HttpHandler
public: public:
DECLARE_PTR_TYPEDEFS(ConfigStagesHandler); DECLARE_PTR_TYPEDEFS(ConfigStagesHandler);
virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const; virtual bool HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
private: private:
void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);

View File

@ -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 <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
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<DynamicObject::Ptr> FilterUtility::GetFilterTargets(const QueryDescription& qd, const Dictionary::Ptr& query)
{
std::vector<DynamicObject::Ptr> 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("<API query>", 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;
}

View File

@ -17,30 +17,36 @@
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * 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 <set>
REGISTER_URLHANDLER("/demo", HttpDemoHandler); namespace icinga
void HttpDemoHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{ {
if (request.RequestMethod == "GET") {
String form = "<h1>Hallo " + user->GetName() + "</h1><form action=\"/demo\" method=\"post\"><input type=\"text\" name=\"msg\"><input type=\"submit\"></form>";
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: ";
char buffer[512]; struct QueryDescription
size_t count; {
while ((count = request.ReadBody(buffer, sizeof(buffer))) > 0) std::set<Type::Ptr> Types;
msg += String(buffer, buffer + count); };
response.WriteBody(msg.CStr(), msg.GetLength());
} else { /**
response.SetStatus(400, "Bad request"); * Filter utilities.
} *
* @ingroup remote
*/
class I2_REMOTE_API FilterUtility
{
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<DynamicObject::Ptr> GetFilterTargets(const QueryDescription& qd, const Dictionary::Ptr& query);
};
} }
#endif /* FILTERUTILITY_H */

View File

@ -90,7 +90,7 @@ bool HttpConnection::ProcessMessage(void)
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpResponse response(m_Stream, m_CurrentRequest); HttpResponse response(m_Stream, m_CurrentRequest);
response.SetStatus(400, "Bad request"); response.SetStatus(400, "Bad request");
String msg = "<h1>Bad request</h1>"; String msg = "<h1>Bad request</h1><p>" + DiagnosticInformation(ex) + "</p>";
response.WriteBody(msg.CStr(), msg.GetLength()); response.WriteBody(msg.CStr(), msg.GetLength());
response.Finish(); response.Finish();

View File

@ -48,48 +48,54 @@ void HttpHandler::Register(const Url::Ptr& url, const HttpHandler::Ptr& handler)
node = sub_node; node = sub_node;
} }
node->Set("handler", handler); Array::Ptr handlers = node->Get("handlers");
}
bool HttpHandler::CanAlsoHandleUrl(const Url::Ptr& url) const if (!handlers) {
{ handlers = new Array();
return false; node->Set("handlers", handlers);
}
handlers->Add(handler);
} }
void HttpHandler::ProcessRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) void HttpHandler::ProcessRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{ {
Dictionary::Ptr node = m_UrlTree; Dictionary::Ptr node = m_UrlTree;
HttpHandler::Ptr current_handler, handler; std::vector<HttpHandler::Ptr> handlers;
bool exact_match = true;
BOOST_FOREACH(const String& elem, request.RequestUrl->GetPath()) { BOOST_FOREACH(const String& elem, request.RequestUrl->GetPath()) {
current_handler = node->Get("handler"); Array::Ptr current_handlers = node->Get("handlers");
if (current_handler)
handler = current_handler; 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"); Dictionary::Ptr children = node->Get("children");
if (!children) { if (!children) {
exact_match = false;
node.reset(); node.reset();
break; break;
} }
node = children->Get(elem); node = children->Get(elem);
if (!node) { if (!node)
exact_match = false; break;
}
std::reverse(handlers.begin(), handlers.end());
bool processed = false;
BOOST_FOREACH(const HttpHandler::Ptr& handler, handlers) {
if (handler->HandleRequest(user, request, response)) {
processed = true;
break; break;
} }
} }
if (!processed) {
if (node) {
current_handler = node->Get("handler");
if (current_handler)
handler = current_handler;
}
if (!handler || (!exact_match && !handler->CanAlsoHandleUrl(request.RequestUrl))) {
response.SetStatus(404, "Not found"); response.SetStatus(404, "Not found");
response.AddHeader("Content-Type", "text/html"); response.AddHeader("Content-Type", "text/html");
String msg = "<h1>Not found</h1>"; String msg = "<h1>Not found</h1>";
@ -97,6 +103,4 @@ void HttpHandler::ProcessRequest(const ApiUser::Ptr& user, HttpRequest& request,
response.Finish(); response.Finish();
return; return;
} }
handler->HandleRequest(user, request, response);
} }

View File

@ -40,8 +40,7 @@ class I2_REMOTE_API HttpHandler : public Object
public: public:
DECLARE_PTR_TYPEDEFS(HttpHandler); DECLARE_PTR_TYPEDEFS(HttpHandler);
virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const; virtual bool HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) = 0;
virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) = 0;
static void Register(const Url::Ptr& url, const HttpHandler::Ptr& handler); static void Register(const Url::Ptr& url, const HttpHandler::Ptr& handler);
static void ProcessRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response); static void ProcessRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);

View File

@ -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 <boost/algorithm/string.hpp>
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<DynamicObject::Ptr> 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;
}

View File

@ -17,22 +17,22 @@
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/ ******************************************************************************/
#ifndef HTTPDEMOHANDLER_H #ifndef STATUSQUERYHANDLER_H
#define HTTPDEMOHANDLER_H #define STATUSQUERYHANDLER_H
#include "remote/httphandler.hpp" #include "remote/httphandler.hpp"
namespace icinga namespace icinga
{ {
class I2_REMOTE_API HttpDemoHandler : public HttpHandler class I2_REMOTE_API StatusQueryHandler : public HttpHandler
{ {
public: 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 */