1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2 
3 #include "remote/configstageshandler.hpp"
4 #include "remote/configpackageutility.hpp"
5 #include "remote/httputility.hpp"
6 #include "remote/filterutility.hpp"
7 #include "base/application.hpp"
8 #include "base/exception.hpp"
9 
10 using namespace icinga;
11 
12 REGISTER_URLHANDLER("/v1/config/stages", ConfigStagesHandler);
13 
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)14 bool ConfigStagesHandler::HandleRequest(
15 	AsioTlsStream& stream,
16 	const ApiUser::Ptr& user,
17 	boost::beast::http::request<boost::beast::http::string_body>& request,
18 	const Url::Ptr& url,
19 	boost::beast::http::response<boost::beast::http::string_body>& response,
20 	const Dictionary::Ptr& params,
21 	boost::asio::yield_context& yc,
22 	HttpServerConnection& server
23 )
24 {
25 	namespace http = boost::beast::http;
26 
27 	if (url->GetPath().size() > 5)
28 		return false;
29 
30 	if (request.method() == http::verb::get)
31 		HandleGet(user, request, url, response, params);
32 	else if (request.method() == http::verb::post)
33 		HandlePost(user, request, url, response, params);
34 	else if (request.method() == http::verb::delete_)
35 		HandleDelete(user, request, url, response, params);
36 	else
37 		return false;
38 
39 	return true;
40 }
41 
HandleGet(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)42 void ConfigStagesHandler::HandleGet(
43 	const ApiUser::Ptr& user,
44 	boost::beast::http::request<boost::beast::http::string_body>& request,
45 	const Url::Ptr& url,
46 	boost::beast::http::response<boost::beast::http::string_body>& response,
47 	const Dictionary::Ptr& params
48 )
49 {
50 	namespace http = boost::beast::http;
51 
52 	FilterUtility::CheckPermission(user, "config/query");
53 
54 	if (url->GetPath().size() >= 4)
55 		params->Set("package", url->GetPath()[3]);
56 
57 	if (url->GetPath().size() >= 5)
58 		params->Set("stage", url->GetPath()[4]);
59 
60 	String packageName = HttpUtility::GetLastParameter(params, "package");
61 	String stageName = HttpUtility::GetLastParameter(params, "stage");
62 
63 	if (!ConfigPackageUtility::ValidatePackageName(packageName))
64 		return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
65 
66 	if (!ConfigPackageUtility::ValidateStageName(stageName))
67 		return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
68 
69 	ArrayData results;
70 
71 	std::vector<std::pair<String, bool> > paths = ConfigPackageUtility::GetFiles(packageName, stageName);
72 
73 	String prefixPath = ConfigPackageUtility::GetPackageDir() + "/" + packageName + "/" + stageName + "/";
74 
75 	for (const auto& kv : paths) {
76 		results.push_back(new Dictionary({
77 			{ "type", kv.second ? "directory" : "file" },
78 			{ "name", kv.first.SubStr(prefixPath.GetLength()) }
79 		}));
80 	}
81 
82 	Dictionary::Ptr result = new Dictionary({
83 		{ "results", new Array(std::move(results)) }
84 	});
85 
86 	response.result(http::status::ok);
87 	HttpUtility::SendJsonBody(response, params, result);
88 }
89 
HandlePost(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)90 void ConfigStagesHandler::HandlePost(
91 	const ApiUser::Ptr& user,
92 	boost::beast::http::request<boost::beast::http::string_body>& request,
93 	const Url::Ptr& url,
94 	boost::beast::http::response<boost::beast::http::string_body>& response,
95 	const Dictionary::Ptr& params
96 )
97 {
98 	namespace http = boost::beast::http;
99 
100 	FilterUtility::CheckPermission(user, "config/modify");
101 
102 	if (url->GetPath().size() >= 4)
103 		params->Set("package", url->GetPath()[3]);
104 
105 	String packageName = HttpUtility::GetLastParameter(params, "package");
106 
107 	if (!ConfigPackageUtility::ValidatePackageName(packageName))
108 		return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
109 
110 	bool reload = true;
111 
112 	if (params->Contains("reload"))
113 		reload = HttpUtility::GetLastParameter(params, "reload");
114 
115 	bool activate = true;
116 
117 	if (params->Contains("activate"))
118 		activate = HttpUtility::GetLastParameter(params, "activate");
119 
120 	Dictionary::Ptr files = params->Get("files");
121 
122 	String stageName;
123 
124 	try {
125 		if (!files)
126 			BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'files' must be specified."));
127 
128 		if (reload && !activate)
129 			BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'reload' must be false when 'activate' is false."));
130 
131 		std::unique_lock<std::mutex> lock(ConfigPackageUtility::GetStaticPackageMutex());
132 
133 		stageName = ConfigPackageUtility::CreateStage(packageName, files);
134 
135 		/* validate the config. on success, activate stage and reload */
136 		ConfigPackageUtility::AsyncTryActivateStage(packageName, stageName, activate, reload);
137 	} catch (const std::exception& ex) {
138 		return HttpUtility::SendJsonError(response, params, 500,
139 			"Stage creation failed.",
140 			DiagnosticInformation(ex));
141 	}
142 
143 
144 	String responseStatus = "Created stage. ";
145 
146 	if (reload)
147 		responseStatus += "Reload triggered.";
148 	else
149 		responseStatus += "Reload skipped.";
150 
151 	Dictionary::Ptr result1 = new Dictionary({
152 		{ "package", packageName },
153 		{ "stage", stageName },
154 		{ "code", 200 },
155 		{ "status", responseStatus }
156 	});
157 
158 	Dictionary::Ptr result = new Dictionary({
159 		{ "results", new Array({ result1 }) }
160 	});
161 
162 	response.result(http::status::ok);
163 	HttpUtility::SendJsonBody(response, params, result);
164 }
165 
HandleDelete(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)166 void ConfigStagesHandler::HandleDelete(
167 	const ApiUser::Ptr& user,
168 	boost::beast::http::request<boost::beast::http::string_body>& request,
169 	const Url::Ptr& url,
170 	boost::beast::http::response<boost::beast::http::string_body>& response,
171 	const Dictionary::Ptr& params
172 )
173 {
174 	namespace http = boost::beast::http;
175 
176 	FilterUtility::CheckPermission(user, "config/modify");
177 
178 	if (url->GetPath().size() >= 4)
179 		params->Set("package", url->GetPath()[3]);
180 
181 	if (url->GetPath().size() >= 5)
182 		params->Set("stage", url->GetPath()[4]);
183 
184 	String packageName = HttpUtility::GetLastParameter(params, "package");
185 	String stageName = HttpUtility::GetLastParameter(params, "stage");
186 
187 	if (!ConfigPackageUtility::ValidatePackageName(packageName))
188 		return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
189 
190 	if (!ConfigPackageUtility::ValidateStageName(stageName))
191 		return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
192 
193 	try {
194 		ConfigPackageUtility::DeleteStage(packageName, stageName);
195 	} catch (const std::exception& ex) {
196 		return HttpUtility::SendJsonError(response, params, 500,
197 			"Failed to delete stage '" + stageName + "' in package '" + packageName + "'.",
198 			DiagnosticInformation(ex));
199 	}
200 
201 	Dictionary::Ptr result1 = new Dictionary({
202 		{ "code", 200 },
203 		{ "package", packageName },
204 		{ "stage", stageName },
205 		{ "status", "Stage deleted." }
206 	});
207 
208 	Dictionary::Ptr result = new Dictionary({
209 		{ "results", new Array({ result1 }) }
210 	});
211 
212 	response.result(http::status::ok);
213 	HttpUtility::SendJsonBody(response, params, result);
214 }
215 
216