1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "remote/objectqueryhandler.hpp"
4 #include "remote/httputility.hpp"
5 #include "remote/filterutility.hpp"
6 #include "base/serializer.hpp"
7 #include "base/dependencygraph.hpp"
8 #include "base/configtype.hpp"
9 #include <boost/algorithm/string/case_conv.hpp>
10 #include <set>
11
12 using namespace icinga;
13
14 REGISTER_URLHANDLER("/v1/objects", ObjectQueryHandler);
15
SerializeObjectAttrs(const Object::Ptr & object,const String & attrPrefix,const Array::Ptr & attrs,bool isJoin,bool allAttrs)16 Dictionary::Ptr ObjectQueryHandler::SerializeObjectAttrs(const Object::Ptr& object,
17 const String& attrPrefix, const Array::Ptr& attrs, bool isJoin, bool allAttrs)
18 {
19 Type::Ptr type = object->GetReflectionType();
20
21 std::vector<int> fids;
22
23 if (isJoin && attrs) {
24 ObjectLock olock(attrs);
25 for (const String& attr : attrs) {
26 if (attr == attrPrefix) {
27 allAttrs = true;
28 break;
29 }
30 }
31 }
32
33 if (!isJoin && (!attrs || attrs->GetLength() == 0))
34 allAttrs = true;
35
36 if (allAttrs) {
37 for (int fid = 0; fid < type->GetFieldCount(); fid++) {
38 fids.push_back(fid);
39 }
40 } else if (attrs) {
41 ObjectLock olock(attrs);
42 for (const String& attr : attrs) {
43 String userAttr;
44
45 if (isJoin) {
46 String::SizeType dpos = attr.FindFirstOf(".");
47 if (dpos == String::NPos)
48 continue;
49
50 String userJoinAttr = attr.SubStr(0, dpos);
51 if (userJoinAttr != attrPrefix)
52 continue;
53
54 userAttr = attr.SubStr(dpos + 1);
55 } else
56 userAttr = attr;
57
58 int fid = type->GetFieldId(userAttr);
59
60 if (fid < 0)
61 BOOST_THROW_EXCEPTION(ScriptError("Invalid field specified: " + userAttr));
62
63 fids.push_back(fid);
64 }
65 }
66
67 DictionaryData resultAttrs;
68 resultAttrs.reserve(fids.size());
69
70 for (int fid : fids) {
71 Field field = type->GetFieldInfo(fid);
72
73 Value val = object->GetField(fid);
74
75 /* hide attributes which shouldn't be user-visible */
76 if (field.Attributes & FANoUserView)
77 continue;
78
79 /* hide internal navigation fields */
80 if (field.Attributes & FANavigation && !(field.Attributes & (FAConfig | FAState)))
81 continue;
82
83 Value sval = Serialize(val, FAConfig | FAState);
84 resultAttrs.emplace_back(field.Name, sval);
85 }
86
87 return new Dictionary(std::move(resultAttrs));
88 }
89
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)90 bool ObjectQueryHandler::HandleRequest(
91 AsioTlsStream& stream,
92 const ApiUser::Ptr& user,
93 boost::beast::http::request<boost::beast::http::string_body>& request,
94 const Url::Ptr& url,
95 boost::beast::http::response<boost::beast::http::string_body>& response,
96 const Dictionary::Ptr& params,
97 boost::asio::yield_context& yc,
98 HttpServerConnection& server
99 )
100 {
101 namespace http = boost::beast::http;
102
103 if (url->GetPath().size() < 3 || url->GetPath().size() > 4)
104 return false;
105
106 if (request.method() != http::verb::get)
107 return false;
108
109 Type::Ptr type = FilterUtility::TypeFromPluralName(url->GetPath()[2]);
110
111 if (!type) {
112 HttpUtility::SendJsonError(response, params, 400, "Invalid type specified.");
113 return true;
114 }
115
116 QueryDescription qd;
117 qd.Types.insert(type->GetName());
118 qd.Permission = "objects/query/" + type->GetName();
119
120 Array::Ptr uattrs, ujoins, umetas;
121
122 try {
123 uattrs = params->Get("attrs");
124 } catch (const std::exception&) {
125 HttpUtility::SendJsonError(response, params, 400,
126 "Invalid type for 'attrs' attribute specified. Array type is required.");
127 return true;
128 }
129
130 try {
131 ujoins = params->Get("joins");
132 } catch (const std::exception&) {
133 HttpUtility::SendJsonError(response, params, 400,
134 "Invalid type for 'joins' attribute specified. Array type is required.");
135 return true;
136 }
137
138 try {
139 umetas = params->Get("meta");
140 } catch (const std::exception&) {
141 HttpUtility::SendJsonError(response, params, 400,
142 "Invalid type for 'meta' attribute specified. Array type is required.");
143 return true;
144 }
145
146 bool allJoins = HttpUtility::GetLastParameter(params, "all_joins");
147
148 params->Set("type", type->GetName());
149
150 if (url->GetPath().size() >= 4) {
151 String attr = type->GetName();
152 boost::algorithm::to_lower(attr);
153 params->Set(attr, url->GetPath()[3]);
154 }
155
156 std::vector<Value> objs;
157
158 try {
159 objs = FilterUtility::GetFilterTargets(qd, params, user);
160 } catch (const std::exception& ex) {
161 HttpUtility::SendJsonError(response, params, 404,
162 "No objects found.",
163 DiagnosticInformation(ex));
164 return true;
165 }
166
167 ArrayData results;
168 results.reserve(objs.size());
169
170 std::set<String> joinAttrs;
171 std::set<String> userJoinAttrs;
172
173 if (ujoins) {
174 ObjectLock olock(ujoins);
175 for (const String& ujoin : ujoins) {
176 userJoinAttrs.insert(ujoin.SubStr(0, ujoin.FindFirstOf(".")));
177 }
178 }
179
180 for (int fid = 0; fid < type->GetFieldCount(); fid++) {
181 Field field = type->GetFieldInfo(fid);
182
183 if (!(field.Attributes & FANavigation))
184 continue;
185
186 if (!allJoins && userJoinAttrs.find(field.NavigationName) == userJoinAttrs.end())
187 continue;
188
189 joinAttrs.insert(field.Name);
190 }
191
192 for (const ConfigObject::Ptr& obj : objs) {
193 DictionaryData result1{
194 { "name", obj->GetName() },
195 { "type", obj->GetReflectionType()->GetName() }
196 };
197
198 DictionaryData metaAttrs;
199
200 if (umetas) {
201 ObjectLock olock(umetas);
202 for (const String& meta : umetas) {
203 if (meta == "used_by") {
204 Array::Ptr used_by = new Array();
205 metaAttrs.emplace_back("used_by", used_by);
206
207 for (const Object::Ptr& pobj : DependencyGraph::GetParents((obj)))
208 {
209 ConfigObject::Ptr configObj = dynamic_pointer_cast<ConfigObject>(pobj);
210
211 if (!configObj)
212 continue;
213
214 used_by->Add(new Dictionary({
215 { "type", configObj->GetReflectionType()->GetName() },
216 { "name", configObj->GetName() }
217 }));
218 }
219 } else if (meta == "location") {
220 metaAttrs.emplace_back("location", obj->GetSourceLocation());
221 } else {
222 HttpUtility::SendJsonError(response, params, 400, "Invalid field specified for meta: " + meta);
223 return true;
224 }
225 }
226 }
227
228 result1.emplace_back("meta", new Dictionary(std::move(metaAttrs)));
229
230 try {
231 result1.emplace_back("attrs", SerializeObjectAttrs(obj, String(), uattrs, false, false));
232 } catch (const ScriptError& ex) {
233 HttpUtility::SendJsonError(response, params, 400, ex.what());
234 return true;
235 }
236
237 DictionaryData joins;
238
239 for (const String& joinAttr : joinAttrs) {
240 Object::Ptr joinedObj;
241 int fid = type->GetFieldId(joinAttr);
242
243 if (fid < 0) {
244 HttpUtility::SendJsonError(response, params, 400, "Invalid field specified for join: " + joinAttr);
245 return true;
246 }
247
248 Field field = type->GetFieldInfo(fid);
249
250 if (!(field.Attributes & FANavigation)) {
251 HttpUtility::SendJsonError(response, params, 400, "Not a joinable field: " + joinAttr);
252 return true;
253 }
254
255 joinedObj = obj->NavigateField(fid);
256
257 if (!joinedObj)
258 continue;
259
260 String prefix = field.NavigationName;
261
262 try {
263 joins.emplace_back(prefix, SerializeObjectAttrs(joinedObj, prefix, ujoins, true, allJoins));
264 } catch (const ScriptError& ex) {
265 HttpUtility::SendJsonError(response, params, 400, ex.what());
266 return true;
267 }
268 }
269
270 result1.emplace_back("joins", new Dictionary(std::move(joins)));
271
272 results.push_back(new Dictionary(std::move(result1)));
273 }
274
275 Dictionary::Ptr result = new Dictionary({
276 { "results", new Array(std::move(results)) }
277 });
278
279 response.result(http::status::ok);
280 HttpUtility::SendJsonBody(response, params, result);
281
282 return true;
283 }
284