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