HttpsServer.cpp 9.51 KB
Newer Older
1
2
3
4
5
6
7
8
/*
 * HttpsServer.cpp
 *
 *  Created on: 25.05.2018
 *      Author: Micha Mueller
 */

#include "HttpsServer.h"
9
#include "timestamp.h"
10

11
#include <iostream>
12
13
#include <memory>
#include <functional>
14

15
16
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/info_parser.hpp>
Micha Mueller's avatar
Micha Mueller committed
17
18
#include <boost/algorithm/string/split.hpp>

19
20
#define LOGH(sev) LOG(sev) << "HttpsServer: "

21
22
HttpsServer::requestHandler::requestHandler(HttpsServer& httpsServer) :	_httpsServer(httpsServer) {}

23
void HttpsServer::requestHandler::operator()(server::request const &request, server::connection_ptr connection) {
24
	//first log some info about client
25
26
27
	server::string_type ip = source(request);
	unsigned int port = request.source_port;

28
	LOGH(info) << ip << ":" << port << " connected";
29

30
	//set appropriate default value to connection status
Micha Mueller's avatar
Micha Mueller committed
31
32
33
	connection->set_status(server::connection::internal_server_error);

	std::ostringstream data;
34
35

	boost::network::uri::uri uri("https://" + request.destination);
Micha Mueller's avatar
Micha Mueller committed
36
	server::string_type method = request.method;
37
38
39
40
41
42
43
	server::string_type path = uri.path();
	server::string_type query = uri.query();

	std::string response = "";
	std::string auth_value = "";
	std::vector<std::string> pathStrs;
	std::vector<std::pair<std::string, std::string>> queries;
44

Micha Mueller's avatar
Micha Mueller committed
45
46
	//first check if request is supported at all
	if (method != "GET" && method != "PUT") {
47
		LOGH(warning) << "Unsupported " << method << " request was made";
Micha Mueller's avatar
Micha Mueller committed
48
		connection->set_status(server::connection::not_supported);
49
50
		goto error;
	}
51

52
	LOGH(info) << method << " request of " << request.destination << " was made";
Micha Mueller's avatar
Micha Mueller committed
53

54
55
56
57
58
59
60
61
	//do some string processing
	//split path into its hierarchical parts
	if (path.size() >= 2) {
		if (path[0] == '/') {
			path.erase(0,1);
		}
		if (path[path.size() -1] == '/') {
			path.erase(path.size() -1);
Micha Mueller's avatar
Micha Mueller committed
62
		}
63

64
65
66
67
68
		boost::split(pathStrs, path, boost::is_any_of("/"), boost::token_compress_off);
	}

	//split query part into the individual queries (key-value pairs)
	{
Micha Mueller's avatar
Micha Mueller committed
69
70
71
72
		std::vector<std::string> queryStrs;
		boost::split(queryStrs, query, boost::is_any_of(";"), boost::token_compress_on);
		for(auto& key : queryStrs) {
			size_t pos = key.find("=");
73
			if (pos != std::string::npos) {
Micha Mueller's avatar
Micha Mueller committed
74
75
76
77
				std::string value;
				value = key.substr(pos+1);
				key.erase(pos);
				queries.push_back(std::make_pair(key, value));
78
79
			}
		}
80
81
	}
	//finished string processing
82

83
84
85
86
87
	//authkey is required in every case
	for (auto& p : queries) {
		if (p.first == "authkey") {
			auth_value = p.second;
			break;
88
		}
89
	}
90

91
	if (pathStrs.size() < 1) {
92
		LOGH(warning) << "Received malformed request: No first path part";
93
94
95
96
97
98
99
100
101
		connection->set_status(server::connection::bad_request);
		goto error;
	}

	//select code depending on request
	if (method == "GET") {

		//first check permission
		if (!_httpsServer.check_authkey(auth_value, permission::GETReq)) {
102
			LOGH(warning) << "Provided authentication token has insufficient permissions";
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
			connection->set_status(server::connection::unauthorized);
			goto error;
		}

		if (pathStrs[0] == "help") {
			response = "dcdbpusher RESTful API cheatsheet:\n"
					" -GET:  /help     This help message\n"
					"        /plugins  List of currently loaded plugins (Discovery)\n"
					"        /[plugin]/sensors\n"
					"                  List of currently running sensors which belong\n"
					"                  to the specified plugin (Discovery)\n"
					"        /[plugin]/[sensor]/avg?interval=[timeInSec]\n"
					"                  Average of last sensor readings from the last\n"
					"                  [interval] seconds or of all cached readings\n"
					"                  if no interval is given\n"
					" -PUT:  /[plugin]/[start|stop]\n"
					"                  Start/stop the sensors of the plugin\n"
					"\n"
					"All resources have to be prepended by host:port and need at\n"
					"least the query ?authkey=[token] at the end. Multiple queries\n"
					"need to be separated by semicolons(';')\n";
		} else if (pathStrs[0] == "plugins") {
125
126
127
128
129
			boost::property_tree::ptree root, plugins;
			for(auto& p : _httpsServer._plugins) {
				plugins.put(p.id, "");
			}
			root.add_child("plugins", plugins);
130
			boost::property_tree::write_info(data, root);
Micha Mueller's avatar
Micha Mueller committed
131
132
		} else {

133
134
			//do some prior checks
			if (pathStrs.size() < 2) {
135
				LOGH(warning) << "Received malformed request: No second path part";
136
137
138
139
140
				connection->set_status(server::connection::bad_request);
				goto error;
			}

			if (pathStrs[1] == "sensors") {
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
				response = "Plugin not found!";
				connection->set_status(server::connection::not_found);

				for(auto& p : _httpsServer._plugins) {
					if (p.id == pathStrs[0]) {
						boost::property_tree::ptree root, sensors;

						for(auto s : p.configurator->getSensors()) {
							sensors.put(s->getName(), "");
						}
						root.add_child(p.id, sensors);
						boost::property_tree::write_info(data, root);
						response = "";
						connection->set_status(server::connection::ok);
						break;
					}
				}
158
			} else {
Micha Mueller's avatar
Micha Mueller committed
159

160
				if (pathStrs.size() < 3) {
161
					LOGH(warning) << "Received malformed request: No third path part";
Micha Mueller's avatar
Micha Mueller committed
162
					connection->set_status(server::connection::bad_request);
163
164
					goto error;
				}
165

166
				if (pathStrs[2] != "avg") {
167
					LOGH(warning) << "Unknown action " << pathStrs[2] << " requested";
168
169
170
171
172
173
174
175
176
177
178
179
					connection->set_status(server::connection::not_supported);
					goto error;
				}

				std::string sensor = pathStrs[1];
				std::string action = pathStrs[2];
				uint64_t time = 0;

				for (auto& p : queries) {
					if (p.first == "interval") {
						time = getTimestamp();
						time -= S_TO_NS(std::stoul(p.second));
Micha Mueller's avatar
Micha Mueller committed
180
181
182
183
					}
				}

				//process actual request
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
				response = "Plugin not found!";
				connection->set_status(server::connection::not_found);

				for(auto& p : _httpsServer._plugins) {
					if (p.id == pathStrs[0]) {
						response = "Sensor not found!";
						for(auto s : p.configurator->getSensors()) {
							if (s->getName() == sensor) {
								uint64_t avg = 0;
								const reading_t * const cache = s->getCache();
								unsigned size = s->getCacheSize();

								unsigned count = 0;
								for(unsigned i = 0; i < size; i++) {
									if (cache[i].timestamp > time) {
										avg += cache[i].value;
										count++;
Micha Mueller's avatar
Micha Mueller committed
201
202
									}
								}
203
204
205
206
207
208
								if (count > 0)
									avg /= count;

								response = pathStrs[0] + "::" + sensor + " Average of last " + std::to_string(count) + " values is " + std::to_string(avg);
								connection->set_status(server::connection::ok);
								break;
209
210
211
							}
						}
					}
212
213
214
215
				}
			}
		}
	} else if (method == "PUT") {
Micha Mueller's avatar
Micha Mueller committed
216

217
218
		//first check permission
		if (!_httpsServer.check_authkey(auth_value, permission::PUTReq)) {
219
			LOGH(warning) << "Provided authentication token has insufficient permissions";
220
221
222
223
224
			connection->set_status(server::connection::unauthorized);
			goto error;
		}

		if (pathStrs.size() < 2) {
225
			LOGH(warning) << "Received malformed request: No second path part";
226
227
228
229
230
231
			connection->set_status(server::connection::bad_request);
			goto error;
		}

		//check if query and action are valid values
		if (pathStrs[1] != "start" && pathStrs[1] != "stop") {
232
			LOGH(warning) << "Unknown action " << pathStrs[1] << " requested";
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
			connection->set_status(server::connection::not_supported);
			goto error;
		}

		std::string action = pathStrs[1];

		//process actual request
		response = "Plugin not found!";
		connection->set_status(server::connection::not_found);

		for(auto& p : _httpsServer._plugins) {
			if (p.id == pathStrs[0]) {
				if (action == "start") {
					for(auto s : p.configurator->getSensors()) {
						s->startPolling();
					}
					response = "Plugin " + pathStrs[0] + ": Sensors started";
					connection->set_status(server::connection::ok);
				} else if (action == "stop") {
					for(auto s : p.configurator->getSensors()) {
						s->stopPolling();
					}
					response = "Plugin " + pathStrs[0] + ": Sensors stopped";
					connection->set_status(server::connection::ok);
257
				}
258
				break;
259
260
261
262
			}
		}
	}

263
	LOGH(info) << "Responding: " << response;
264
265
266
267
268
	data << response << std::endl;

	//jump right here if an error was encountered.
	//An empty response will be send while the connections status should be set to an appropriate error state
	error:
269
	server::response_header headers[] = { {"Connection", "close"}, {"Content-Type", "text/plain"} };
270
271
272
273
274
	connection->set_headers(boost::make_iterator_range(headers, headers + 2));
	connection->write(data.str());
}

void HttpsServer::requestHandler::log(const server::string_type& message) {
275
	LOGH(error) << message;
276
}
277

278
HttpsServer::HttpsServer(restAPISettings_t restAPISettings, pluginVector_t& plugins) :
279
		_plugins(plugins), _handler(*this) {
280

281
282
283
	std::shared_ptr<asio::ssl::context> ctx = std::make_shared<asio::ssl::context>(asio::ssl::context::sslv23);
	ctx->set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv3 | asio::ssl::context::single_dh_use);

284
	// Set certificate, private key and DH parameters
285
	//ctx->set_password_callback(HttpsServer::password_callback);
286
287
288
	ctx->use_certificate_chain_file(restAPISettings.certificate);
	ctx->use_private_key_file(restAPISettings.privateKey, asio::ssl::context::pem);
	ctx->use_tmp_dh_file(restAPISettings.dhFile);
289

290
	server::options options(_handler);
291
	_server = new server(options.address(restAPISettings.restHost).port(restAPISettings.restPort).context(ctx));
292
293
294
295
296
297
}

HttpsServer::~HttpsServer() {
	delete _server;
}

298
299
300
301
302
303
304
305
bool HttpsServer::check_authkey(const std::string& authkey, permission requiredPerm) {
	authkeyMap_t::iterator it = _authkeys.find(authkey);
	if (it != _authkeys.end()) {
		return it->second[requiredPerm];
	}
	return false;
}

306
307
308
309
310
/*
std::string HttpsServer::password_callback(std::size_t max_length, asio::ssl::context_base::password_purpose purpose) {
    return std::string("pwd");
}
*/