1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2 
3 #include "remote/actionshandler.hpp"
4 #include "remote/httputility.hpp"
5 #include "remote/filterutility.hpp"
6 #include "remote/apiaction.hpp"
7 #include "base/defer.hpp"
8 #include "base/exception.hpp"
9 #include "base/logger.hpp"
10 #include <set>
11 
12 using namespace icinga;
13 
14 thread_local ApiUser::Ptr ActionsHandler::AuthenticatedApiUser;
15 
16 REGISTER_URLHANDLER("/v1/actions", ActionsHandler);
17 
HandleRequest(AsioTlsStream & stream,const ApiUser::Ptr & user,boost::beast::http::request<boost::beast::http::string_body> & request,const Url::Ptr & url,boost::beast::http::response<boost::beast::http::string_body> & response,const Dictionary::Ptr & params,boost::asio::yield_context & yc,HttpServerConnection & server)18 bool ActionsHandler::HandleRequest(
19 	AsioTlsStream& stream,
20 	const ApiUser::Ptr& user,
21 	boost::beast::http::request<boost::beast::http::string_body>& request,
22 	const Url::Ptr& url,
23 	boost::beast::http::response<boost::beast::http::string_body>& response,
24 	const Dictionary::Ptr& params,
25 	boost::asio::yield_context& yc,
26 	HttpServerConnection& server
27 )
28 {
29 	namespace http = boost::beast::http;
30 
31 	if (url->GetPath().size() != 3)
32 		return false;
33 
34 	if (request.method() != http::verb::post)
35 		return false;
36 
37 	String actionName = url->GetPath()[2];
38 
39 	ApiAction::Ptr action = ApiAction::GetByName(actionName);
40 
41 	if (!action) {
42 		HttpUtility::SendJsonError(response, params, 404, "Action '" + actionName + "' does not exist.");
43 		return true;
44 	}
45 
46 	QueryDescription qd;
47 
48 	const std::vector<String>& types = action->GetTypes();
49 	std::vector<Value> objs;
50 
51 	String permission = "actions/" + actionName;
52 
53 	if (!types.empty()) {
54 		qd.Types = std::set<String>(types.begin(), types.end());
55 		qd.Permission = permission;
56 
57 		try {
58 			objs = FilterUtility::GetFilterTargets(qd, params, user);
59 		} catch (const std::exception& ex) {
60 			HttpUtility::SendJsonError(response, params, 404,
61 				"No objects found.",
62 				DiagnosticInformation(ex));
63 			return true;
64 		}
65 	} else {
66 		FilterUtility::CheckPermission(user, permission);
67 		objs.emplace_back(nullptr);
68 	}
69 
70 	ArrayData results;
71 
72 	Log(LogNotice, "ApiActionHandler")
73 		<< "Running action " << actionName;
74 
75 	bool verbose = false;
76 
77 	ActionsHandler::AuthenticatedApiUser = user;
78 	Defer a ([]() {
79 		ActionsHandler::AuthenticatedApiUser = nullptr;
80 	});
81 
82 	if (params)
83 		verbose = HttpUtility::GetLastParameter(params, "verbose");
84 
85 	for (const ConfigObject::Ptr& obj : objs) {
86 		try {
87 			results.emplace_back(action->Invoke(obj, params));
88 		} catch (const std::exception& ex) {
89 			Dictionary::Ptr fail = new Dictionary({
90 				{ "code", 500 },
91 				{ "status", "Action execution failed: '" + DiagnosticInformation(ex, false) + "'." }
92 			});
93 
94 			/* Exception for actions. Normally we would handle this inside SendJsonError(). */
95 			if (verbose)
96 				fail->Set("diagnostic_information", DiagnosticInformation(ex));
97 
98 			results.emplace_back(std::move(fail));
99 		}
100 	}
101 
102 	int statusCode = 500;
103 	std::set<int> okStatusCodes, nonOkStatusCodes;
104 
105 	for (const Dictionary::Ptr& res : results) {
106 		if (!res->Contains("code")) {
107 			continue;
108 		}
109 
110 		auto code = res->Get("code");
111 
112 		if (code >= 200 && code <= 299) {
113 			okStatusCodes.insert(code);
114 		} else {
115 			nonOkStatusCodes.insert(code);
116 		}
117 	}
118 
119 	size_t okSize = okStatusCodes.size();
120 	size_t nonOkSize = nonOkStatusCodes.size();
121 
122 	if (okSize == 1u && nonOkSize == 0u) {
123 		statusCode = *okStatusCodes.begin();
124 	} else if (nonOkSize == 1u) {
125 		statusCode = *nonOkStatusCodes.begin();
126 	} else if (okSize >= 2u && nonOkSize == 0u) {
127 		statusCode = 200;
128 	}
129 
130 	response.result(statusCode);
131 
132 	Dictionary::Ptr result = new Dictionary({
133 		{ "results", new Array(std::move(results)) }
134 	});
135 
136 	HttpUtility::SendJsonBody(response, params, result);
137 
138 	return true;
139 }
140