Commit b77fa188 authored by Micha Mueller's avatar Micha Mueller
Browse files

Purge old RestAPI implementation based on cpprestsdk

parent 355cee8b
......@@ -12,7 +12,6 @@ OPENSSL_VERSION = 1.0.2l
CPPDRV_VERSION = 2.10.0
LIBUV_VERSION = 1.24.0
CPPNET_VERSION = 0.12.0-final
CPPRESTSDK_VERSION = 2.10.6
BACNET-STACK_VERSION = 0.8.5
FREEIPMI_VERSION = 1.5.5
NET-SNMP_VERSION = 5.8
......@@ -26,7 +25,6 @@ DISTFILES = apache-cassandra-$(CASSANDRA_VERSION).tar.gz;http://archive.apache.o
libuv-v$(LIBUV_VERSION).tar.gz;https://dist.libuv.org/dist/v$(LIBUV_VERSION)/libuv-v$(LIBUV_VERSION).tar.gz \
cpp-driver-$(CPPDRV_VERSION).tar.gz;https://github.com/datastax/cpp-driver/archive/$(CPPDRV_VERSION).tar.gz \
cpp-netlib-$(CPPNET_VERSION).tar.gz;http://downloads.cpp-netlib.org/0.12.0/cpp-netlib-$(CPPNET_VERSION).tar.gz \
cpprestsdk-$(CPPRESTSDK_VERSION).tar.gz;https://github.com/Microsoft/cpprestsdk/archive/v$(CPPRESTSDK_VERSION).tar.gz \
bacnet-stack-$(BACNET-STACK_VERSION).tgz;https://downloads.sourceforge.net/project/bacnet/bacnet-stack/bacnet-stack-$(BACNET-STACK_VERSION)/bacnet-stack-$(BACNET-STACK_VERSION).tgz \
freeipmi-$(FREEIPMI_VERSION).tar.gz;http://ftp.gnu.org/gnu/freeipmi/freeipmi-$(FREEIPMI_VERSION).tar.gz \
net-snmp-$(NET-SNMP_VERSION).tar.gz;https://sourceforge.net/projects/net-snmp/files/net-snmp/$(NET-SNMP_VERSION)/net-snmp-$(NET-SNMP_VERSION).tar.gz/download
......@@ -38,7 +36,6 @@ DISTFILES_HASHES = apache-cassandra-2.2.10.tar.gz|4c58cb7c6753ce26f7c4d650502fee
cpp-driver-2.10.0.tar.gz|6d15dd2cccd2efd1fdc86089d26971d0;\
libuv-v1.24.0.tar.gz|90320330757253b07404d2a97f59c66b;\
cpp-netlib-0.12.0-final.tar.gz|29b87c0e8c1a9e7fbdea5afcec947d53;\
cpprestsdk-2.10.6.tar.gz|0a9b2424578fbeb1ac8465173ce8fc71;\
bacnet-stack-0.8.5.tgz|66b69111d91432fa67a7c6c1a653434d;\
freeipmi-1.5.5.tar.gz|b8abfefee0b757f351d8fab777e3c1bb;\
net-snmp-5.8.tar.gz|63bfc65fbb86cdb616598df1aff6458a
......@@ -215,25 +212,6 @@ $(DCDBDEPSPATH)/cpp-netlib-$(CPPNET_VERSION)/.built: $(DCDBDEPSPATH)/cpp-netlib-
$(DCDBDEPSPATH)/cpp-netlib-$(CPPNET_VERSION)/.installed: $(DCDBDEPSPATH)/cpp-netlib-$(CPPNET_VERSION)/.built | $(DCDBDEPLOYPATH)
@echo "Installing cpp-netlib..."
cd $(DCDBDEPSPATH)/cpp-netlib_build && make install && touch $(@)
$(DCDBDEPSPATH)/cpprestsdk-$(CPPRESTSDK_VERSION)/.built: $(DCDBDEPSPATH)/cpprestsdk-$(CPPRESTSDK_VERSION)/.patched
@echo "Building cpprestsdk..."
mkdir -p $(DCDBDEPSPATH)/cpprestsdk_build
cd $(DCDBDEPSPATH)/cpprestsdk_build && \
cmake -DCMAKE_BUILD_TYPE=Release \
-DBOOST_ROOT=$(DCDBDEPSPATH)/boost_$(BOOST_VERSION_U) \
-DOPENSSL_ROOT_DIR=$(DCDBDEPSPATH)/openssl-$(OPENSSL_VERSION) \
-DCPPREST_EXCLUDE_WEBSOCKETS=ON \
-DCPPREST_EXCLUDE_COMPRESSION=ON \
-DBUILD_TESTS=OFF \
-DBUILD_SAMPLES=OFF \
-DCMAKE_INSTALL_PREFIX=$(DCDBDEPLOYPATH)/ \
$(@D)/Release && \
make -j $(MAKETHREADS) && touch $(@)
$(DCDBDEPSPATH)/cpprestsdk-$(CPPRESTSDK_VERSION)/.installed: $(DCDBDEPSPATH)/cpprestsdk-$(CPPRESTSDK_VERSION)/.built
@echo "Installing cpprestsdk..."
cd $(DCDBDEPSPATH)/cpprestsdk_build && make install && touch $(@)
$(DCDBDEPSPATH)/apache-cassandra-$(CASSANDRA_VERSION)/.built: $(DCDBDEPSPATH)/apache-cassandra-$(CASSANDRA_VERSION)/.patched
@touch $(DCDBDEPSPATH)/apache-cassandra-$(CASSANDRA_VERSION)/.built
......
/*
* HttpsServer.h
*
* Created on: 19.03.2019
* Author: Micha Mueller, Daniele Tafani (original)
*/
#ifndef COMMON_INCLUDE_HTTPSSERVER_H_
#define COMMON_INCLUDE_HTTPSSERVER_H_
#include <bitset>
#include <unordered_map>
#include <utility>
#include <vector>
#include "cpprest/http_msg.h"
#include "cpprest/http_listener.h"
#include "logging.h"
/**
* Struct defining general server settings.
*/
typedef struct {
std::string host; /**< Host name to use */
std::string port; /**< Port to use */
std::string certificate; /**< Certificate chain file in PEM format */
std::string privateKey; /**< Private key file in PEM format */
std::string dhFile; /**< File with Diffie-Hellman parameters */
} serverSettings_t;
/**
* Enum to distinguish different permissions
*/
enum permission {
GET = 0, /**< Permission to make GET requests */
PUT = 1, /**< Permission to make PUT requests */
POST = 2, /**< Permission to make POST requests */
DELETE = 3, /**< Permission to make DELETE requests */
NUM_PERMISSIONS = 4 /**< Number of permissions there are. */
};
using userAttributes_t = std::pair<std::string, std::bitset<NUM_PERMISSIONS>>;
using userBase_t = std::unordered_map<std::string, userAttributes_t>;
/**
* General HttpsServer class bundling some common functionality for different
* REST API servers. Intended as parent class for concrete REST API server
* implementations.
*/
class HttpsServer {
protected:
//This class should not be constructible on its own
HttpsServer(serverSettings_t settings);
virtual ~HttpsServer() {
stop();
}
public:
/**
* @brief Start the server and listen to incoming requests.
*/
void start() {
_listener->open().wait();
_helper->open().wait();
}
/**
* @brief Stop the server, i.e. stop accepting new request and close all
* connections.
*/
void stop() {
_listener->close().wait();
_helper->close().wait();
}
/**
* @brief Add a user to the list of known users.
*
* @param userName Identifying name of the new user.
* @param att Attributes related to the new user.
*
* @return True if the new user was successfully added. False if the user
* name is already in use.
*/
bool addUser(std::string userName, userAttributes_t att) {
return _users.insert(std::make_pair(userName, att)).second;
}
protected:
/**
* @brief Handler for GET requests.
*
* @details Implement here the logic for GET requests. This method will be
* called from the internal request handler which takes care of
* authentication and permission validation.
*
* @param msg The incoming request message.
*/
virtual void handle_get(web::http::http_request& msg);
/**
* @brief Handler for PUT requests.
*
* @details Implement here the logic for PUT requests. This method will be
* called from the internal request handler which takes care of
* authentication and permission validation.
*
* @param msg The incoming request message.
*/
virtual void handle_put(web::http::http_request& msg);
/**
* @brief Handler for POST requests.
*
* @details Implement here the logic for POST requests. This method will be
* called from the internal request handler which takes care of
* authentication and permission validation.
*
* @param msg The incoming request message.
*/
virtual void handle_post(web::http::http_request& msg);
/**
* @brief Handler for DELETE requests.
*
* @details Implement here the logic for DELETE requests. This method will be
* called from the internal request handler which takes care of
* authentication and permission validation.
*
* @param msg The incoming request message.
*/
virtual void handle_delete(web::http::http_request& msg);
/**
* @brief Handler for GET "/help" requests.
*
* @details This handler should respond with a nice help message shortly
* explaining the usage of your RESTful API. It can be accessed by
* sending a GET request to the endpoint "/help" and requires no
* authentication nor permissions.
*
* @param msg The incoming request message
*/
virtual void handle_get_help(web::http::http_request& msg);
/**
* @brief Split a relative URI into its components.
*
* @details Splits a URI of format /goto/location?arg1=test1;arg2=test2 into
* pathParts: ["goto", "location"]
* queries: [("arg1", "test1"), ("arg2", "test")]
*
* @param uri The relative URI to split.
* @param pathParts Vector of strings where the path parts are stored in.
* @param queries Vector of string pairs, where pairs of query-identifier
* and query-value are stored in.
*/
//TODO extend to support #fragments
void splitUri(std::string uri, std::vector<std::string>& pathParts,
std::vector<std::pair<std::string, std::string>>& queries);
private:
/**
* @brief Internal handler for GET requests.
*
* @details Checks the provided authentication credentials and if the user
* has sufficient permissions for this request. On success, forwards
* the message to the actual handler.
*/
void handle_get_impl(web::http::http_request msg) {
if (validateUser(msg, permission::GET)) {
//explicitly call overwriting method if present
this->handle_get(msg);
}
}
/**
* @brief Internal handler for PUT requests.
*
* @details Checks the provided authentication credentials and if the user
* has sufficient permissions for this request. On success, forwards
* the message to the actual handler.
*/
void handle_put_impl(web::http::http_request msg) {
if (validateUser(msg, permission::PUT)) {
//explicitly call overwriting method if present
this->handle_put(msg);
}
}
/**
* @brief Internal handler for POST requests.
*
* @details Checks the provided authentication credentials and if the user
* has sufficient permissions for this request. On success, forwards
* the message to the actual handler.
*/
void handle_post_impl(web::http::http_request msg) {
if (validateUser(msg, permission::POST)) {
//explicitly call overwriting method if present
this->handle_post(msg);
}
}
/**
* @brief Internal handler for DELETE requests.
*
* @details Checks the provided authentication credentials and if the user
* has sufficient permissions for this request. On success, forwards
* the message to the actual handler.
*/
void handle_delete_impl(web::http::http_request msg) {
if (validateUser(msg, permission::DELETE)) {
//explicitly call overwriting method if present
this->handle_delete(msg);
}
}
/**
* @brief Internal handler for GET '/help' requests.
*
* @details Just forwards the message to the actual handler. GET '/help'
* does not require any special permissions.
*/
void handle_get_help_impl(web::http::http_request msg) {
this->handle_get_help(msg);
}
/**
* @brief Validates if correct user and password credentials were provided.
*
* @details Extracts and decodes the credentials from msg and checks whether
* the user name is known and the password matches. If not, an
* appropriate "Unauthorized" response is sent and no further
* response from the caller is required. If the password is valid,
* it is further verified that the user has the required permission.
* If the user is not permitted, a "Forbidden" response is sent and
* no further response from the caller is required.
*
* @param msg The request message for which to validate the user.
* @param perm Permission the user should have.
*
* @return True if the user was successfully authenticated and has the
* required permission. False otherwise (i.e. a response has
* already been sent).
*/
bool validateUser(const web::http::http_request& msg, permission perm);
protected:
logger_t lg;
private:
using http_listener = web::http::experimental::listener::http_listener;
std::unique_ptr<http_listener> _listener; /**< Listener for all REST API
requests */
std::unique_ptr<http_listener> _helper; /**< Listener which only listens for
the specific "/help" endpoint */
userBase_t _users; /**< Storage for users and related attributes like
permissions and password */
};
#endif /* COMMON_INCLUDE_HTTPSSERVER_H_ */
/*
* HttpsServer.cpp
*
* Created on: 19.03.2019
* Author: Micha Mueller, Daniele Tafani (original)
*/
#include "HttpsServer.h"
#include <sstream>
#include <boost/asio/ssl.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/insert_linebreaks.hpp>
#include <boost/archive/iterators/remove_whitespace.hpp>
#include <boost/archive/iterators/transform_width.hpp>
using namespace web::http;
using namespace web::http::experimental::listener;
#define HLOG(sev) LOG(sev) << "HttpsServer: "
HttpsServer::HttpsServer(serverSettings_t settings) {
http_listener_config cfg;
cfg.set_ssl_context_callback([settings](boost::asio::ssl::context& ctx) {
ctx.set_options(boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv3 |
boost::asio::ssl::context::single_dh_use);
// Password callback needs to be set before setting cert and key.
/*
ctx.set_password_callback([](std::size_t max_length, asio::ssl::context::password_purpose purpose)
{
return "password";
});
*/
ctx.use_certificate_chain_file(settings.certificate);
ctx.use_private_key_file(settings.privateKey, boost::asio::ssl::context::pem);
ctx.use_tmp_dh_file(settings.dhFile);
});
utility::string_t address = U("https://" + settings.host + ":" + settings.port);
_listener = std::unique_ptr<http_listener>(new http_listener(address, cfg));
_helper = std::unique_ptr<http_listener>(new http_listener(address + "/help", cfg));
_listener->support(methods::GET, std::bind(&HttpsServer::handle_get_impl, this, std::placeholders::_1));
_listener->support(methods::PUT, std::bind(&HttpsServer::handle_put_impl, this, std::placeholders::_1));
_listener->support(methods::POST, std::bind(&HttpsServer::handle_post_impl, this, std::placeholders::_1));
_listener->support(methods::DEL, std::bind(&HttpsServer::handle_delete_impl, this, std::placeholders::_1));
_helper->support(methods::GET, std::bind(&HttpsServer::handle_get_help_impl, this, std::placeholders::_1));
}
void HttpsServer::handle_get(http_request& msg) {
http_response response;
response.set_status_code(status_codes::NotImplemented);
response.set_body("GET Method not implemented\n");
msg.reply(response);
}
void HttpsServer::handle_put(http_request& msg) {
http_response response;
response.set_status_code(status_codes::NotImplemented);
response.set_body("PUT Method not implemented\n");
msg.reply(response);
}
void HttpsServer::handle_post(http_request& msg) {
http_response response;
response.set_status_code(status_codes::NotImplemented);
response.set_body("POST Method not implemented\n");
msg.reply(response);
}
void HttpsServer::handle_delete(http_request& msg) {
http_response response;
response.set_status_code(status_codes::NotImplemented);
response.set_body("DELETE Method not implemented\n");
msg.reply(response);
}
void HttpsServer::handle_get_help(http_request& msg) {
http_response response;
response.set_status_code(status_codes::NotImplemented);
response.set_body("GET /help not implemented\n");
msg.reply(response);
}
void HttpsServer::splitUri(std::string uri, std::vector<std::string>& pathParts,
std::vector<std::pair<std::string, std::string>>& queries) {
//split into path and query
std::string path;
std::string query;
size_t pos = uri.find('?');
path = uri.substr(0, pos);
query = uri.substr(pos+1, uri.length());
//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);
}
std::stringstream stream(path);
std::string part;
while(std::getline(stream, part, '/')) {
pathParts.push_back(part);
}
}
//split query part into the individual queries (key-value pairs)
std::vector<std::string> queryStrs;
std::stringstream stream(query);
std::string part;
while(std::getline(stream, part, ';')) {
queryStrs.push_back(part);
}
for(auto& key : queryStrs) {
size_t pos = key.find("=");
if (pos != std::string::npos) {
std::string value;
value = key.substr(pos+1);
key.erase(pos);
queries.push_back(std::make_pair(key, value));
}
}
}
bool HttpsServer::validateUser(const http_request& msg, permission perm) {
std::string usr;
std::string pwd;
std::string output;
http_response response;
HLOG(info) << msg.remote_address() << " connected";
HLOG(info) << msg.to_string();
//Get the value of the HTTP Basic Authorization field from the Header.
http_headers::const_iterator it = msg.headers().find("Authorization");
if (it == msg.headers().end()) {
HLOG(info) << "No credentials were provided";
response.set_status_code(status_codes::Unauthorized);
output = "Unauthorized access!\n";
response.set_body(output);
msg.reply(response);
return false;
}
std::string auth = it->second;
std::string credentials;
//Remove the substring "Basic" and decode the credentials.
auth.erase(0,6);
using namespace boost::archive::iterators;
using ItBinaryT = transform_width<binary_from_base64<remove_whitespace
<std::string::const_iterator>>, 8, 6>;
try {
// If the input isn't a multiple of 4, pad with =
size_t num_pad_chars((4 - auth.size() % 4) % 4);
auth.append(num_pad_chars, '=');
size_t pad_chars(std::count(auth.begin(), auth.end(), '='));
std::replace(auth.begin(), auth.end(), '=', 'A');
std::string output(ItBinaryT(auth.begin()), ItBinaryT(auth.end()));
output.erase(output.end() - pad_chars, output.end());
credentials = output;
} catch (std::exception const&) {
credentials = std::string("");
}
size_t pos = credentials.find(':');
usr = credentials.substr(0, pos);
pwd = credentials.substr(pos+1, credentials.length());
//Check credentials
userAttributes_t userData;
try {
userData = _users.at(usr);
} catch (const std::out_of_range& e) {
HLOG(warning) << "User does not exist: " << usr;
response.set_status_code(status_codes::Unauthorized);
output = "Unauthorized access!\n";
response.set_body(output);
msg.reply(response);
return false;
}
if (pwd != userData.first) {
HLOG(warning) << "Invalid password provided: " << usr << ":" << pwd;
response.set_status_code(status_codes::Unauthorized);
output = "Unauthorized access!\n";
response.set_body(output);
msg.reply(response);
return false;
}
try {
if (!userData.second.test(perm)) {
HLOG(warning) << "User " << usr << " has insufficient permissions";
response.set_status_code(status_codes::Forbidden);
output = "Insufficient permissions!\n";
response.set_body(output);
msg.reply(response);
return false;
}
} catch (const std::out_of_range& e) {
HLOG(error) << "Internal error: permission out of range";
response.set_status_code(status_codes::InternalError);
output = "Sorry, this should not have happened...\n";
response.set_body(output);
msg.reply(response);
return false;
}
return true;
}
<
/*
* RestAPIServer.cpp
*
* Created on: 22.03.2019
* Author: Micha Mueller, Alessio Netti
*/
#include "RestAPIServer.h"
using namespace web::http;
#define RLOG(sev) LOG(sev) << "RestAPIServer: "
void RestAPIServer::handle_get(http_request& msg) {
http_response response;
status_code status = status_codes::InternalError;
std::string output = "";
std::vector<std::string> pathParts;
std::vector<std::pair<std::string, std::string>> queries;
bool json = false;
std::string uri = msg.relative_uri().to_string();
HttpsServer::splitUri(uri, pathParts, queries);
if (pathParts.size() < 1) {
RLOG(warning) << "Received malformed request: No first path part";
status = status_codes::BadRequest;
output = "No first path part recognized\n";
goto sendResponse;
}
//json formatted response expected?
for (auto& p : queries) {
if (p.first == "json") {
if (stoi(p.second) > 0) {
json = true;
}
}
}
//Managing REST GET commands to the data analytics framework
if(pathParts[0] == "analytics") {
try {
restResponse_t reply = _manager->REST(pathParts, queries, "GET", _io);
output = reply.data;
output += reply.response;
status = status_codes::OK;
} catch(const std::invalid_argument &e) {
RLOG(warning) << e.what();
status = status_codes::BadRequest;
} catch(const std::domain_error &e) {
output = e.what();
status = status_codes::NotFound;
} catch(const std::exception &e) {
RLOG(warning) << e.what();
status = status_codes::InternalError;
}
} else if (pathParts[0] == "plugins") {
std::ostringstream data;
if (json) {
boost::property_tree::ptree root, plugins;
for(auto& p : _plugins) {
plugins.put(p.id, "");