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