mirror of
https://github.com/Telecominfraproject/wlan-cloud-owprov.git
synced 2025-10-30 02:02:36 +00:00
Initial
This commit is contained in:
82
CMakeLists.txt
Normal file
82
CMakeLists.txt
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
project(ucentraltopo VERSION 0.1.0)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
if(UNIX AND APPLE)
|
||||||
|
set(OPENSSL_ROOT_DIR /usr/local/opt/openssl)
|
||||||
|
set(MYSQL_ROOT_DIR /usr/local/opt/mysql-client)
|
||||||
|
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
|
set(PostgreSQL_TYPE_INCLUDE_DIR /usr/include/postgresql)
|
||||||
|
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build)
|
||||||
|
file(READ build BUILD_NUM)
|
||||||
|
if(BUILD_INCREMENT)
|
||||||
|
MATH(EXPR BUILD_NUM "${BUILD_NUM}+1")
|
||||||
|
file(WRITE build ${BUILD_NUM})
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
set(BUILD_NUM 1)
|
||||||
|
file(WRITE build ${BUILD_NUM})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(BUILD_SHARED_LIBS 1)
|
||||||
|
add_definitions(-DAPP_VERSION="${CMAKE_PROJECT_VERSION}" -DBUILD_NUMBER="${BUILD_NUM}" -DAWS_CUSTOM_MEMORY_MANAGEMENT)
|
||||||
|
|
||||||
|
set(Boost_USE_STATIC_LIBS OFF)
|
||||||
|
set(Boost_USE_MULTITHREADED ON)
|
||||||
|
set(Boost_USE_STATIC_RUNTIME OFF)
|
||||||
|
|
||||||
|
find_package(Boost REQUIRED system)
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
find_package(AWSSDK REQUIRED COMPONENTS s3)
|
||||||
|
find_package(Poco REQUIRED COMPONENTS Crypto JWT Net Util NetSSL Data DataSQLite)
|
||||||
|
|
||||||
|
if(SMALL_BUILD)
|
||||||
|
find_package(Poco REQUIRED COMPONENTS Crypto JWT Net Util NetSSL Data DataSQLite)
|
||||||
|
else()
|
||||||
|
find_package(CppKafka REQUIRED)
|
||||||
|
find_package(PostgreSQL REQUIRED)
|
||||||
|
find_package(MySQL REQUIRED)
|
||||||
|
find_package(Poco REQUIRED COMPONENTS JSON Crypto JWT Net Util NetSSL Data DataSQLite DataPostgreSQL DataMySQL)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include_directories(/usr/local/include /usr/local/opt/openssl/include src include/kafka /usr/local/opt/mysql-client/include)
|
||||||
|
|
||||||
|
add_executable( ucentraltopo
|
||||||
|
build
|
||||||
|
src/Daemon.cpp src/Daemon.h
|
||||||
|
src/Dashboard.h src/Dashboard.cpp
|
||||||
|
src/MicroService.cpp src/MicroService.h
|
||||||
|
src/Utils.h src/Utils.cpp
|
||||||
|
src/RESTAPI_utils.cpp src/RESTAPI_utils.h
|
||||||
|
src/RESTAPI_SecurityObjects.h src/RESTAPI_SecurityObjects.h
|
||||||
|
src/RESTAPI_TopoObjects.cpp src/RESTAPI_TopoObjects.h
|
||||||
|
src/ALBHealthCheckServer.h
|
||||||
|
src/AuthClient.cpp src/AuthClient.h
|
||||||
|
src/KafkaManager.cpp src/KafkaManager.h
|
||||||
|
src/Kafka_topics.h
|
||||||
|
src/OpenAPIRequest.cpp src/OpenAPIRequest.h
|
||||||
|
src/RESTAPI_SecurityObjects.h src/RESTAPI_SecurityObjects.cpp
|
||||||
|
src/RESTAPI_handler.cpp src/RESTAPI_handler.h
|
||||||
|
src/RESTAPI_server.h src/RESTAPI_server.cpp
|
||||||
|
src/RESTAPI_system_command.h src/RESTAPI_system_command.cpp
|
||||||
|
src/RESTAPI_InternalServer.h src/RESTAPI_InternalServer.cpp
|
||||||
|
src/StorageService.cpp src/StorageService.h
|
||||||
|
src/SubSystemServer.cpp src/SubSystemServer.h
|
||||||
|
src/storage_pgql.cpp src/storage_mysql.cpp src/storage_sqlite.cpp
|
||||||
|
src/storage_tables.cpp
|
||||||
|
src/uCentralTypes.h
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(ucentraltopo PUBLIC
|
||||||
|
${Poco_LIBRARIES} ${MySQL_LIBRARIES}
|
||||||
|
${Boost_LIBRARIES}
|
||||||
|
${ZLIB_LIBRARIES} ${AWSSDK_LINK_LIBRARIES}
|
||||||
|
CppKafka::cppkafka )
|
||||||
|
|
||||||
4
set_env.sh
Executable file
4
set_env.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
export UCENTRALPROV_CONFIG=`pwd`
|
||||||
|
export UCENTRALPROV_ROOT=`pwd`
|
||||||
114
src/ALBHealthCheckServer.h
Normal file
114
src/ALBHealthCheckServer.h
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-06-04.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_ALBHEALTHCHECKSERVER_H
|
||||||
|
#define UCENTRALGW_ALBHEALTHCHECKSERVER_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "Poco/Thread.h"
|
||||||
|
#include "Poco/Net/HTTPServer.h"
|
||||||
|
#include "Poco/Net/HTTPServerRequest.h"
|
||||||
|
#include "Poco/Net/HTTPServerResponse.h"
|
||||||
|
#include "Poco/Net/HTTPRequestHandler.h"
|
||||||
|
#include "Poco/Logger.h"
|
||||||
|
|
||||||
|
#include "Daemon.h"
|
||||||
|
#include "SubSystemServer.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class ALBRequestHandler: public Poco::Net::HTTPRequestHandler
|
||||||
|
/// Return a HTML document with the current date and time.
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ALBRequestHandler(Poco::Logger & L)
|
||||||
|
: Logger_(L)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleRequest(Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response)
|
||||||
|
{
|
||||||
|
Logger_.information(Poco::format("ALB-REQUEST(%s): New ALB request.",Request.clientAddress().toString()));
|
||||||
|
Response.setChunkedTransferEncoding(true);
|
||||||
|
Response.setContentType("text/html");
|
||||||
|
Response.setDate(Poco::Timestamp());
|
||||||
|
Response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
|
||||||
|
Response.setKeepAlive(true);
|
||||||
|
Response.set("Connection","keep-alive");
|
||||||
|
Response.setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
|
||||||
|
std::ostream &Answer = Response.send();
|
||||||
|
Answer << "uCentralGW Alive and kicking!" ;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Poco::Logger & Logger_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ALBRequestHandlerFactory: public Poco::Net::HTTPRequestHandlerFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit ALBRequestHandlerFactory(Poco::Logger & L):
|
||||||
|
Logger_(L)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ALBRequestHandler* createRequestHandler(const Poco::Net::HTTPServerRequest& request) override
|
||||||
|
{
|
||||||
|
if (request.getURI() == "/")
|
||||||
|
return new ALBRequestHandler(Logger_);
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Poco::Logger &Logger_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ALBHealthCheckServer : public SubSystemServer {
|
||||||
|
public:
|
||||||
|
ALBHealthCheckServer() noexcept:
|
||||||
|
SubSystemServer("ALBHealthCheckServer", "ALB-SVR", "alb")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static ALBHealthCheckServer *instance() {
|
||||||
|
if (instance_ == nullptr) {
|
||||||
|
instance_ = new ALBHealthCheckServer;
|
||||||
|
}
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Start() {
|
||||||
|
if(Daemon()->ConfigGetBool("alb.enable",false)) {
|
||||||
|
Port_ = (int)Daemon()->ConfigGetInt("alb.port",15015);
|
||||||
|
Socket_ = std::make_unique<Poco::Net::ServerSocket>(Port_);
|
||||||
|
auto Params = new Poco::Net::HTTPServerParams;
|
||||||
|
Server_ = std::make_unique<Poco::Net::HTTPServer>(new ALBRequestHandlerFactory(Logger_), *Socket_, Params);
|
||||||
|
Server_->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stop() {
|
||||||
|
if(Server_)
|
||||||
|
Server_->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static ALBHealthCheckServer *instance_;
|
||||||
|
std::unique_ptr<Poco::Net::HTTPServer> Server_;
|
||||||
|
std::unique_ptr<Poco::Net::ServerSocket> Socket_;
|
||||||
|
int Port_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ALBHealthCheckServer * ALBHealthCheckServer() { return ALBHealthCheckServer::instance(); }
|
||||||
|
inline class ALBHealthCheckServer * ALBHealthCheckServer::instance_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // UCENTRALGW_ALBHEALTHCHECKSERVER_H
|
||||||
59
src/AuthClient.cpp
Normal file
59
src/AuthClient.cpp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-06-30.
|
||||||
|
//
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "AuthClient.h"
|
||||||
|
#include "RESTAPI_SecurityObjects.h"
|
||||||
|
#include "Daemon.h"
|
||||||
|
#include "OpenAPIRequest.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
class AuthClient * AuthClient::instance_ = nullptr;
|
||||||
|
|
||||||
|
int AuthClient::Start() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthClient::Stop() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthClient::RemovedCachedToken(const std::string &Token) {
|
||||||
|
SubMutexGuard G(Mutex_);
|
||||||
|
UserCache_.erase(Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsTokenExpired(const SecurityObjects::WebToken &T) {
|
||||||
|
return ((T.expires_in_+T.created_)<std::time(nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AuthClient::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo ) {
|
||||||
|
SubMutexGuard G(Mutex_);
|
||||||
|
|
||||||
|
auto User = UserCache_.find(SessionToken);
|
||||||
|
if(User != UserCache_.end() && !IsTokenExpired(User->second.webtoken)) {
|
||||||
|
UInfo = User->second;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Types::StringPairVec QueryData;
|
||||||
|
QueryData.push_back(std::make_pair("token",SessionToken));
|
||||||
|
OpenAPIRequestGet Req(uSERVICE_SECURITY,
|
||||||
|
"/api/v1/validateToken",
|
||||||
|
QueryData,
|
||||||
|
5000);
|
||||||
|
Poco::JSON::Object::Ptr Response;
|
||||||
|
if(Req.Do(Response)==Poco::Net::HTTPResponse::HTTP_OK) {
|
||||||
|
if(Response->has("tokenInfo") && Response->has("userInfo")) {
|
||||||
|
SecurityObjects::UserInfoAndPolicy P;
|
||||||
|
P.from_json(Response);
|
||||||
|
UserCache_[SessionToken] = P;
|
||||||
|
UInfo = P;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/AuthClient.h
Normal file
45
src/AuthClient.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-06-30.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_AUTHCLIENT_H
|
||||||
|
#define UCENTRALGW_AUTHCLIENT_H
|
||||||
|
|
||||||
|
#include "Poco/JSON/Object.h"
|
||||||
|
#include "Poco/Net/HTTPServerRequest.h"
|
||||||
|
#include "Poco/Net/HTTPServerResponse.h"
|
||||||
|
#include "Poco/JWT/Signer.h"
|
||||||
|
#include "Poco/SHA2Engine.h"
|
||||||
|
#include "RESTAPI_SecurityObjects.h"
|
||||||
|
#include "SubSystemServer.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class AuthClient : public SubSystemServer {
|
||||||
|
public:
|
||||||
|
explicit AuthClient() noexcept:
|
||||||
|
SubSystemServer("Authentication", "AUTH-CLNT", "authentication")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static AuthClient *instance() {
|
||||||
|
if (instance_ == nullptr) {
|
||||||
|
instance_ = new AuthClient;
|
||||||
|
}
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Start() override;
|
||||||
|
void Stop() override;
|
||||||
|
bool IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo );
|
||||||
|
void RemovedCachedToken(const std::string &Token);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static AuthClient *instance_;
|
||||||
|
SecurityObjects::UserInfoCache UserCache_;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline AuthClient * AuthClient() { return AuthClient::instance(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // UCENTRALGW_AUTHCLIENT_H
|
||||||
61
src/Daemon.cpp
Normal file
61
src/Daemon.cpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
#include "Poco/Util/Application.h"
|
||||||
|
#include "Poco/Util/Option.h"
|
||||||
|
#include "Poco/Environment.h"
|
||||||
|
|
||||||
|
#include "Daemon.h"
|
||||||
|
|
||||||
|
#include "StorageService.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "AuthClient.h"
|
||||||
|
#include "RESTAPI_server.h"
|
||||||
|
#include "RESTAPI_InternalServer.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
class Daemon *Daemon::instance_ = nullptr;
|
||||||
|
|
||||||
|
class Daemon *Daemon::instance() {
|
||||||
|
if (instance_ == nullptr) {
|
||||||
|
instance_ = new Daemon(vDAEMON_PROPERTIES_FILENAME,
|
||||||
|
vDAEMON_ROOT_ENV_VAR,
|
||||||
|
vDAEMON_CONFIG_ENV_VAR,
|
||||||
|
vDAEMON_APP_NAME,
|
||||||
|
vDAEMON_BUS_TIMER,
|
||||||
|
Types::SubSystemVec{
|
||||||
|
Storage(),
|
||||||
|
AuthClient(),
|
||||||
|
RESTAPI_server(),
|
||||||
|
RESTAPI_InternalServer()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Daemon::initialize(Poco::Util::Application &self) {
|
||||||
|
MicroService::initialize(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
try {
|
||||||
|
auto App = uCentral::Daemon::instance();
|
||||||
|
auto ExitCode = App->run(argc, argv);
|
||||||
|
delete App;
|
||||||
|
|
||||||
|
return ExitCode;
|
||||||
|
|
||||||
|
} catch (Poco::Exception &exc) {
|
||||||
|
std::cerr << exc.displayText() << std::endl;
|
||||||
|
return Poco::Util::Application::EXIT_SOFTWARE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// end of namespace
|
||||||
62
src/Daemon.h
Normal file
62
src/Daemon.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRAL_UCENTRAL_H
|
||||||
|
#define UCENTRAL_UCENTRAL_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "Poco/Util/Application.h"
|
||||||
|
#include "Poco/Util/ServerApplication.h"
|
||||||
|
#include "Poco/Util/Option.h"
|
||||||
|
#include "Poco/Util/OptionSet.h"
|
||||||
|
#include "Poco/UUIDGenerator.h"
|
||||||
|
#include "Poco/ErrorHandler.h"
|
||||||
|
#include "Poco/Crypto/RSAKey.h"
|
||||||
|
#include "Poco/Crypto/CipherFactory.h"
|
||||||
|
#include "Poco/Crypto/Cipher.h"
|
||||||
|
|
||||||
|
#include "Dashboard.h"
|
||||||
|
#include "MicroService.h"
|
||||||
|
#include "uCentralTypes.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
static const char * vDAEMON_PROPERTIES_FILENAME = "ucentraltopo.properties";
|
||||||
|
static const char * vDAEMON_ROOT_ENV_VAR = "UCENTRALTOPO_ROOT";
|
||||||
|
static const char * vDAEMON_CONFIG_ENV_VAR = "UCENTRALTOPO_CONFIG";
|
||||||
|
static const char * vDAEMON_APP_NAME = uSERVICE_TOPOLOGY.c_str();
|
||||||
|
static const uint64_t vDAEMON_BUS_TIMER = 10000;
|
||||||
|
|
||||||
|
class Daemon : public MicroService {
|
||||||
|
public:
|
||||||
|
explicit Daemon(std::string PropFile,
|
||||||
|
std::string RootEnv,
|
||||||
|
std::string ConfigEnv,
|
||||||
|
std::string AppName,
|
||||||
|
uint64_t BusTimer,
|
||||||
|
Types::SubSystemVec SubSystems) :
|
||||||
|
MicroService( PropFile, RootEnv, ConfigEnv, AppName, BusTimer, SubSystems) {};
|
||||||
|
|
||||||
|
void initialize(Poco::Util::Application &self);
|
||||||
|
static Daemon *instance();
|
||||||
|
inline TopoDashboard & GetDashboard() { return DB_; }
|
||||||
|
private:
|
||||||
|
static Daemon *instance_;
|
||||||
|
TopoDashboard DB_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Daemon * Daemon() { return Daemon::instance(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //UCENTRAL_UCENTRAL_H
|
||||||
17
src/Dashboard.cpp
Normal file
17
src/Dashboard.cpp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-07-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Dashboard.h"
|
||||||
|
#include "StorageService.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
void TopoDashboard::Create() {
|
||||||
|
uint64_t Now = std::time(nullptr);
|
||||||
|
if(LastRun_==0 || (Now-LastRun_)>120) {
|
||||||
|
DB_.reset();
|
||||||
|
// Todo: call dashboard creation code.
|
||||||
|
LastRun_ = Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Dashboard.h
Normal file
23
src/Dashboard.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-07-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_DASHBOARD_H
|
||||||
|
#define UCENTRALGW_DASHBOARD_H
|
||||||
|
|
||||||
|
#include "uCentralTypes.h"
|
||||||
|
#include "RESTAPI_TopoObjects.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
class TopoDashboard {
|
||||||
|
public:
|
||||||
|
void Create();
|
||||||
|
const TopoObjects::Report & Report() const { return DB_;}
|
||||||
|
inline void Reset() { LastRun_=0; DB_.reset(); }
|
||||||
|
private:
|
||||||
|
TopoObjects::Report DB_;
|
||||||
|
uint64_t LastRun_=0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // UCENTRALGW_DASHBOARD_H
|
||||||
221
src/KafkaManager.cpp
Normal file
221
src/KafkaManager.cpp
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "KafkaManager.h"
|
||||||
|
|
||||||
|
#include "Daemon.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class KafkaManager *KafkaManager::instance_ = nullptr;
|
||||||
|
|
||||||
|
KafkaManager::KafkaManager() noexcept:
|
||||||
|
SubSystemServer("KafkaManager", "KAFKA-SVR", "ucentral.kafka")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void KafkaManager::initialize(Poco::Util::Application & self) {
|
||||||
|
SubSystemServer::initialize(self);
|
||||||
|
KafkaEnabled_ = Daemon()->ConfigGetBool("ucentral.kafka.enable",false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SMALL_BUILD
|
||||||
|
|
||||||
|
int KafkaManager::Start() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void KafkaManager::Stop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
int KafkaManager::Start() {
|
||||||
|
if(!KafkaEnabled_)
|
||||||
|
return 0;
|
||||||
|
ProducerThr_ = std::make_unique<std::thread>([this]() { this->ProducerThr(); });
|
||||||
|
ConsumerThr_ = std::make_unique<std::thread>([this]() { this->ConsumerThr(); });
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KafkaManager::Stop() {
|
||||||
|
if(KafkaEnabled_) {
|
||||||
|
ProducerRunning_ = ConsumerRunning_ = false;
|
||||||
|
ProducerThr_->join();
|
||||||
|
ConsumerThr_->join();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KafkaManager::ProducerThr() {
|
||||||
|
cppkafka::Configuration Config({
|
||||||
|
{ "client.id", Daemon()->ConfigGetString("ucentral.kafka.client.id") },
|
||||||
|
{ "metadata.broker.list", Daemon()->ConfigGetString("ucentral.kafka.brokerlist") }
|
||||||
|
});
|
||||||
|
SystemInfoWrapper_ = R"lit({ "system" : { "id" : )lit" +
|
||||||
|
std::to_string(Daemon()->ID()) +
|
||||||
|
R"lit( , "host" : ")lit" + Daemon()->PrivateEndPoint() +
|
||||||
|
R"lit(" } , "payload" : )lit" ;
|
||||||
|
cppkafka::Producer Producer(Config);
|
||||||
|
ProducerRunning_ = true;
|
||||||
|
while(ProducerRunning_) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SubMutexGuard G(ProducerMutex_);
|
||||||
|
auto Num=0;
|
||||||
|
while (!Queue_.empty()) {
|
||||||
|
const auto M = Queue_.front();
|
||||||
|
Producer.produce(
|
||||||
|
cppkafka::MessageBuilder(M.Topic).key(M.Key).payload(M.PayLoad));
|
||||||
|
Queue_.pop();
|
||||||
|
Num++;
|
||||||
|
}
|
||||||
|
if(Num)
|
||||||
|
Producer.flush();
|
||||||
|
} catch (const cppkafka::HandleException &E ) {
|
||||||
|
Logger_.warning(Poco::format("Caught a Kafka exception (producer): %s",std::string{E.what()}));
|
||||||
|
} catch (const Poco::Exception &E) {
|
||||||
|
Logger_.log(E);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KafkaManager::PartitionAssignment(const cppkafka::TopicPartitionList& partitions) {
|
||||||
|
Logger_.information(Poco::format("Partition assigned: %Lu...",(uint64_t )partitions.front().get_partition()));
|
||||||
|
}
|
||||||
|
void KafkaManager::PartitionRevocation(const cppkafka::TopicPartitionList& partitions) {
|
||||||
|
Logger_.information(Poco::format("Partition revocation: %Lu...",(uint64_t )partitions.front().get_partition()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void KafkaManager::ConsumerThr() {
|
||||||
|
cppkafka::Configuration Config({
|
||||||
|
{ "client.id", Daemon()->ConfigGetString("ucentral.kafka.client.id") },
|
||||||
|
{ "metadata.broker.list", Daemon()->ConfigGetString("ucentral.kafka.brokerlist") },
|
||||||
|
{ "group.id", Daemon()->ConfigGetString("ucentral.kafka.group.id") },
|
||||||
|
{ "enable.auto.commit", Daemon()->ConfigGetBool("ucentral.kafka.auto.commit",false) },
|
||||||
|
{ "auto.offset.reset", "latest" } ,
|
||||||
|
{ "enable.partition.eof", false }
|
||||||
|
});
|
||||||
|
|
||||||
|
cppkafka::TopicConfiguration topic_config = {
|
||||||
|
{ "auto.offset.reset", "smallest" }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now configure it to be the default topic config
|
||||||
|
Config.set_default_topic_configuration(topic_config);
|
||||||
|
|
||||||
|
cppkafka::Consumer Consumer(Config);
|
||||||
|
Consumer.set_assignment_callback([this](cppkafka::TopicPartitionList& partitions) {
|
||||||
|
if(!partitions.empty()) {
|
||||||
|
Logger_.information(Poco::format("Partition assigned: %Lu...",
|
||||||
|
(uint64_t)partitions.front().get_partition()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Consumer.set_revocation_callback([this](const cppkafka::TopicPartitionList& partitions) {
|
||||||
|
if(!partitions.empty()) {
|
||||||
|
Logger_.information(Poco::format("Partition revocation: %Lu...",
|
||||||
|
(uint64_t)partitions.front().get_partition()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bool AutoCommit = Daemon()->ConfigGetBool("ucentral.kafka.auto.commit",false);
|
||||||
|
auto BatchSize = Daemon()->ConfigGetInt("ucentral.kafka.consumer.batchsize",20);
|
||||||
|
|
||||||
|
Types::StringVec Topics;
|
||||||
|
for(const auto &i:Notifiers_)
|
||||||
|
Topics.push_back(i.first);
|
||||||
|
|
||||||
|
Consumer.subscribe(Topics);
|
||||||
|
|
||||||
|
ConsumerRunning_ = true;
|
||||||
|
while(ConsumerRunning_) {
|
||||||
|
try {
|
||||||
|
std::vector<cppkafka::Message> MsgVec = Consumer.poll_batch(BatchSize, std::chrono::milliseconds(200));
|
||||||
|
for(auto const &Msg:MsgVec) {
|
||||||
|
if (!Msg)
|
||||||
|
continue;
|
||||||
|
if (Msg.get_error()) {
|
||||||
|
if (!Msg.is_eof()) {
|
||||||
|
Logger_.error(Poco::format("Error: %s", Msg.get_error().to_string()));
|
||||||
|
}if(!AutoCommit)
|
||||||
|
Consumer.async_commit(Msg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SubMutexGuard G(ConsumerMutex_);
|
||||||
|
auto It = Notifiers_.find(Msg.get_topic());
|
||||||
|
if (It != Notifiers_.end()) {
|
||||||
|
Types::TopicNotifyFunctionList &FL = It->second;
|
||||||
|
std::string Key{Msg.get_key()};
|
||||||
|
std::string Payload{Msg.get_payload()};
|
||||||
|
for (auto &F : FL) {
|
||||||
|
std::thread T(F.first, Key, Payload);
|
||||||
|
T.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!AutoCommit)
|
||||||
|
Consumer.async_commit(Msg);
|
||||||
|
}
|
||||||
|
} catch (const cppkafka::HandleException &E) {
|
||||||
|
Logger_.warning(Poco::format("Caught a Kafka exception (consumer): %s",std::string{E.what()}));
|
||||||
|
} catch (const Poco::Exception &E) {
|
||||||
|
Logger_.log(E);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string KafkaManager::WrapSystemId(const std::string & PayLoad) {
|
||||||
|
return std::move( SystemInfoWrapper_ + PayLoad + "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void KafkaManager::PostMessage(std::string topic, std::string key, std::string PayLoad, bool WrapMessage ) {
|
||||||
|
if(KafkaEnabled_) {
|
||||||
|
SubMutexGuard G(Mutex_);
|
||||||
|
KMessage M{
|
||||||
|
.Topic = topic,
|
||||||
|
.Key = key,
|
||||||
|
.PayLoad = WrapMessage ? WrapSystemId(PayLoad) : PayLoad };
|
||||||
|
Queue_.push(M);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int KafkaManager::RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F) {
|
||||||
|
if(KafkaEnabled_) {
|
||||||
|
SubMutexGuard G(Mutex_);
|
||||||
|
auto It = Notifiers_.find(Topic);
|
||||||
|
if(It == Notifiers_.end()) {
|
||||||
|
Types::TopicNotifyFunctionList L;
|
||||||
|
L.emplace(L.end(),std::make_pair(F,FunctionId_));
|
||||||
|
Notifiers_[Topic] = std::move(L);
|
||||||
|
} else {
|
||||||
|
It->second.emplace(It->second.end(),std::make_pair(F,FunctionId_));
|
||||||
|
}
|
||||||
|
return FunctionId_++;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KafkaManager::UnregisterTopicWatcher(const std::string &Topic, int Id) {
|
||||||
|
if(KafkaEnabled_) {
|
||||||
|
SubMutexGuard G(Mutex_);
|
||||||
|
auto It = Notifiers_.find(Topic);
|
||||||
|
if(It != Notifiers_.end()) {
|
||||||
|
Types::TopicNotifyFunctionList & L = It->second;
|
||||||
|
for(auto it=L.begin(); it!=L.end(); it++)
|
||||||
|
if(it->second == Id) {
|
||||||
|
L.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
} // namespace
|
||||||
74
src/KafkaManager.h
Normal file
74
src/KafkaManager.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_KAFKAMANAGER_H
|
||||||
|
#define UCENTRALGW_KAFKAMANAGER_H
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "SubSystemServer.h"
|
||||||
|
#include "uCentralTypes.h"
|
||||||
|
|
||||||
|
#include "cppkafka/cppkafka.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class KafkaManager : public SubSystemServer {
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct KMessage {
|
||||||
|
std::string Topic,
|
||||||
|
Key,
|
||||||
|
PayLoad;
|
||||||
|
};
|
||||||
|
|
||||||
|
void initialize(Poco::Util::Application & self) override;
|
||||||
|
static KafkaManager *instance() {
|
||||||
|
if(instance_== nullptr)
|
||||||
|
instance_ = new KafkaManager;
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProducerThr();
|
||||||
|
void ConsumerThr();
|
||||||
|
|
||||||
|
int Start() override;
|
||||||
|
void Stop() override;
|
||||||
|
|
||||||
|
void PostMessage(std::string topic, std::string key, std::string payload, bool WrapMessage = true);
|
||||||
|
[[nodiscard]] std::string WrapSystemId(const std::string & PayLoad);
|
||||||
|
[[nodiscard]] bool Enabled() { return KafkaEnabled_; }
|
||||||
|
int RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction & F);
|
||||||
|
void UnregisterTopicWatcher(const std::string &Topic, int FunctionId);
|
||||||
|
void WakeUp();
|
||||||
|
void PartitionAssignment(const cppkafka::TopicPartitionList& partitions);
|
||||||
|
void PartitionRevocation(const cppkafka::TopicPartitionList& partitions);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static KafkaManager *instance_;
|
||||||
|
SubMutex ProducerMutex_;
|
||||||
|
SubMutex ConsumerMutex_;
|
||||||
|
bool KafkaEnabled_ = false;
|
||||||
|
std::atomic_bool ProducerRunning_ = false;
|
||||||
|
std::atomic_bool ConsumerRunning_ = false;
|
||||||
|
std::queue<KMessage> Queue_;
|
||||||
|
std::string SystemInfoWrapper_;
|
||||||
|
std::unique_ptr<std::thread> ConsumerThr_;
|
||||||
|
std::unique_ptr<std::thread> ProducerThr_;
|
||||||
|
int FunctionId_=1;
|
||||||
|
Types::NotifyTable Notifiers_;
|
||||||
|
std::unique_ptr<cppkafka::Configuration> Config_;
|
||||||
|
|
||||||
|
KafkaManager() noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline KafkaManager * KafkaManager() { return KafkaManager::instance(); }
|
||||||
|
} // NameSpace
|
||||||
|
|
||||||
|
#endif // UCENTRALGW_KAFKAMANAGER_H
|
||||||
36
src/Kafka_topics.h
Normal file
36
src/Kafka_topics.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-06-07.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_KAFKA_TOPICS_H
|
||||||
|
#define UCENTRALGW_KAFKA_TOPICS_H
|
||||||
|
|
||||||
|
namespace uCentral::KafkaTopics {
|
||||||
|
static const std::string HEALTHCHECK{"healthcheck"};
|
||||||
|
static const std::string STATE{"state"};
|
||||||
|
static const std::string CONNECTION{"connection"};
|
||||||
|
static const std::string WIFISCAN{"wifiscan"};
|
||||||
|
static const std::string ALERTS{"alerts"};
|
||||||
|
static const std::string COMMAND{"command"};
|
||||||
|
static const std::string SERVICE_EVENTS{"service_events"};
|
||||||
|
|
||||||
|
namespace ServiceEvents {
|
||||||
|
static const std::string EVENT_JOIN{"join"};
|
||||||
|
static const std::string EVENT_LEAVE{"leave"};
|
||||||
|
static const std::string EVENT_KEEP_ALIVE{"keep-alive"};
|
||||||
|
static const std::string EVENT_REMOVE_TOKEN{"remove-token"};
|
||||||
|
|
||||||
|
namespace Fields {
|
||||||
|
static const std::string EVENT{"event"};
|
||||||
|
static const std::string ID{"id"};
|
||||||
|
static const std::string TYPE{"type"};
|
||||||
|
static const std::string PUBLIC{"publicEndPoint"};
|
||||||
|
static const std::string PRIVATE{"privateEndPoint"};
|
||||||
|
static const std::string KEY{"key"};
|
||||||
|
static const std::string VRSN{"version"};
|
||||||
|
static const std::string TOKEN{"token"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // UCENTRALGW_KAFKA_TOPICS_H
|
||||||
491
src/MicroService.cpp
Normal file
491
src/MicroService.cpp
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-06-22.
|
||||||
|
//
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
|
#include "Poco/Util/Application.h"
|
||||||
|
#include "Poco/Util/ServerApplication.h"
|
||||||
|
#include "Poco/Util/Option.h"
|
||||||
|
#include "Poco/Util/OptionSet.h"
|
||||||
|
#include "Poco/Util/HelpFormatter.h"
|
||||||
|
#include "Poco/Environment.h"
|
||||||
|
#include "Poco/Net/HTTPSStreamFactory.h"
|
||||||
|
#include "Poco/Net/HTTPStreamFactory.h"
|
||||||
|
#include "Poco/Net/FTPSStreamFactory.h"
|
||||||
|
#include "Poco/Net/FTPStreamFactory.h"
|
||||||
|
#include "Poco/Path.h"
|
||||||
|
#include "Poco/File.h"
|
||||||
|
#include "Poco/String.h"
|
||||||
|
#include "Poco/JSON/Object.h"
|
||||||
|
#include "Poco/JSON/Parser.h"
|
||||||
|
#include "Poco/JSON/Stringifier.h"
|
||||||
|
|
||||||
|
#include "ALBHealthCheckServer.h"
|
||||||
|
#ifndef SMALL_BUILD
|
||||||
|
#include "KafkaManager.h"
|
||||||
|
#endif
|
||||||
|
#include "Kafka_topics.h"
|
||||||
|
|
||||||
|
#include "MicroService.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#ifndef TIP_SECURITY_SERVICE
|
||||||
|
#include "AuthClient.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
void MyErrorHandler::exception(const Poco::Exception & E) {
|
||||||
|
Poco::Thread * CurrentThread = Poco::Thread::current();
|
||||||
|
App_.logger().log(E);
|
||||||
|
App_.logger().error(Poco::format("Exception occurred in %s",CurrentThread->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyErrorHandler::exception(const std::exception & E) {
|
||||||
|
Poco::Thread * CurrentThread = Poco::Thread::current();
|
||||||
|
App_.logger().warning(Poco::format("std::exception on %s",CurrentThread->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyErrorHandler::exception() {
|
||||||
|
Poco::Thread * CurrentThread = Poco::Thread::current();
|
||||||
|
App_.logger().warning(Poco::format("exception on %s",CurrentThread->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::Exit(int Reason) {
|
||||||
|
std::exit(Reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::BusMessageReceived(const std::string &Key, const std::string & Message) {
|
||||||
|
SubMutexGuard G(InfraMutex_);
|
||||||
|
try {
|
||||||
|
Poco::JSON::Parser P;
|
||||||
|
auto Object = P.parse(Message).extract<Poco::JSON::Object::Ptr>();
|
||||||
|
if (Object->has(KafkaTopics::ServiceEvents::Fields::ID) &&
|
||||||
|
Object->has(KafkaTopics::ServiceEvents::Fields::EVENT)) {
|
||||||
|
uint64_t ID = Object->get(KafkaTopics::ServiceEvents::Fields::ID);
|
||||||
|
auto Event = Object->get(KafkaTopics::ServiceEvents::Fields::EVENT).toString();
|
||||||
|
if (ID != ID_) {
|
||||||
|
if( Event==KafkaTopics::ServiceEvents::EVENT_JOIN ||
|
||||||
|
Event==KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE ||
|
||||||
|
Event==KafkaTopics::ServiceEvents::EVENT_LEAVE ) {
|
||||||
|
if( Object->has(KafkaTopics::ServiceEvents::Fields::TYPE) &&
|
||||||
|
Object->has(KafkaTopics::ServiceEvents::Fields::PUBLIC) &&
|
||||||
|
Object->has(KafkaTopics::ServiceEvents::Fields::PRIVATE) &&
|
||||||
|
Object->has(KafkaTopics::ServiceEvents::Fields::VRSN) &&
|
||||||
|
Object->has(KafkaTopics::ServiceEvents::Fields::KEY)) {
|
||||||
|
|
||||||
|
if (Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE && Services_.find(ID) != Services_.end()) {
|
||||||
|
Services_[ID].LastUpdate = std::time(nullptr);
|
||||||
|
} else if (Event == KafkaTopics::ServiceEvents::EVENT_LEAVE) {
|
||||||
|
Services_.erase(ID);
|
||||||
|
logger().information(Poco::format("Service %s ID=%Lu leaving system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID));
|
||||||
|
} else if (Event == KafkaTopics::ServiceEvents::EVENT_JOIN || Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE) {
|
||||||
|
logger().information(Poco::format("Service %s ID=%Lu joining system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID));
|
||||||
|
Services_[ID] = MicroServiceMeta{
|
||||||
|
.Id = ID,
|
||||||
|
.Type = Poco::toLower(Object->get(KafkaTopics::ServiceEvents::Fields::TYPE).toString()),
|
||||||
|
.PrivateEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),
|
||||||
|
.PublicEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PUBLIC).toString(),
|
||||||
|
.AccessKey = Object->get(KafkaTopics::ServiceEvents::Fields::KEY).toString(),
|
||||||
|
.Version = Object->get(KafkaTopics::ServiceEvents::Fields::VRSN).toString(),
|
||||||
|
.LastUpdate = (uint64_t)std::time(nullptr)};
|
||||||
|
for (const auto &[Id, Svc] : Services_) {
|
||||||
|
logger().information(Poco::format("ID: %Lu Type: %s EndPoint: %s",Id,Svc.Type,Svc.PrivateEndPoint));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger().error(Poco::format("KAFKA-MSG: invalid event '%s', missing a field.",Event));
|
||||||
|
}
|
||||||
|
} else if (Event==KafkaTopics::ServiceEvents::EVENT_REMOVE_TOKEN) {
|
||||||
|
if(Object->has(KafkaTopics::ServiceEvents::Fields::TOKEN)) {
|
||||||
|
#ifndef TIP_SECURITY_SERVICE
|
||||||
|
AuthClient()->RemovedCachedToken(Object->get(KafkaTopics::ServiceEvents::Fields::TOKEN).toString());
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
logger().error(Poco::format("KAFKA-MSG: invalid event '%s', missing token",Event));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger().error(Poco::format("Unknown Event: %s Source: %Lu", Event, ID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger().error("Bad bus message.");
|
||||||
|
}
|
||||||
|
} catch (const Poco::Exception &E) {
|
||||||
|
logger().log(E);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MicroServiceMetaVec MicroService::GetServices(const std::string & Type) {
|
||||||
|
SubMutexGuard G(InfraMutex_);
|
||||||
|
|
||||||
|
auto T = Poco::toLower(Type);
|
||||||
|
MicroServiceMetaVec Res;
|
||||||
|
for(const auto &[Id,ServiceRec]:Services_) {
|
||||||
|
if(ServiceRec.Type==T)
|
||||||
|
Res.push_back(ServiceRec);
|
||||||
|
}
|
||||||
|
return Res;
|
||||||
|
}
|
||||||
|
|
||||||
|
MicroServiceMetaVec MicroService::GetServices() {
|
||||||
|
SubMutexGuard G(InfraMutex_);
|
||||||
|
|
||||||
|
MicroServiceMetaVec Res;
|
||||||
|
for(const auto &[Id,ServiceRec]:Services_) {
|
||||||
|
Res.push_back(ServiceRec);
|
||||||
|
}
|
||||||
|
return Res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::initialize(Poco::Util::Application &self) {
|
||||||
|
// add the default services
|
||||||
|
SubSystems_.push_back(KafkaManager());
|
||||||
|
SubSystems_.push_back(ALBHealthCheckServer());
|
||||||
|
|
||||||
|
Poco::Net::initializeSSL();
|
||||||
|
Poco::Net::HTTPStreamFactory::registerFactory();
|
||||||
|
Poco::Net::HTTPSStreamFactory::registerFactory();
|
||||||
|
Poco::Net::FTPStreamFactory::registerFactory();
|
||||||
|
Poco::Net::FTPSStreamFactory::registerFactory();
|
||||||
|
std::string Location = Poco::Environment::get(DAEMON_CONFIG_ENV_VAR,".");
|
||||||
|
Poco::Path ConfigFile;
|
||||||
|
|
||||||
|
ConfigFile = ConfigFileName_.empty() ? Location + "/" + DAEMON_PROPERTIES_FILENAME : ConfigFileName_;
|
||||||
|
|
||||||
|
if(!ConfigFile.isFile())
|
||||||
|
{
|
||||||
|
std::cerr << DAEMON_APP_NAME << ": Configuration "
|
||||||
|
<< ConfigFile.toString() << " does not seem to exist. Please set " + DAEMON_CONFIG_ENV_VAR
|
||||||
|
+ " env variable the path of the " + DAEMON_PROPERTIES_FILENAME + " file." << std::endl;
|
||||||
|
std::exit(Poco::Util::Application::EXIT_CONFIG);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char * LogFilePathKey = "logging.channels.c2.path";
|
||||||
|
|
||||||
|
loadConfiguration(ConfigFile.toString());
|
||||||
|
|
||||||
|
if(LogDir_.empty()) {
|
||||||
|
std::string OriginalLogFileValue = ConfigPath(LogFilePathKey);
|
||||||
|
config().setString(LogFilePathKey, OriginalLogFileValue);
|
||||||
|
} else {
|
||||||
|
config().setString(LogFilePathKey, LogDir_);
|
||||||
|
}
|
||||||
|
Poco::File DataDir(ConfigPath("ucentral.system.data"));
|
||||||
|
DataDir_ = DataDir.path();
|
||||||
|
if(!DataDir.exists()) {
|
||||||
|
try {
|
||||||
|
DataDir.createDirectory();
|
||||||
|
} catch (const Poco::Exception &E) {
|
||||||
|
logger().log(E);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string KeyFile = ConfigPath("ucentral.service.key");
|
||||||
|
std::string KeyFilePassword = ConfigPath("ucentral.service.key.password" , "" );
|
||||||
|
AppKey_ = Poco::SharedPtr<Poco::Crypto::RSAKey>(new Poco::Crypto::RSAKey("", KeyFile, KeyFilePassword));
|
||||||
|
Cipher_ = CipherFactory_.createCipher(*AppKey_);
|
||||||
|
ID_ = Utils::GetSystemId();
|
||||||
|
if(!DebugMode_)
|
||||||
|
DebugMode_ = ConfigGetBool("ucentral.system.debug",false);
|
||||||
|
MyPrivateEndPoint_ = ConfigGetString("ucentral.system.uri.private");
|
||||||
|
MyPublicEndPoint_ = ConfigGetString("ucentral.system.uri.public");
|
||||||
|
UIURI_ = ConfigGetString("ucentral.system.uri.ui");
|
||||||
|
MyHash_ = CreateHash(MyPublicEndPoint_);
|
||||||
|
InitializeSubSystemServers();
|
||||||
|
ServerApplication::initialize(self);
|
||||||
|
|
||||||
|
Types::TopicNotifyFunction F = [this](std::string s1,std::string s2) { this->BusMessageReceived(s1,s2); };
|
||||||
|
KafkaManager()->RegisterTopicWatcher(KafkaTopics::SERVICE_EVENTS, F);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::uninitialize() {
|
||||||
|
// add your own uninitialization code here
|
||||||
|
ServerApplication::uninitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::reinitialize(Poco::Util::Application &self) {
|
||||||
|
ServerApplication::reinitialize(self);
|
||||||
|
// add your own reinitialization code here
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::defineOptions(Poco::Util::OptionSet &options) {
|
||||||
|
ServerApplication::defineOptions(options);
|
||||||
|
|
||||||
|
options.addOption(
|
||||||
|
Poco::Util::Option("help", "", "display help information on command line arguments")
|
||||||
|
.required(false)
|
||||||
|
.repeatable(false)
|
||||||
|
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleHelp)));
|
||||||
|
|
||||||
|
options.addOption(
|
||||||
|
Poco::Util::Option("file", "", "specify the configuration file")
|
||||||
|
.required(false)
|
||||||
|
.repeatable(false)
|
||||||
|
.argument("file")
|
||||||
|
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleConfig)));
|
||||||
|
|
||||||
|
options.addOption(
|
||||||
|
Poco::Util::Option("debug", "", "to run in debug, set to true")
|
||||||
|
.required(false)
|
||||||
|
.repeatable(false)
|
||||||
|
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleDebug)));
|
||||||
|
|
||||||
|
options.addOption(
|
||||||
|
Poco::Util::Option("logs", "", "specify the log directory and file (i.e. dir/file.log)")
|
||||||
|
.required(false)
|
||||||
|
.repeatable(false)
|
||||||
|
.argument("dir")
|
||||||
|
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleLogs)));
|
||||||
|
|
||||||
|
options.addOption(
|
||||||
|
Poco::Util::Option("version", "", "get the version and quit.")
|
||||||
|
.required(false)
|
||||||
|
.repeatable(false)
|
||||||
|
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleVersion)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::handleHelp(const std::string &name, const std::string &value) {
|
||||||
|
HelpRequested_ = true;
|
||||||
|
displayHelp();
|
||||||
|
stopOptionsProcessing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::handleVersion(const std::string &name, const std::string &value) {
|
||||||
|
HelpRequested_ = true;
|
||||||
|
std::cout << Version() << std::endl;
|
||||||
|
stopOptionsProcessing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::handleDebug(const std::string &name, const std::string &value) {
|
||||||
|
if(value == "true")
|
||||||
|
DebugMode_ = true ;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::handleLogs(const std::string &name, const std::string &value) {
|
||||||
|
LogDir_ = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::handleConfig(const std::string &name, const std::string &value) {
|
||||||
|
ConfigFileName_ = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::displayHelp() {
|
||||||
|
Poco::Util::HelpFormatter helpFormatter(options());
|
||||||
|
helpFormatter.setCommand(commandName());
|
||||||
|
helpFormatter.setUsage("OPTIONS");
|
||||||
|
helpFormatter.setHeader("A " + DAEMON_APP_NAME + " implementation for TIP.");
|
||||||
|
helpFormatter.format(std::cout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::InitializeSubSystemServers() {
|
||||||
|
for(auto i:SubSystems_)
|
||||||
|
addSubsystem(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::StartSubSystemServers() {
|
||||||
|
for(auto i:SubSystems_) {
|
||||||
|
i->Start();
|
||||||
|
}
|
||||||
|
BusEventManager_.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::StopSubSystemServers() {
|
||||||
|
BusEventManager_.Stop();
|
||||||
|
for(auto i=SubSystems_.rbegin(); i!=SubSystems_.rend(); ++i)
|
||||||
|
(*i)->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MicroService::CreateUUID() {
|
||||||
|
return UUIDGenerator_.create().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MicroService::SetSubsystemLogLevel(const std::string &SubSystem, const std::string &Level) {
|
||||||
|
try {
|
||||||
|
auto P = Poco::Logger::parseLevel(Level);
|
||||||
|
auto Sub = Poco::toLower(SubSystem);
|
||||||
|
|
||||||
|
if (Sub == "all") {
|
||||||
|
for (auto i : SubSystems_) {
|
||||||
|
i->Logger().setLevel(P);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// std::cout << "Sub:" << SubSystem << " Level:" << Level << std::endl;
|
||||||
|
for (auto i : SubSystems_) {
|
||||||
|
if (Sub == Poco::toLower(i->Name())) {
|
||||||
|
i->Logger().setLevel(P);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const Poco::Exception & E) {
|
||||||
|
std::cout << "Exception" << std::endl;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Types::StringVec MicroService::GetSubSystems() const {
|
||||||
|
Types::StringVec Result;
|
||||||
|
for(auto i:SubSystems_)
|
||||||
|
Result.push_back(i->Name());
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Types::StringPairVec MicroService::GetLogLevels() const {
|
||||||
|
Types::StringPairVec Result;
|
||||||
|
|
||||||
|
for(auto &i:SubSystems_) {
|
||||||
|
auto P = std::make_pair( i->Name(), Utils::LogLevelToString(i->GetLoggingLevel()));
|
||||||
|
Result.push_back(P);
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Types::StringVec & MicroService::GetLogLevelNames() const {
|
||||||
|
static Types::StringVec LevelNames{"none", "fatal", "critical", "error", "warning", "notice", "information", "debug", "trace" };
|
||||||
|
return LevelNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t MicroService::ConfigGetInt(const std::string &Key,uint64_t Default) {
|
||||||
|
return (uint64_t) config().getInt64(Key,Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t MicroService::ConfigGetInt(const std::string &Key) {
|
||||||
|
return config().getInt(Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t MicroService::ConfigGetBool(const std::string &Key,bool Default) {
|
||||||
|
return config().getBool(Key,Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t MicroService::ConfigGetBool(const std::string &Key) {
|
||||||
|
return config().getBool(Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MicroService::ConfigGetString(const std::string &Key,const std::string & Default) {
|
||||||
|
return config().getString(Key, Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MicroService::ConfigGetString(const std::string &Key) {
|
||||||
|
return config().getString(Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MicroService::ConfigPath(const std::string &Key,const std::string & Default) {
|
||||||
|
std::string R = config().getString(Key, Default);
|
||||||
|
return Poco::Path::expand(R);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MicroService::ConfigPath(const std::string &Key) {
|
||||||
|
std::string R = config().getString(Key);
|
||||||
|
return Poco::Path::expand(R);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MicroService::Encrypt(const std::string &S) {
|
||||||
|
return Cipher_->encryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MicroService::Decrypt(const std::string &S) {
|
||||||
|
return Cipher_->decryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MicroService::CreateHash(const std::string &S) {
|
||||||
|
SHA2_.update(S);
|
||||||
|
return Utils::ToHex(SHA2_.digest());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MicroService::MakeSystemEventMessage( const std::string & Type ) const {
|
||||||
|
Poco::JSON::Object Obj;
|
||||||
|
Obj.set(KafkaTopics::ServiceEvents::Fields::EVENT,Type);
|
||||||
|
Obj.set(KafkaTopics::ServiceEvents::Fields::ID,ID_);
|
||||||
|
Obj.set(KafkaTopics::ServiceEvents::Fields::TYPE,Poco::toLower(DAEMON_APP_NAME));
|
||||||
|
Obj.set(KafkaTopics::ServiceEvents::Fields::PUBLIC,MyPublicEndPoint_);
|
||||||
|
Obj.set(KafkaTopics::ServiceEvents::Fields::PRIVATE,MyPrivateEndPoint_);
|
||||||
|
Obj.set(KafkaTopics::ServiceEvents::Fields::KEY,MyHash_);
|
||||||
|
Obj.set(KafkaTopics::ServiceEvents::Fields::VRSN,Version_);
|
||||||
|
std::stringstream ResultText;
|
||||||
|
Poco::JSON::Stringifier::stringify(Obj, ResultText);
|
||||||
|
return ResultText.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BusEventManager::run() {
|
||||||
|
Running_ = true;
|
||||||
|
auto Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_JOIN);
|
||||||
|
KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false);
|
||||||
|
while(Running_) {
|
||||||
|
Poco::Thread::trySleep((unsigned long)Daemon()->DaemonBusTimer());
|
||||||
|
if(!Running_)
|
||||||
|
break;
|
||||||
|
auto Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE);
|
||||||
|
KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false);
|
||||||
|
}
|
||||||
|
Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_LEAVE);
|
||||||
|
KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
void BusEventManager::Start() {
|
||||||
|
if(KafkaManager()->Enabled()) {
|
||||||
|
Thread_.start(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BusEventManager::Stop() {
|
||||||
|
if(KafkaManager()->Enabled()) {
|
||||||
|
Running_ = false;
|
||||||
|
Thread_.wakeUp();
|
||||||
|
Thread_.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool MicroService::IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request) {
|
||||||
|
try {
|
||||||
|
auto APIKEY = Request.get("X-API-KEY");
|
||||||
|
return APIKEY == MyHash_;
|
||||||
|
} catch (const Poco::Exception &E) {
|
||||||
|
logger().log(E);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroService::SavePID() {
|
||||||
|
try {
|
||||||
|
std::ofstream O;
|
||||||
|
O.open(Daemon()->DataDir() + "/pidfile",std::ios::binary | std::ios::trunc);
|
||||||
|
O << Poco::Process::id();
|
||||||
|
O.close();
|
||||||
|
} catch (...)
|
||||||
|
{
|
||||||
|
std::cout << "Could not save system ID" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int MicroService::main(const ArgVec &args) {
|
||||||
|
|
||||||
|
MyErrorHandler ErrorHandler(*this);
|
||||||
|
Poco::ErrorHandler::set(&ErrorHandler);
|
||||||
|
|
||||||
|
if (!HelpRequested_) {
|
||||||
|
SavePID();
|
||||||
|
Poco::Logger &logger = Poco::Logger::get(DAEMON_APP_NAME);
|
||||||
|
logger.notice(Poco::format("Starting %s version %s.",DAEMON_APP_NAME, Version()));
|
||||||
|
|
||||||
|
if(Poco::Net::Socket::supportsIPv6())
|
||||||
|
logger.information("System supports IPv6.");
|
||||||
|
else
|
||||||
|
logger.information("System does NOT support IPv6.");
|
||||||
|
|
||||||
|
if (config().getBool("application.runAsDaemon", false)) {
|
||||||
|
logger.information("Starting as a daemon.");
|
||||||
|
}
|
||||||
|
logger.information(Poco::format("System ID set to %Lu",ID_));
|
||||||
|
StartSubSystemServers();
|
||||||
|
waitForTerminationRequest();
|
||||||
|
StopSubSystemServers();
|
||||||
|
|
||||||
|
logger.notice(Poco::format("Stopped %s...",DAEMON_APP_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Application::EXIT_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
174
src/MicroService.h
Normal file
174
src/MicroService.h
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-06-22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_MICROSERVICE_H
|
||||||
|
#define UCENTRALGW_MICROSERVICE_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "Poco/Util/Application.h"
|
||||||
|
#include "Poco/Util/ServerApplication.h"
|
||||||
|
#include "Poco/Util/Option.h"
|
||||||
|
#include "Poco/Util/OptionSet.h"
|
||||||
|
#include "Poco/UUIDGenerator.h"
|
||||||
|
#include "Poco/ErrorHandler.h"
|
||||||
|
#include "Poco/Crypto/RSAKey.h"
|
||||||
|
#include "Poco/Crypto/CipherFactory.h"
|
||||||
|
#include "Poco/Crypto/Cipher.h"
|
||||||
|
#include "Poco/SHA2Engine.h"
|
||||||
|
#include "Poco/Net/HTTPServerRequest.h"
|
||||||
|
#include "Poco/Process.h"
|
||||||
|
|
||||||
|
#include "uCentralTypes.h"
|
||||||
|
#include "SubSystemServer.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
static const std::string uSERVICE_SECURITY{"ucentralsec"};
|
||||||
|
static const std::string uSERVICE_GATEWAY{"ucentralgw"};
|
||||||
|
static const std::string uSERVICE_FIRMWARE{ "ucentralfws"};
|
||||||
|
static const std::string uSERVICE_TOPOLOGY{ "ucentraltopo"};
|
||||||
|
static const std::string uSERVICE_PROVISIONING{ "ucentralprov"};
|
||||||
|
|
||||||
|
class MyErrorHandler : public Poco::ErrorHandler {
|
||||||
|
public:
|
||||||
|
explicit MyErrorHandler(Poco::Util::Application &App) : App_(App) {}
|
||||||
|
void exception(const Poco::Exception & E) override;
|
||||||
|
void exception(const std::exception & E) override;
|
||||||
|
void exception() override;
|
||||||
|
private:
|
||||||
|
Poco::Util::Application &App_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BusEventManager : public Poco::Runnable {
|
||||||
|
public:
|
||||||
|
void run() override;
|
||||||
|
void Start();
|
||||||
|
void Stop();
|
||||||
|
private:
|
||||||
|
std::atomic_bool Running_ = false;
|
||||||
|
Poco::Thread Thread_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MicroServiceMeta {
|
||||||
|
uint64_t Id=0;
|
||||||
|
std::string Type;
|
||||||
|
std::string PrivateEndPoint;
|
||||||
|
std::string PublicEndPoint;
|
||||||
|
std::string AccessKey;
|
||||||
|
std::string Version;
|
||||||
|
uint64_t LastUpdate=0;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::map<uint64_t, MicroServiceMeta> MicroServiceMetaMap;
|
||||||
|
typedef std::vector<MicroServiceMeta> MicroServiceMetaVec;
|
||||||
|
|
||||||
|
class MicroService : public Poco::Util::ServerApplication {
|
||||||
|
public:
|
||||||
|
explicit MicroService( std::string PropFile,
|
||||||
|
std::string RootEnv,
|
||||||
|
std::string ConfigVar,
|
||||||
|
std::string AppName,
|
||||||
|
uint64_t BusTimer,
|
||||||
|
Types::SubSystemVec Subsystems) :
|
||||||
|
DAEMON_PROPERTIES_FILENAME(std::move(PropFile)),
|
||||||
|
DAEMON_ROOT_ENV_VAR(std::move(RootEnv)),
|
||||||
|
DAEMON_CONFIG_ENV_VAR(std::move(ConfigVar)),
|
||||||
|
DAEMON_APP_NAME(std::move(AppName)),
|
||||||
|
DAEMON_BUS_TIMER(BusTimer),
|
||||||
|
SubSystems_(std::move(Subsystems)) {
|
||||||
|
std::string V{APP_VERSION};
|
||||||
|
std::string B{BUILD_NUMBER};
|
||||||
|
Version_ = V + "(" + B + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(const ArgVec &args) override;
|
||||||
|
void initialize(Application &self) override;
|
||||||
|
void uninitialize() override;
|
||||||
|
void reinitialize(Application &self) override;
|
||||||
|
void defineOptions(Poco::Util::OptionSet &options) override;
|
||||||
|
void handleHelp(const std::string &name, const std::string &value);
|
||||||
|
void handleVersion(const std::string &name, const std::string &value);
|
||||||
|
void handleDebug(const std::string &name, const std::string &value);
|
||||||
|
void handleLogs(const std::string &name, const std::string &value);
|
||||||
|
void handleConfig(const std::string &name, const std::string &value);
|
||||||
|
void displayHelp();
|
||||||
|
|
||||||
|
void InitializeSubSystemServers();
|
||||||
|
void StartSubSystemServers();
|
||||||
|
void StopSubSystemServers();
|
||||||
|
void Exit(int Reason);
|
||||||
|
bool SetSubsystemLogLevel(const std::string & SubSystem, const std::string & Level);
|
||||||
|
[[nodiscard]] std::string Version() { return Version_; }
|
||||||
|
[[nodiscard]] const Poco::SharedPtr<Poco::Crypto::RSAKey> & Key() { return AppKey_; }
|
||||||
|
[[nodiscard]] inline const std::string & DataDir() { return DataDir_; }
|
||||||
|
[[nodiscard]] std::string CreateUUID();
|
||||||
|
[[nodiscard]] bool Debug() const { return DebugMode_; }
|
||||||
|
[[nodiscard]] uint64_t ID() const { return ID_; }
|
||||||
|
[[nodiscard]] Types::StringVec GetSubSystems() const;
|
||||||
|
[[nodiscard]] Types::StringPairVec GetLogLevels() const;
|
||||||
|
[[nodiscard]] const Types::StringVec & GetLogLevelNames() const;
|
||||||
|
[[nodiscard]] std::string ConfigGetString(const std::string &Key,const std::string & Default);
|
||||||
|
[[nodiscard]] std::string ConfigGetString(const std::string &Key);
|
||||||
|
[[nodiscard]] std::string ConfigPath(const std::string &Key,const std::string & Default);
|
||||||
|
[[nodiscard]] std::string ConfigPath(const std::string &Key);
|
||||||
|
[[nodiscard]] uint64_t ConfigGetInt(const std::string &Key,uint64_t Default);
|
||||||
|
[[nodiscard]] uint64_t ConfigGetInt(const std::string &Key);
|
||||||
|
[[nodiscard]] uint64_t ConfigGetBool(const std::string &Key,bool Default);
|
||||||
|
[[nodiscard]] uint64_t ConfigGetBool(const std::string &Key);
|
||||||
|
[[nodiscard]] std::string Encrypt(const std::string &S);
|
||||||
|
[[nodiscard]] std::string Decrypt(const std::string &S);
|
||||||
|
[[nodiscard]] std::string CreateHash(const std::string &S);
|
||||||
|
[[nodiscard]] std::string Hash() const { return MyHash_; };
|
||||||
|
[[nodiscard]] std::string ServiceType() const { return DAEMON_APP_NAME; };
|
||||||
|
[[nodiscard]] std::string PrivateEndPoint() const { return MyPrivateEndPoint_; };
|
||||||
|
[[nodiscard]] std::string PublicEndPoint() const { return MyPublicEndPoint_; };
|
||||||
|
[[nodiscard]] std::string MakeSystemEventMessage( const std::string & Type ) const ;
|
||||||
|
inline uint64_t DaemonBusTimer() const { return DAEMON_BUS_TIMER; };
|
||||||
|
|
||||||
|
void BusMessageReceived( const std::string & Key, const std::string & Message);
|
||||||
|
[[nodiscard]] MicroServiceMetaVec GetServices(const std::string & type);
|
||||||
|
[[nodiscard]] MicroServiceMetaVec GetServices();
|
||||||
|
[[nodiscard]] bool IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request);
|
||||||
|
|
||||||
|
void SavePID();
|
||||||
|
inline uint64_t GetPID() { return Poco::Process::id(); };
|
||||||
|
[[nodiscard]] inline const std::string GetPublicAPIEndPoint() const { return MyPublicEndPoint_ + "/api/v1"; };
|
||||||
|
[[nodiscard]] inline const std::string & GetUIURI() const { return UIURI_;};
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool HelpRequested_ = false;
|
||||||
|
std::string LogDir_;
|
||||||
|
std::string ConfigFileName_;
|
||||||
|
Poco::UUIDGenerator UUIDGenerator_;
|
||||||
|
uint64_t ID_ = 1;
|
||||||
|
Poco::SharedPtr<Poco::Crypto::RSAKey> AppKey_ = nullptr;
|
||||||
|
bool DebugMode_ = false;
|
||||||
|
std::string DataDir_;
|
||||||
|
Types::SubSystemVec SubSystems_;
|
||||||
|
Poco::Crypto::CipherFactory & CipherFactory_ = Poco::Crypto::CipherFactory::defaultFactory();
|
||||||
|
Poco::Crypto::Cipher * Cipher_ = nullptr;
|
||||||
|
Poco::SHA2Engine SHA2_;
|
||||||
|
MicroServiceMetaMap Services_;
|
||||||
|
std::string MyHash_;
|
||||||
|
std::string MyPrivateEndPoint_;
|
||||||
|
std::string MyPublicEndPoint_;
|
||||||
|
std::string UIURI_;
|
||||||
|
std::string Version_;
|
||||||
|
BusEventManager BusEventManager_;
|
||||||
|
SubMutex InfraMutex_;
|
||||||
|
|
||||||
|
std::string DAEMON_PROPERTIES_FILENAME;
|
||||||
|
std::string DAEMON_ROOT_ENV_VAR;
|
||||||
|
std::string DAEMON_CONFIG_ENV_VAR;
|
||||||
|
std::string DAEMON_APP_NAME;
|
||||||
|
uint64_t DAEMON_BUS_TIMER;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // UCENTRALGW_MICROSERVICE_H
|
||||||
68
src/OpenAPIRequest.cpp
Normal file
68
src/OpenAPIRequest.cpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-07-01.
|
||||||
|
//
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "OpenAPIRequest.h"
|
||||||
|
|
||||||
|
#include "Poco/Net/HTTPSClientSession.h"
|
||||||
|
#include <Poco/Net/HTTPClientSession.h>
|
||||||
|
#include <Poco/Net/HTTPRequest.h>
|
||||||
|
#include <Poco/Net/HTTPResponse.h>
|
||||||
|
#include <Poco/StreamCopier.h>
|
||||||
|
#include <Poco/JSON/Parser.h>
|
||||||
|
#include <Poco/Path.h>
|
||||||
|
#include <Poco/URI.h>
|
||||||
|
#include <Poco/Exception.h>
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "Daemon.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
OpenAPIRequestGet::OpenAPIRequestGet( const std::string & ServiceType,
|
||||||
|
const std::string & EndPoint,
|
||||||
|
Types::StringPairVec & QueryData,
|
||||||
|
uint64_t msTimeout):
|
||||||
|
Type_(ServiceType),
|
||||||
|
EndPoint_(EndPoint),
|
||||||
|
QueryData_(QueryData),
|
||||||
|
msTimeout_(msTimeout) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int OpenAPIRequestGet::Do(Poco::JSON::Object::Ptr &ResponseObject) {
|
||||||
|
try {
|
||||||
|
auto Services = Daemon()->GetServices(Type_);
|
||||||
|
for(auto const &Svc:Services) {
|
||||||
|
Poco::URI URI(Svc.PrivateEndPoint);
|
||||||
|
Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort());
|
||||||
|
|
||||||
|
URI.setPath(EndPoint_);
|
||||||
|
for (const auto &qp : QueryData_)
|
||||||
|
URI.addQueryParameter(qp.first, qp.second);
|
||||||
|
|
||||||
|
std::string Path(URI.getPathAndQuery());
|
||||||
|
Session.setTimeout(Poco::Timespan(5, 0));
|
||||||
|
|
||||||
|
Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_GET,
|
||||||
|
Path,
|
||||||
|
Poco::Net::HTTPMessage::HTTP_1_1);
|
||||||
|
Request.add("X-API-KEY", Svc.AccessKey);
|
||||||
|
Session.sendRequest(Request);
|
||||||
|
|
||||||
|
Poco::Net::HTTPResponse Response;
|
||||||
|
std::istream &is = Session.receiveResponse(Response);
|
||||||
|
if(Response.getStatus()==Poco::Net::HTTPResponse::HTTP_OK) {
|
||||||
|
Poco::JSON::Parser P;
|
||||||
|
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
||||||
|
}
|
||||||
|
return Response.getStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const Poco::Exception &E)
|
||||||
|
{
|
||||||
|
std::cerr << E.displayText() << std::endl;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/OpenAPIRequest.h
Normal file
29
src/OpenAPIRequest.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-07-01.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_OPENAPIREQUEST_H
|
||||||
|
#define UCENTRALGW_OPENAPIREQUEST_H
|
||||||
|
|
||||||
|
#include "Poco/JSON/Object.h"
|
||||||
|
|
||||||
|
#include "uCentralTypes.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class OpenAPIRequestGet {
|
||||||
|
public:
|
||||||
|
explicit OpenAPIRequestGet( const std::string & Type,
|
||||||
|
const std::string & EndPoint,
|
||||||
|
Types::StringPairVec & QueryData,
|
||||||
|
uint64_t msTimeout);
|
||||||
|
int Do(Poco::JSON::Object::Ptr &ResponseObject);
|
||||||
|
private:
|
||||||
|
std::string Type_;
|
||||||
|
std::string EndPoint_;
|
||||||
|
Types::StringPairVec QueryData_;
|
||||||
|
uint64_t msTimeout_;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // UCENTRALGW_OPENAPIREQUEST_H
|
||||||
67
src/RESTAPI_InternalServer.cpp
Normal file
67
src/RESTAPI_InternalServer.cpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-06-29.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "RESTAPI_InternalServer.h"
|
||||||
|
|
||||||
|
#include "Poco/URI.h"
|
||||||
|
|
||||||
|
#include "RESTAPI_system_command.h"
|
||||||
|
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class RESTAPI_InternalServer *RESTAPI_InternalServer::instance_ = nullptr;
|
||||||
|
|
||||||
|
RESTAPI_InternalServer::RESTAPI_InternalServer() noexcept: SubSystemServer("RESTAPIInternalServer", "REST-ISRV", "ucentral.internal.restapi")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int RESTAPI_InternalServer::Start() {
|
||||||
|
Logger_.information("Starting.");
|
||||||
|
|
||||||
|
for(const auto & Svr: ConfigServersList_) {
|
||||||
|
Logger_.information(Poco::format("Starting: %s:%s Keyfile:%s CertFile: %s", Svr.Address(), std::to_string(Svr.Port()),
|
||||||
|
Svr.KeyFile(),Svr.CertFile()));
|
||||||
|
|
||||||
|
auto Sock{Svr.CreateSecureSocket(Logger_)};
|
||||||
|
|
||||||
|
Svr.LogCert(Logger_);
|
||||||
|
if(!Svr.RootCA().empty())
|
||||||
|
Svr.LogCas(Logger_);
|
||||||
|
auto Params = new Poco::Net::HTTPServerParams;
|
||||||
|
Params->setMaxThreads(50);
|
||||||
|
Params->setMaxQueued(200);
|
||||||
|
Params->setKeepAlive(true);
|
||||||
|
|
||||||
|
auto NewServer = std::make_unique<Poco::Net::HTTPServer>(new InternalRequestHandlerFactory, Pool_, Sock, Params);
|
||||||
|
NewServer->start();
|
||||||
|
RESTServers_.push_back(std::move(NewServer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPI_InternalServer::Stop() {
|
||||||
|
Logger_.information("Stopping ");
|
||||||
|
for( const auto & svr : RESTServers_ )
|
||||||
|
svr->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Poco::Net::HTTPRequestHandler *InternalRequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest & Request) {
|
||||||
|
|
||||||
|
Logger_.debug(
|
||||||
|
Poco::format("REQUEST(%s): %s %s", uCentral::Utils::FormatIPv6(Request.clientAddress().toString()),
|
||||||
|
Request.getMethod(), Request.getURI()));
|
||||||
|
|
||||||
|
Poco::URI uri(Request.getURI());
|
||||||
|
const auto &Path = uri.getPath();
|
||||||
|
RESTAPIHandler::BindingMap Bindings;
|
||||||
|
|
||||||
|
return RESTAPI_Router_I<
|
||||||
|
RESTAPI_system_command
|
||||||
|
>(Path, Bindings, Logger_);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
53
src/RESTAPI_InternalServer.h
Normal file
53
src/RESTAPI_InternalServer.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-06-29.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALSEC_RESTAPI_INTERNALSERVER_H
|
||||||
|
#define UCENTRALSEC_RESTAPI_INTERNALSERVER_H
|
||||||
|
|
||||||
|
#include "SubSystemServer.h"
|
||||||
|
#include "Poco/Net/HTTPServer.h"
|
||||||
|
#include "Poco/Net/HTTPRequestHandler.h"
|
||||||
|
#include "Poco/Net/HTTPRequestHandlerFactory.h"
|
||||||
|
#include "Poco/Net/HTTPServerRequest.h"
|
||||||
|
#include "Poco/Net/NetException.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class RESTAPI_InternalServer : public SubSystemServer {
|
||||||
|
|
||||||
|
public:
|
||||||
|
RESTAPI_InternalServer() noexcept;
|
||||||
|
|
||||||
|
static RESTAPI_InternalServer *instance() {
|
||||||
|
if (instance_ == nullptr) {
|
||||||
|
instance_ = new RESTAPI_InternalServer;
|
||||||
|
}
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Start() override;
|
||||||
|
void Stop() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static RESTAPI_InternalServer *instance_;
|
||||||
|
std::vector<std::unique_ptr<Poco::Net::HTTPServer>> RESTServers_;
|
||||||
|
Poco::ThreadPool Pool_;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline RESTAPI_InternalServer * RESTAPI_InternalServer() { return RESTAPI_InternalServer::instance(); };
|
||||||
|
|
||||||
|
class InternalRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory {
|
||||||
|
public:
|
||||||
|
InternalRequestHandlerFactory() :
|
||||||
|
Logger_(RESTAPI_InternalServer()->Logger()){}
|
||||||
|
|
||||||
|
Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &request) override;
|
||||||
|
private:
|
||||||
|
Poco::Logger & Logger_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif //UCENTRALSEC_RESTAPI_INTERNALSERVER_H
|
||||||
357
src/RESTAPI_SecurityObjects.cpp
Normal file
357
src/RESTAPI_SecurityObjects.cpp
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Poco/JSON/Parser.h"
|
||||||
|
#include "Poco/JSON/Stringifier.h"
|
||||||
|
|
||||||
|
#include "RESTAPI_SecurityObjects.h"
|
||||||
|
#include "RESTAPI_utils.h"
|
||||||
|
|
||||||
|
using uCentral::RESTAPI_utils::field_to_json;
|
||||||
|
using uCentral::RESTAPI_utils::field_from_json;
|
||||||
|
|
||||||
|
namespace uCentral::SecurityObjects {
|
||||||
|
|
||||||
|
void AclTemplate::to_json(Poco::JSON::Object &Obj) const {
|
||||||
|
field_to_json(Obj,"Read",Read_);
|
||||||
|
field_to_json(Obj,"ReadWrite",ReadWrite_);
|
||||||
|
field_to_json(Obj,"ReadWriteCreate",ReadWriteCreate_);
|
||||||
|
field_to_json(Obj,"Delete",Delete_);
|
||||||
|
field_to_json(Obj,"PortalLogin",PortalLogin_);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceAccessType ResourceAccessTypeFromString(const std::string &s) {
|
||||||
|
if(!Poco::icompare(s,"READ")) return READ;
|
||||||
|
if(!Poco::icompare(s,"MODIFY")) return MODIFY;
|
||||||
|
if(!Poco::icompare(s,"DELETE")) return DELETE;
|
||||||
|
if(!Poco::icompare(s,"CREATE")) return CREATE;
|
||||||
|
if(!Poco::icompare(s,"TEST")) return TEST;
|
||||||
|
if(!Poco::icompare(s,"MOVE")) return MOVE;
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ResourceAccessTypeToString(const ResourceAccessType & T) {
|
||||||
|
switch(T) {
|
||||||
|
case READ: return "READ";
|
||||||
|
case MODIFY: return "MODIFY";
|
||||||
|
case DELETE: return "DELETE";
|
||||||
|
case CREATE: return "CREATE";
|
||||||
|
case TEST: return "TEST";
|
||||||
|
case MOVE: return "MOVE";
|
||||||
|
default: return "NONE";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
USER_ROLE UserTypeFromString(const std::string &U) {
|
||||||
|
if (!Poco::icompare(U,"root"))
|
||||||
|
return ROOT;
|
||||||
|
else if (!Poco::icompare(U,"admin"))
|
||||||
|
return ADMIN;
|
||||||
|
else if (!Poco::icompare(U,"subscriber"))
|
||||||
|
return SUBSCRIBER;
|
||||||
|
else if (!Poco::icompare(U,"csr"))
|
||||||
|
return CSR;
|
||||||
|
else if (!Poco::icompare(U, "system"))
|
||||||
|
return SYSTEM;
|
||||||
|
else if (!Poco::icompare(U, "special"))
|
||||||
|
return SPECIAL;
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UserTypeToString(USER_ROLE U) {
|
||||||
|
switch(U) {
|
||||||
|
case UNKNOWN: return "unknown";
|
||||||
|
case ROOT: return "root";
|
||||||
|
case SUBSCRIBER: return "subscriber";
|
||||||
|
case CSR: return "csr";
|
||||||
|
case SYSTEM: return "system";
|
||||||
|
case SPECIAL: return "special";
|
||||||
|
case ADMIN: return "admin";
|
||||||
|
default: return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AclTemplate::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||||
|
try {
|
||||||
|
field_from_json(Obj, "Read", Read_);
|
||||||
|
field_from_json(Obj, "ReadWrite", ReadWrite_);
|
||||||
|
field_from_json(Obj, "ReadWriteCreate", ReadWriteCreate_);
|
||||||
|
field_from_json(Obj, "Delete", Delete_);
|
||||||
|
field_from_json(Obj, "PortalLogin", PortalLogin_);
|
||||||
|
return true;
|
||||||
|
} catch(...) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebToken::to_json(Poco::JSON::Object & Obj) const {
|
||||||
|
Poco::JSON::Object AclTemplateObj;
|
||||||
|
acl_template_.to_json(AclTemplateObj);
|
||||||
|
field_to_json(Obj,"access_token",access_token_);
|
||||||
|
field_to_json(Obj,"refresh_token",refresh_token_);
|
||||||
|
field_to_json(Obj,"token_type",token_type_);
|
||||||
|
field_to_json(Obj,"expires_in",expires_in_);
|
||||||
|
field_to_json(Obj,"idle_timeout",idle_timeout_);
|
||||||
|
field_to_json(Obj,"created",created_);
|
||||||
|
field_to_json(Obj,"username",username_);
|
||||||
|
field_to_json(Obj,"userMustChangePassword",userMustChangePassword);
|
||||||
|
field_to_json(Obj,"errorCode", errorCode);
|
||||||
|
Obj.set("aclTemplate",AclTemplateObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebToken::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||||
|
try {
|
||||||
|
if (Obj->isObject("aclTemplate")) {
|
||||||
|
Poco::JSON::Object::Ptr AclTemplate = Obj->getObject("aclTemplate");
|
||||||
|
acl_template_.from_json(AclTemplate);
|
||||||
|
}
|
||||||
|
field_from_json(Obj, "access_token", access_token_);
|
||||||
|
field_from_json(Obj, "refresh_token", refresh_token_);
|
||||||
|
field_from_json(Obj, "token_type", token_type_);
|
||||||
|
field_from_json(Obj, "expires_in", expires_in_);
|
||||||
|
field_from_json(Obj, "idle_timeout", idle_timeout_);
|
||||||
|
field_from_json(Obj, "created", created_);
|
||||||
|
field_from_json(Obj, "username", username_);
|
||||||
|
field_from_json(Obj, "userMustChangePassword",userMustChangePassword);
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserInfo::to_json(Poco::JSON::Object &Obj) const {
|
||||||
|
field_to_json(Obj,"Id",Id);
|
||||||
|
field_to_json(Obj,"name",name);
|
||||||
|
field_to_json(Obj,"description", description);
|
||||||
|
field_to_json(Obj,"avatar", avatar);
|
||||||
|
field_to_json(Obj,"email", email);
|
||||||
|
field_to_json(Obj,"validated", validated);
|
||||||
|
field_to_json(Obj,"validationEmail", validationEmail);
|
||||||
|
field_to_json(Obj,"validationDate", validationDate);
|
||||||
|
field_to_json(Obj,"creationDate", creationDate);
|
||||||
|
field_to_json(Obj,"validationURI", validationURI);
|
||||||
|
field_to_json(Obj,"changePassword", changePassword);
|
||||||
|
field_to_json(Obj,"lastLogin", lastLogin);
|
||||||
|
field_to_json(Obj,"currentLoginURI", currentLoginURI);
|
||||||
|
field_to_json(Obj,"lastPasswordChange", lastPasswordChange);
|
||||||
|
field_to_json(Obj,"lastEmailCheck", lastEmailCheck);
|
||||||
|
field_to_json(Obj,"waitingForEmailCheck", waitingForEmailCheck);
|
||||||
|
field_to_json(Obj,"locale", locale);
|
||||||
|
field_to_json(Obj,"notes", notes);
|
||||||
|
field_to_json(Obj,"location", location);
|
||||||
|
field_to_json(Obj,"owner", owner);
|
||||||
|
field_to_json(Obj,"suspended", suspended);
|
||||||
|
field_to_json(Obj,"blackListed", blackListed);
|
||||||
|
field_to_json<USER_ROLE>(Obj,"userRole", userRole, UserTypeToString);
|
||||||
|
field_to_json(Obj,"userTypeProprietaryInfo", userTypeProprietaryInfo);
|
||||||
|
field_to_json(Obj,"securityPolicy", securityPolicy);
|
||||||
|
field_to_json(Obj,"securityPolicyChange", securityPolicyChange);
|
||||||
|
field_to_json(Obj,"currentPassword",currentPassword);
|
||||||
|
field_to_json(Obj,"lastPasswords",lastPasswords);
|
||||||
|
field_to_json(Obj,"oauthType",oauthType);
|
||||||
|
field_to_json(Obj,"oauthUserInfo",oauthUserInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool UserInfo::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||||
|
try {
|
||||||
|
field_from_json(Obj,"Id",Id);
|
||||||
|
field_from_json(Obj,"name",name);
|
||||||
|
field_from_json(Obj,"description",description);
|
||||||
|
field_from_json(Obj,"avatar",avatar);
|
||||||
|
field_from_json(Obj,"email",email);
|
||||||
|
field_from_json(Obj,"validationEmail",validationEmail);
|
||||||
|
field_from_json(Obj,"validationURI",validationURI);
|
||||||
|
field_from_json(Obj,"currentLoginURI",currentLoginURI);
|
||||||
|
field_from_json(Obj,"locale",locale);
|
||||||
|
field_from_json(Obj,"notes",notes);
|
||||||
|
field_from_json<USER_ROLE>(Obj,"userRole",userRole, UserTypeFromString);
|
||||||
|
field_from_json(Obj,"securityPolicy",securityPolicy);
|
||||||
|
field_from_json(Obj,"userTypeProprietaryInfo",userTypeProprietaryInfo);
|
||||||
|
field_from_json(Obj,"validationDate",validationDate);
|
||||||
|
field_from_json(Obj,"creationDate",creationDate);
|
||||||
|
field_from_json(Obj,"lastLogin",lastLogin);
|
||||||
|
field_from_json(Obj,"lastPasswordChange",lastPasswordChange);
|
||||||
|
field_from_json(Obj,"lastEmailCheck",lastEmailCheck);
|
||||||
|
field_from_json(Obj,"securityPolicyChange",securityPolicyChange);
|
||||||
|
field_from_json(Obj,"validated",validated);
|
||||||
|
field_from_json(Obj,"changePassword",changePassword);
|
||||||
|
field_from_json(Obj,"waitingForEmailCheck",waitingForEmailCheck);
|
||||||
|
field_from_json(Obj,"suspended",suspended);
|
||||||
|
field_from_json(Obj,"blackListed",blackListed);
|
||||||
|
field_from_json(Obj,"currentPassword",currentPassword);
|
||||||
|
field_from_json(Obj,"lastPasswords",lastPasswords);
|
||||||
|
field_from_json(Obj,"oauthType",oauthType);
|
||||||
|
field_from_json(Obj,"oauthUserInfo",oauthUserInfo);
|
||||||
|
return true;
|
||||||
|
} catch (const Poco::Exception &E) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
void InternalServiceInfo::to_json(Poco::JSON::Object &Obj) const {
|
||||||
|
field_to_json(Obj,"privateURI",privateURI);
|
||||||
|
field_to_json(Obj,"publicURI",publicURI);
|
||||||
|
field_to_json(Obj,"token",token);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool InternalServiceInfo::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||||
|
try {
|
||||||
|
field_from_json(Obj,"privateURI",privateURI);
|
||||||
|
field_from_json(Obj,"publicURI",publicURI);
|
||||||
|
field_from_json(Obj,"token",token);
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
void InternalSystemServices::to_json(Poco::JSON::Object &Obj) const {
|
||||||
|
field_to_json(Obj,"key",key);
|
||||||
|
field_to_json(Obj,"version",version);
|
||||||
|
field_to_json(Obj,"services",services);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool InternalSystemServices::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||||
|
try {
|
||||||
|
field_from_json(Obj, "key", key);
|
||||||
|
field_from_json(Obj, "version", version);
|
||||||
|
field_from_json(Obj, "services", services);
|
||||||
|
return true;
|
||||||
|
} catch(...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
void SystemEndpoint::to_json(Poco::JSON::Object &Obj) const {
|
||||||
|
field_to_json(Obj,"type",type);
|
||||||
|
field_to_json(Obj,"id",id);
|
||||||
|
field_to_json(Obj,"vendor",vendor);
|
||||||
|
field_to_json(Obj,"uri",uri);
|
||||||
|
field_to_json(Obj,"authenticationType",authenticationType);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool SystemEndpoint::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||||
|
try {
|
||||||
|
field_from_json(Obj, "type", type);
|
||||||
|
field_from_json(Obj, "id", id);
|
||||||
|
field_from_json(Obj, "vendor", vendor);
|
||||||
|
field_from_json(Obj, "uri", uri);
|
||||||
|
field_from_json(Obj, "authenticationType", authenticationType);
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
void SystemEndpointList::to_json(Poco::JSON::Object &Obj) const {
|
||||||
|
field_to_json(Obj,"endpoints",endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SystemEndpointList::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||||
|
try {
|
||||||
|
field_from_json(Obj, "endpoints", endpoints);
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserInfoAndPolicy::to_json(Poco::JSON::Object &Obj) const {
|
||||||
|
Poco::JSON::Object UI, TI;
|
||||||
|
userinfo.to_json(UI);
|
||||||
|
webtoken.to_json(TI);
|
||||||
|
Obj.set("tokenInfo",TI);
|
||||||
|
Obj.set("userInfo",UI);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UserInfoAndPolicy::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||||
|
try {
|
||||||
|
field_from_json(Obj, "tokenInfo", webtoken);
|
||||||
|
field_from_json(Obj, "userInfo", userinfo);
|
||||||
|
return true;
|
||||||
|
} catch(...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NoteInfo::to_json(Poco::JSON::Object &Obj) const {
|
||||||
|
field_to_json(Obj,"created", created);
|
||||||
|
field_to_json(Obj,"createdBy", createdBy);
|
||||||
|
field_to_json(Obj,"note", note);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NoteInfo::from_json(Poco::JSON::Object::Ptr Obj) {
|
||||||
|
try {
|
||||||
|
field_from_json(Obj,"created",created);
|
||||||
|
field_from_json(Obj,"createdBy",createdBy);
|
||||||
|
field_from_json(Obj,"note",note);
|
||||||
|
} catch(...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileAction::to_json(Poco::JSON::Object &Obj) const {
|
||||||
|
field_to_json(Obj,"resource", resource);
|
||||||
|
field_to_json<ResourceAccessType>(Obj,"access", access, ResourceAccessTypeToString);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProfileAction::from_json(Poco::JSON::Object::Ptr Obj) {
|
||||||
|
try {
|
||||||
|
field_from_json(Obj,"resource",resource);
|
||||||
|
field_from_json<ResourceAccessType>(Obj,"access",access,ResourceAccessTypeFromString );
|
||||||
|
} catch(...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecurityProfile::to_json(Poco::JSON::Object &Obj) const {
|
||||||
|
field_to_json(Obj,"id", id);
|
||||||
|
field_to_json(Obj,"name", name);
|
||||||
|
field_to_json(Obj,"description", description);
|
||||||
|
field_to_json(Obj,"policy", policy);
|
||||||
|
field_to_json(Obj,"role", role);
|
||||||
|
field_to_json(Obj,"notes", notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SecurityProfile::from_json(Poco::JSON::Object::Ptr Obj) {
|
||||||
|
try {
|
||||||
|
field_from_json(Obj,"id",id);
|
||||||
|
field_from_json(Obj,"name",name);
|
||||||
|
field_from_json(Obj,"description",description);
|
||||||
|
field_from_json(Obj,"policy",policy);
|
||||||
|
field_from_json(Obj,"role",role);
|
||||||
|
field_from_json(Obj,"notes",notes);
|
||||||
|
} catch(...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecurityProfileList::to_json(Poco::JSON::Object &Obj) const {
|
||||||
|
field_to_json(Obj, "profiles", profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SecurityProfileList::from_json(Poco::JSON::Object::Ptr Obj) {
|
||||||
|
try {
|
||||||
|
field_from_json(Obj,"profiles",profiles);
|
||||||
|
} catch(...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
179
src/RESTAPI_SecurityObjects.h
Normal file
179
src/RESTAPI_SecurityObjects.h
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRAL_RESTAPI_SECURITYOBJECTS_H
|
||||||
|
#define UCENTRAL_RESTAPI_SECURITYOBJECTS_H
|
||||||
|
|
||||||
|
#include "Poco/JSON/Object.h"
|
||||||
|
#include "uCentralTypes.h"
|
||||||
|
|
||||||
|
namespace uCentral::SecurityObjects {
|
||||||
|
|
||||||
|
struct AclTemplate {
|
||||||
|
bool Read_ = true;
|
||||||
|
bool ReadWrite_ = true;
|
||||||
|
bool ReadWriteCreate_ = true;
|
||||||
|
bool Delete_ = true;
|
||||||
|
bool PortalLogin_ = true;
|
||||||
|
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(const Poco::JSON::Object::Ptr &Obj); };
|
||||||
|
|
||||||
|
struct WebToken {
|
||||||
|
std::string access_token_;
|
||||||
|
std::string refresh_token_;
|
||||||
|
std::string id_token_;
|
||||||
|
std::string token_type_;
|
||||||
|
std::string username_;
|
||||||
|
bool userMustChangePassword=false;
|
||||||
|
uint64_t errorCode=0;
|
||||||
|
uint64_t expires_in_=0;
|
||||||
|
uint64_t idle_timeout_=0;
|
||||||
|
AclTemplate acl_template_;
|
||||||
|
uint64_t created_=0;
|
||||||
|
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
enum USER_ROLE {
|
||||||
|
UNKNOWN, ROOT, ADMIN, SUBSCRIBER, CSR, SYSTEM, SPECIAL
|
||||||
|
};
|
||||||
|
|
||||||
|
USER_ROLE UserTypeFromString(const std::string &U);
|
||||||
|
std::string UserTypeToString(USER_ROLE U);
|
||||||
|
|
||||||
|
struct NoteInfo {
|
||||||
|
uint64_t created = std::time(nullptr);
|
||||||
|
std::string createdBy;
|
||||||
|
std::string note;
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(Poco::JSON::Object::Ptr Obj);
|
||||||
|
};
|
||||||
|
typedef std::vector<NoteInfo> NoteInfoVec;
|
||||||
|
|
||||||
|
struct UserInfo {
|
||||||
|
std::string Id;
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
std::string avatar;
|
||||||
|
std::string email;
|
||||||
|
bool validated = false;
|
||||||
|
std::string validationEmail;
|
||||||
|
uint64_t validationDate = 0;
|
||||||
|
uint64_t creationDate = 0;
|
||||||
|
std::string validationURI;
|
||||||
|
bool changePassword = false;
|
||||||
|
uint64_t lastLogin = 0;
|
||||||
|
std::string currentLoginURI;
|
||||||
|
uint64_t lastPasswordChange = 0;
|
||||||
|
uint64_t lastEmailCheck = 0;
|
||||||
|
bool waitingForEmailCheck = false;
|
||||||
|
std::string locale;
|
||||||
|
NoteInfoVec notes;
|
||||||
|
std::string location;
|
||||||
|
std::string owner;
|
||||||
|
bool suspended = false;
|
||||||
|
bool blackListed = false;
|
||||||
|
USER_ROLE userRole;
|
||||||
|
std::string userTypeProprietaryInfo;
|
||||||
|
std::string securityPolicy;
|
||||||
|
uint64_t securityPolicyChange = 0 ;
|
||||||
|
std::string currentPassword;
|
||||||
|
Types::StringVec lastPasswords;
|
||||||
|
std::string oauthType;
|
||||||
|
std::string oauthUserInfo;
|
||||||
|
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||||
|
};
|
||||||
|
typedef std::vector<UserInfo> UserInfoVec;
|
||||||
|
|
||||||
|
struct InternalServiceInfo {
|
||||||
|
std::string privateURI;
|
||||||
|
std::string publicURI;
|
||||||
|
std::string token;
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||||
|
};
|
||||||
|
typedef std::vector<InternalServiceInfo> InternalServiceInfoVec;
|
||||||
|
|
||||||
|
struct InternalSystemServices {
|
||||||
|
std::string key;
|
||||||
|
std::string version;
|
||||||
|
InternalServiceInfoVec services;
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SystemEndpoint {
|
||||||
|
std::string type;
|
||||||
|
uint64_t id = 0;
|
||||||
|
std::string vendor;
|
||||||
|
std::string uri;
|
||||||
|
std::string authenticationType;
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||||
|
};
|
||||||
|
typedef std::vector<SystemEndpoint> SystemEndpointVec;
|
||||||
|
|
||||||
|
struct SystemEndpointList {
|
||||||
|
SystemEndpointVec endpoints;
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UserInfoAndPolicy {
|
||||||
|
WebToken webtoken;
|
||||||
|
UserInfo userinfo;
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||||
|
};
|
||||||
|
typedef std::map<std::string,SecurityObjects::UserInfoAndPolicy> UserInfoCache;
|
||||||
|
|
||||||
|
enum ResourceAccessType {
|
||||||
|
NONE,
|
||||||
|
READ,
|
||||||
|
MODIFY,
|
||||||
|
DELETE,
|
||||||
|
CREATE,
|
||||||
|
TEST,
|
||||||
|
MOVE
|
||||||
|
};
|
||||||
|
|
||||||
|
ResourceAccessType ResourceAccessTypeFromString(const std::string &s);
|
||||||
|
std::string ResourceAccessTypeToString(const ResourceAccessType & T);
|
||||||
|
|
||||||
|
struct ProfileAction {
|
||||||
|
std::string resource;
|
||||||
|
ResourceAccessType access;
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(Poco::JSON::Object::Ptr Obj);
|
||||||
|
};
|
||||||
|
typedef std::vector<ProfileAction> ProfileActionVec;
|
||||||
|
|
||||||
|
struct SecurityProfile {
|
||||||
|
uint64_t id;
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
ProfileActionVec policy;
|
||||||
|
std::string role;
|
||||||
|
NoteInfoVec notes;
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(Poco::JSON::Object::Ptr Obj);
|
||||||
|
};
|
||||||
|
typedef std::vector<SecurityProfile> SecurityProfileVec;
|
||||||
|
|
||||||
|
struct SecurityProfileList {
|
||||||
|
SecurityProfileVec profiles;
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
bool from_json(Poco::JSON::Object::Ptr Obj);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //UCENTRAL_RESTAPI_SECURITYOBJECTS_H
|
||||||
10
src/RESTAPI_TopoObjects.cpp
Normal file
10
src/RESTAPI_TopoObjects.cpp
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-07-25.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "RESTAPI_TopoObjects.h"
|
||||||
|
|
||||||
|
namespace uCentral::TopoObjects {
|
||||||
|
void Report::reset() {
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/RESTAPI_TopoObjects.h
Normal file
19
src/RESTAPI_TopoObjects.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-07-25.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRAL_TOPO_RESTAPI_TOPOOBJECTS_H
|
||||||
|
#define UCENTRAL_TOPO_RESTAPI_TOPOOBJECTS_H
|
||||||
|
|
||||||
|
#include "Poco/JSON/Object.h"
|
||||||
|
|
||||||
|
namespace uCentral::TopoObjects {
|
||||||
|
struct Report {
|
||||||
|
|
||||||
|
void to_json(Poco::JSON::Object &Obj) const;
|
||||||
|
void reset();
|
||||||
|
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //UCENTRAL_TOPO_RESTAPI_TOPOOBJECTS_H
|
||||||
404
src/RESTAPI_handler.cpp
Normal file
404
src/RESTAPI_handler.cpp
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <future>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "Poco/URI.h"
|
||||||
|
#include "Poco/Net/OAuth20Credentials.h"
|
||||||
|
|
||||||
|
#ifdef TIP_SECURITY_SERVICE
|
||||||
|
#include "AuthService.h"
|
||||||
|
#else
|
||||||
|
#include "AuthClient.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "RESTAPI_handler.h"
|
||||||
|
#include "RESTAPI_protocol.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "Daemon.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
bool RESTAPIHandler::ParseBindings(const std::string & Request, const std::list<const char *> & EndPoints, BindingMap &bindings) {
|
||||||
|
std::string Param, Value;
|
||||||
|
|
||||||
|
bindings.clear();
|
||||||
|
std::vector<std::string> PathItems = uCentral::Utils::Split(Request, '/');
|
||||||
|
|
||||||
|
for(const auto &EndPoint:EndPoints) {
|
||||||
|
std::vector<std::string> ParamItems = uCentral::Utils::Split(EndPoint, '/');
|
||||||
|
if (PathItems.size() != ParamItems.size())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool Matched = true;
|
||||||
|
for (auto i = 0; i != PathItems.size() && Matched; i++) {
|
||||||
|
// std::cout << "PATH:" << PathItems[i] << " ENDPOINT:" << ParamItems[i] << std::endl;
|
||||||
|
if (PathItems[i] != ParamItems[i]) {
|
||||||
|
if (ParamItems[i][0] == '{') {
|
||||||
|
auto ParamName = ParamItems[i].substr(1, ParamItems[i].size() - 2);
|
||||||
|
bindings[Poco::toLower(ParamName)] = PathItems[i];
|
||||||
|
} else {
|
||||||
|
Matched = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(Matched)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::PrintBindings() {
|
||||||
|
for (auto &[key, value] : Bindings_)
|
||||||
|
std::cout << "Key = " << key << " Value= " << value << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::ParseParameters(Poco::Net::HTTPServerRequest &request) {
|
||||||
|
|
||||||
|
Poco::URI uri(request.getURI());
|
||||||
|
Parameters_ = uri.getQueryParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_number(const std::string &s) {
|
||||||
|
return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_bool(const std::string &s) {
|
||||||
|
if (s == "true" || s == "false")
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t RESTAPIHandler::GetParameter(const std::string &Name, const uint64_t Default) {
|
||||||
|
|
||||||
|
for (const auto &i : Parameters_) {
|
||||||
|
if (i.first == Name) {
|
||||||
|
if (!is_number(i.second))
|
||||||
|
return Default;
|
||||||
|
return std::stoi(i.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RESTAPIHandler::GetBoolParameter(const std::string &Name, bool Default) {
|
||||||
|
|
||||||
|
for (const auto &i : Parameters_) {
|
||||||
|
if (i.first == Name) {
|
||||||
|
if (!is_bool(i.second))
|
||||||
|
return Default;
|
||||||
|
return i.second == "true";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RESTAPIHandler::GetParameter(const std::string &Name, const std::string &Default) {
|
||||||
|
for (const auto &i : Parameters_) {
|
||||||
|
if (i.first == Name)
|
||||||
|
return i.second;
|
||||||
|
}
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &RESTAPIHandler::GetBinding(const std::string &Name, const std::string &Default) {
|
||||||
|
auto E = Bindings_.find(Poco::toLower(Name));
|
||||||
|
if (E == Bindings_.end())
|
||||||
|
return Default;
|
||||||
|
|
||||||
|
return E->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string MakeList(const std::vector<std::string> &L) {
|
||||||
|
std::string Return;
|
||||||
|
for (const auto &i : L)
|
||||||
|
if (Return.empty())
|
||||||
|
Return = i;
|
||||||
|
else
|
||||||
|
Return += ", " + i;
|
||||||
|
|
||||||
|
return Return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::AddCORS(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
auto Origin = Request.find("Origin");
|
||||||
|
if (Origin != Request.end()) {
|
||||||
|
Response.set("Access-Control-Allow-Origin", Origin->second);
|
||||||
|
Response.set("Vary", "Origin");
|
||||||
|
} else {
|
||||||
|
Response.set("Access-Control-Allow-Origin", "*");
|
||||||
|
}
|
||||||
|
Response.set("Access-Control-Allow-Headers", "*");
|
||||||
|
Response.set("Access-Control-Allow-Methods", MakeList(Methods_));
|
||||||
|
Response.set("Access-Control-Max-Age", "86400");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::SetCommonHeaders(Poco::Net::HTTPServerResponse &Response, bool CloseConnection) {
|
||||||
|
Response.setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
|
||||||
|
Response.setChunkedTransferEncoding(true);
|
||||||
|
Response.setContentType("application/json");
|
||||||
|
if(CloseConnection) {
|
||||||
|
Response.set("Connection", "close");
|
||||||
|
Response.setKeepAlive(false);
|
||||||
|
} else {
|
||||||
|
Response.setKeepAlive(true);
|
||||||
|
Response.set("Connection", "Keep-Alive");
|
||||||
|
Response.set("Keep-Alive", "timeout=5, max=1000");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::ProcessOptions(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
AddCORS(Request, Response);
|
||||||
|
SetCommonHeaders(Response);
|
||||||
|
Response.setContentLength(0);
|
||||||
|
Response.set("Access-Control-Allow-Credentials", "true");
|
||||||
|
Response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
|
||||||
|
Response.set("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
|
||||||
|
/* std::cout << "RESPONSE:" << std::endl;
|
||||||
|
for(const auto &[f,s]:Response)
|
||||||
|
std::cout << "First: " << f << " second:" << s << std::endl;
|
||||||
|
*/
|
||||||
|
Response.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::PrepareResponse(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response,
|
||||||
|
Poco::Net::HTTPResponse::HTTPStatus Status,
|
||||||
|
bool CloseConnection) {
|
||||||
|
Response.setStatus(Status);
|
||||||
|
AddCORS(Request, Response);
|
||||||
|
SetCommonHeaders(Response, CloseConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::BadRequest(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response,
|
||||||
|
const std::string & Reason) {
|
||||||
|
PrepareResponse(Request, Response, Poco::Net::HTTPResponse::HTTP_BAD_REQUEST);
|
||||||
|
Poco::JSON::Object ErrorObject;
|
||||||
|
ErrorObject.set("ErrorCode",500);
|
||||||
|
ErrorObject.set("ErrorDetails",Request.getMethod());
|
||||||
|
ErrorObject.set("ErrorDescription",Reason.empty() ? "Command is missing parameters or wrong values." : Reason) ;
|
||||||
|
std::ostream &Answer = Response.send();
|
||||||
|
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::UnAuthorized(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response,
|
||||||
|
const std::string & Reason) {
|
||||||
|
PrepareResponse(Request, Response, Poco::Net::HTTPResponse::HTTP_FORBIDDEN);
|
||||||
|
Poco::JSON::Object ErrorObject;
|
||||||
|
ErrorObject.set("ErrorCode",403);
|
||||||
|
ErrorObject.set("ErrorDetails",Request.getMethod());
|
||||||
|
ErrorObject.set("ErrorDescription",Reason.empty() ? "No access allowed." : Reason) ;
|
||||||
|
std::ostream &Answer = Response.send();
|
||||||
|
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::NotFound(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
PrepareResponse(Request, Response, Poco::Net::HTTPResponse::HTTP_NOT_FOUND);
|
||||||
|
Poco::JSON::Object ErrorObject;
|
||||||
|
ErrorObject.set("ErrorCode",404);
|
||||||
|
ErrorObject.set("ErrorDetails",Request.getMethod());
|
||||||
|
ErrorObject.set("ErrorDescription","This resource does not exist.");
|
||||||
|
std::ostream &Answer = Response.send();
|
||||||
|
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::OK(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
PrepareResponse(Request, Response);
|
||||||
|
if( Request.getMethod()==Poco::Net::HTTPRequest::HTTP_DELETE ||
|
||||||
|
Request.getMethod()==Poco::Net::HTTPRequest::HTTP_OPTIONS) {
|
||||||
|
Response.send();
|
||||||
|
} else {
|
||||||
|
Poco::JSON::Object ErrorObject;
|
||||||
|
ErrorObject.set("Code", 0);
|
||||||
|
ErrorObject.set("Operation", Request.getMethod());
|
||||||
|
ErrorObject.set("Details", "Command completed.");
|
||||||
|
std::ostream &Answer = Response.send();
|
||||||
|
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::SendFile(Poco::File & File, const std::string & UUID, Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
Response.set("Content-Type","application/octet-stream");
|
||||||
|
Response.set("Content-Disposition", "attachment; filename=" + UUID );
|
||||||
|
Response.set("Content-Transfer-Encoding","binary");
|
||||||
|
Response.set("Accept-Ranges", "bytes");
|
||||||
|
Response.set("Cache-Control", "private");
|
||||||
|
Response.set("Pragma", "private");
|
||||||
|
Response.set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
||||||
|
Response.set("Content-Length", std::to_string(File.getSize()));
|
||||||
|
AddCORS(Request, Response);
|
||||||
|
Response.sendFile(File.path(),"application/octet-stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::SendFile(Poco::File & File, Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
Poco::Path P(File.path());
|
||||||
|
auto MT = Utils::FindMediaType(File);
|
||||||
|
if(MT.Encoding==Utils::BINARY) {
|
||||||
|
Response.set("Content-Transfer-Encoding","binary");
|
||||||
|
Response.set("Accept-Ranges", "bytes");
|
||||||
|
}
|
||||||
|
Response.set("Cache-Control", "private");
|
||||||
|
Response.set("Pragma", "private");
|
||||||
|
Response.set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
||||||
|
AddCORS(Request, Response);
|
||||||
|
Response.sendFile(File.path(),MT.ContentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::SendFile(Poco::TemporaryFile &TempAvatar, const std::string &Type, const std::string & Name, Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
auto MT = Utils::FindMediaType(Name);
|
||||||
|
if(MT.Encoding==Utils::BINARY) {
|
||||||
|
Response.set("Content-Transfer-Encoding","binary");
|
||||||
|
Response.set("Accept-Ranges", "bytes");
|
||||||
|
}
|
||||||
|
Response.set("Content-Disposition", "attachment; filename=" + Name );
|
||||||
|
Response.set("Accept-Ranges", "bytes");
|
||||||
|
Response.set("Cache-Control", "private");
|
||||||
|
Response.set("Pragma", "private");
|
||||||
|
Response.set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
||||||
|
AddCORS(Request, Response);
|
||||||
|
Response.sendFile(TempAvatar.path(),MT.ContentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::SendHTMLFileBack(Poco::File & File,
|
||||||
|
Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response ,
|
||||||
|
const Types::StringPairVec & FormVars) {
|
||||||
|
Response.set("Pragma", "private");
|
||||||
|
Response.set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
||||||
|
Response.set("Content-Length", std::to_string(File.getSize()));
|
||||||
|
AddCORS(Request, Response);
|
||||||
|
auto FormContent = Utils::LoadFile(File.path());
|
||||||
|
Utils::ReplaceVariables(FormContent, FormVars);
|
||||||
|
Response.setChunkedTransferEncoding(true);
|
||||||
|
Response.setContentType("text/html");
|
||||||
|
std::ostream& ostr = Response.send();
|
||||||
|
ostr << FormContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::ReturnStatus(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response,
|
||||||
|
Poco::Net::HTTPResponse::HTTPStatus Status,
|
||||||
|
bool CloseConnection) {
|
||||||
|
PrepareResponse(Request, Response, Status, CloseConnection);
|
||||||
|
if(Status == Poco::Net::HTTPResponse::HTTP_NO_CONTENT) {
|
||||||
|
Response.setContentLength(0);
|
||||||
|
Response.erase("Content-Type");
|
||||||
|
Response.setChunkedTransferEncoding(false);
|
||||||
|
}
|
||||||
|
Response.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RESTAPIHandler::ContinueProcessing(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
if (Request.getMethod() == Poco::Net::HTTPRequest::HTTP_OPTIONS) {
|
||||||
|
ProcessOptions(Request, Response);
|
||||||
|
return false;
|
||||||
|
} else if (std::find(Methods_.begin(), Methods_.end(), Request.getMethod()) == Methods_.end()) {
|
||||||
|
BadRequest(Request, Response);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RESTAPIHandler::IsAuthorized(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
if(Internal_) {
|
||||||
|
return Daemon()->IsValidAPIKEY(Request);
|
||||||
|
} else {
|
||||||
|
if (SessionToken_.empty()) {
|
||||||
|
try {
|
||||||
|
Poco::Net::OAuth20Credentials Auth(Request);
|
||||||
|
|
||||||
|
if (Auth.getScheme() == "Bearer") {
|
||||||
|
SessionToken_ = Auth.getBearerToken();
|
||||||
|
}
|
||||||
|
} catch (const Poco::Exception &E) {
|
||||||
|
Logger_.log(E);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef TIP_SECURITY_SERVICE
|
||||||
|
if (AuthService()->IsAuthorized(Request, SessionToken_, UserInfo_)) {
|
||||||
|
#else
|
||||||
|
if (AuthClient()->IsAuthorized(Request, SessionToken_, UserInfo_)) {
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
UnAuthorized(Request, Response);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
bool RESTAPIHandler::ValidateAPIKey(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
auto Key = Request.get("X-API-KEY", "");
|
||||||
|
|
||||||
|
if (Key.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
void RESTAPIHandler::ReturnObject(Poco::Net::HTTPServerRequest &Request, Poco::JSON::Object &Object,
|
||||||
|
Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
PrepareResponse(Request, Response);
|
||||||
|
std::ostream &Answer = Response.send();
|
||||||
|
Poco::JSON::Stringifier::stringify(Object, Answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPIHandler::InitQueryBlock() {
|
||||||
|
QB_.SerialNumber = GetParameter(uCentral::RESTAPI::Protocol::SERIALNUMBER, "");
|
||||||
|
QB_.StartDate = GetParameter(uCentral::RESTAPI::Protocol::STARTDATE, 0);
|
||||||
|
QB_.EndDate = GetParameter(uCentral::RESTAPI::Protocol::ENDDATE, 0);
|
||||||
|
QB_.Offset = GetParameter(uCentral::RESTAPI::Protocol::OFFSET, 0);
|
||||||
|
QB_.Limit = GetParameter(uCentral::RESTAPI::Protocol::LIMIT, 100);
|
||||||
|
QB_.Filter = GetParameter(uCentral::RESTAPI::Protocol::FILTER, "");
|
||||||
|
QB_.Select = GetParameter(uCentral::RESTAPI::Protocol::SELECT, "");
|
||||||
|
QB_.Lifetime = GetBoolParameter(uCentral::RESTAPI::Protocol::LIFETIME,false);
|
||||||
|
QB_.LogType = GetParameter(uCentral::RESTAPI::Protocol::LOGTYPE,0);
|
||||||
|
QB_.LastOnly = GetBoolParameter(uCentral::RESTAPI::Protocol::LASTONLY,false);
|
||||||
|
QB_.Newest = GetBoolParameter(uCentral::RESTAPI::Protocol::NEWEST,false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] uint64_t RESTAPIHandler::Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default){
|
||||||
|
if(Obj->has(Parameter))
|
||||||
|
return Obj->get(Parameter);
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string RESTAPIHandler::GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default){
|
||||||
|
if(Obj->has(Parameter))
|
||||||
|
return Obj->get(Parameter).toString();
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool RESTAPIHandler::GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default){
|
||||||
|
if(Obj->has(Parameter))
|
||||||
|
return Obj->get(Parameter).toString()=="true";
|
||||||
|
return Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] uint64_t RESTAPIHandler::GetWhen(const Poco::JSON::Object::Ptr &Obj) {
|
||||||
|
return RESTAPIHandler::Get(uCentral::RESTAPI::Protocol::WHEN, Obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
215
src/RESTAPI_handler.h
Normal file
215
src/RESTAPI_handler.h
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRAL_RESTAPI_HANDLER_H
|
||||||
|
#define UCENTRAL_RESTAPI_HANDLER_H
|
||||||
|
|
||||||
|
#include "Poco/URI.h"
|
||||||
|
#include "Poco/Net/HTTPRequestHandler.h"
|
||||||
|
#include "Poco/Net/HTTPRequestHandlerFactory.h"
|
||||||
|
#include "Poco/Net/HTTPServerRequest.h"
|
||||||
|
#include "Poco/Net/HTTPServerResponse.h"
|
||||||
|
#include "Poco/Net/NetException.h"
|
||||||
|
#include "Poco/Net/PartHandler.h"
|
||||||
|
|
||||||
|
#include "Poco/Logger.h"
|
||||||
|
#include "Poco/File.h"
|
||||||
|
#include "Poco/TemporaryFile.h"
|
||||||
|
#include "Poco/JSON/Object.h"
|
||||||
|
#include "Poco/CountingStream.h"
|
||||||
|
#include "Poco/NullStream.h"
|
||||||
|
|
||||||
|
#include "RESTAPI_SecurityObjects.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class RESTAPI_PartHandler: public Poco::Net::PartHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RESTAPI_PartHandler():
|
||||||
|
_length(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void handlePart(const Poco::Net::MessageHeader& header, std::istream& stream) override
|
||||||
|
{
|
||||||
|
_type = header.get("Content-Type", "(unspecified)");
|
||||||
|
if (header.has("Content-Disposition"))
|
||||||
|
{
|
||||||
|
std::string disp;
|
||||||
|
Poco::Net::NameValueCollection params;
|
||||||
|
Poco::Net::MessageHeader::splitParameters(header["Content-Disposition"], disp, params);
|
||||||
|
_name = params.get("name", "(unnamed)");
|
||||||
|
_fileName = params.get("filename", "(unnamed)");
|
||||||
|
}
|
||||||
|
|
||||||
|
Poco::CountingInputStream istr(stream);
|
||||||
|
Poco::NullOutputStream ostr;
|
||||||
|
Poco::StreamCopier::copyStream(istr, ostr);
|
||||||
|
_length = (int)istr.chars();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int length() const
|
||||||
|
{
|
||||||
|
return _length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const std::string& name() const
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const std::string& fileName() const
|
||||||
|
{
|
||||||
|
return _fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const std::string& contentType() const
|
||||||
|
{
|
||||||
|
return _type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _length;
|
||||||
|
std::string _type;
|
||||||
|
std::string _name;
|
||||||
|
std::string _fileName;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RESTAPIHandler : public Poco::Net::HTTPRequestHandler {
|
||||||
|
public:
|
||||||
|
struct QueryBlock {
|
||||||
|
uint64_t StartDate = 0 , EndDate = 0 , Offset = 0 , Limit = 0, LogType = 0 ;
|
||||||
|
std::string SerialNumber, Filter, Select;
|
||||||
|
bool Lifetime=false, LastOnly=false, Newest=false;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::map<std::string, std::string> BindingMap;
|
||||||
|
|
||||||
|
RESTAPIHandler(BindingMap map, Poco::Logger &l, std::vector<std::string> Methods, bool Internal=false)
|
||||||
|
: Bindings_(std::move(map)), Logger_(l), Methods_(std::move(Methods)), Internal_(Internal) {}
|
||||||
|
|
||||||
|
static bool ParseBindings(const std::string & Request, const std::list<const char *> & EndPoints, BindingMap &Keys);
|
||||||
|
void PrintBindings();
|
||||||
|
void ParseParameters(Poco::Net::HTTPServerRequest &request);
|
||||||
|
|
||||||
|
void AddCORS(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &response);
|
||||||
|
void SetCommonHeaders(Poco::Net::HTTPServerResponse &response, bool CloseConnection=false);
|
||||||
|
void ProcessOptions(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &response);
|
||||||
|
void
|
||||||
|
PrepareResponse(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &response,
|
||||||
|
Poco::Net::HTTPResponse::HTTPStatus Status = Poco::Net::HTTPResponse::HTTP_OK,
|
||||||
|
bool CloseConnection = false);
|
||||||
|
bool ContinueProcessing(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response);
|
||||||
|
|
||||||
|
bool IsAuthorized(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response);
|
||||||
|
/* bool ValidateAPIKey(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response); */
|
||||||
|
|
||||||
|
uint64_t GetParameter(const std::string &Name, uint64_t Default);
|
||||||
|
std::string GetParameter(const std::string &Name, const std::string &Default);
|
||||||
|
bool GetBoolParameter(const std::string &Name, bool Default);
|
||||||
|
|
||||||
|
void BadRequest(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response, const std::string &Reason = "");
|
||||||
|
void UnAuthorized(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response, const std::string &Reason = "");
|
||||||
|
void ReturnObject(Poco::Net::HTTPServerRequest &Request, Poco::JSON::Object &Object,
|
||||||
|
Poco::Net::HTTPServerResponse &Response);
|
||||||
|
void NotFound(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
|
||||||
|
void OK(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
|
||||||
|
void ReturnStatus(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response,
|
||||||
|
Poco::Net::HTTPResponse::HTTPStatus Status,
|
||||||
|
bool CloseConnection=false);
|
||||||
|
void SendFile(Poco::File & File, const std::string & UUID,
|
||||||
|
Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
|
||||||
|
void SendHTMLFileBack(Poco::File & File,
|
||||||
|
Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response ,
|
||||||
|
const Types::StringPairVec & FormVars);
|
||||||
|
void SendFile(Poco::TemporaryFile &TempAvatar, const std::string &Type, const std::string & Name, Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
|
||||||
|
|
||||||
|
void SendFile(Poco::File & File, Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
|
||||||
|
|
||||||
|
const std::string &GetBinding(const std::string &Name, const std::string &Default);
|
||||||
|
void InitQueryBlock();
|
||||||
|
|
||||||
|
[[nodiscard]] static uint64_t Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default=0);
|
||||||
|
[[nodiscard]] static std::string GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default="");
|
||||||
|
[[nodiscard]] static bool GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default=false);
|
||||||
|
[[nodiscard]] static uint64_t GetWhen(const Poco::JSON::Object::Ptr &Obj);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
BindingMap Bindings_;
|
||||||
|
Poco::URI::QueryParameters Parameters_;
|
||||||
|
Poco::Logger &Logger_;
|
||||||
|
std::string SessionToken_;
|
||||||
|
SecurityObjects::UserInfoAndPolicy UserInfo_;
|
||||||
|
std::vector<std::string> Methods_;
|
||||||
|
QueryBlock QB_;
|
||||||
|
bool Internal_=false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RESTAPI_UnknownRequestHandler : public RESTAPIHandler {
|
||||||
|
public:
|
||||||
|
RESTAPI_UnknownRequestHandler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L)
|
||||||
|
: RESTAPIHandler(bindings, L, std::vector<std::string>{}) {}
|
||||||
|
void handleRequest(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response) override {
|
||||||
|
if (!IsAuthorized(Request, Response))
|
||||||
|
return;
|
||||||
|
BadRequest(Request, Response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
constexpr auto test_has_PathName_method(T*)
|
||||||
|
-> decltype( T::PathName() , std::true_type{} )
|
||||||
|
{
|
||||||
|
return std::true_type{};
|
||||||
|
}
|
||||||
|
constexpr auto test_has_PathName_method(...) -> std::false_type
|
||||||
|
{
|
||||||
|
return std::false_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
RESTAPIHandler * RESTAPI_Router(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & Logger ) {
|
||||||
|
static_assert(test_has_PathName_method((T*)nullptr), "Class must have a static PathName() method.");
|
||||||
|
if(RESTAPIHandler::ParseBindings(RequestedPath,T::PathName(),Bindings)) {
|
||||||
|
return new T(Bindings, Logger, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (sizeof...(Args) == 0) {
|
||||||
|
return new RESTAPI_UnknownRequestHandler(Bindings,Logger);
|
||||||
|
} else {
|
||||||
|
return RESTAPI_Router<Args...>(RequestedPath, Bindings, Logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
RESTAPIHandler * RESTAPI_Router_I(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & Logger) {
|
||||||
|
static_assert(test_has_PathName_method((T*)nullptr), "Class must have a static PathName() method.");
|
||||||
|
if(RESTAPIHandler::ParseBindings(RequestedPath,T::PathName(),Bindings)) {
|
||||||
|
return new T(Bindings, Logger, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (sizeof...(Args) == 0) {
|
||||||
|
return new RESTAPI_UnknownRequestHandler(Bindings,Logger);
|
||||||
|
} else {
|
||||||
|
return RESTAPI_Router_I<Args...>(RequestedPath, Bindings, Logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //UCENTRAL_RESTAPI_HANDLER_H
|
||||||
128
src/RESTAPI_protocol.h
Normal file
128
src/RESTAPI_protocol.h
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_RESTAPI_PROTOCOL_H
|
||||||
|
#define UCENTRALGW_RESTAPI_PROTOCOL_H
|
||||||
|
|
||||||
|
namespace uCentral::RESTAPI::Protocol {
|
||||||
|
static const char * CAPABILITIES = "capabilities";
|
||||||
|
static const char * LOGS = "logs";
|
||||||
|
static const char * HEALTHCHECKS = "healthchecks";
|
||||||
|
static const char * STATISTICS = "statistics";
|
||||||
|
static const char * STATUS = "status";
|
||||||
|
static const char * SERIALNUMBER = "serialNumber";
|
||||||
|
static const char * PERFORM = "perform";
|
||||||
|
static const char * CONFIGURE = "configure";
|
||||||
|
static const char * UPGRADE = "upgrade";
|
||||||
|
static const char * REBOOT = "reboot";
|
||||||
|
static const char * FACTORY = "factory";
|
||||||
|
static const char * LEDS = "leds";
|
||||||
|
static const char * TRACE = "trace";
|
||||||
|
static const char * REQUEST = "request";
|
||||||
|
static const char * WIFISCAN = "wifiscan";
|
||||||
|
static const char * EVENTQUEUE = "eventqueue";
|
||||||
|
static const char * RTTY = "rtty";
|
||||||
|
static const char * COMMAND = "command";
|
||||||
|
static const char * STARTDATE = "startDate";
|
||||||
|
static const char * ENDDATE = "endDate";
|
||||||
|
static const char * OFFSET = "offset";
|
||||||
|
static const char * LIMIT = "limit";
|
||||||
|
static const char * LIFETIME = "lifetime";
|
||||||
|
static const char * UUID = "UUID";
|
||||||
|
static const char * DATA = "data";
|
||||||
|
static const char * CONFIGURATION = "configuration";
|
||||||
|
static const char * WHEN = "when";
|
||||||
|
static const char * URI = "uri";
|
||||||
|
static const char * LOGTYPE = "logType";
|
||||||
|
static const char * VALUES = "values";
|
||||||
|
static const char * TYPES = "types";
|
||||||
|
static const char * PAYLOAD = "payload";
|
||||||
|
static const char * KEEPREDIRECTOR = "keepRedirector";
|
||||||
|
static const char * NETWORK = "network";
|
||||||
|
static const char * INTERFACE = "interface";
|
||||||
|
static const char * BANDS = "bands";
|
||||||
|
static const char * CHANNELS = "channels";
|
||||||
|
static const char * VERBOSE = "verbose";
|
||||||
|
static const char * MESSAGE = "message";
|
||||||
|
static const char * STATE = "state";
|
||||||
|
static const char * HEALTHCHECK = "healthcheck";
|
||||||
|
static const char * PCAP_FILE_TYPE = "pcap";
|
||||||
|
static const char * DURATION = "duration";
|
||||||
|
static const char * NUMBEROFPACKETS = "numberOfPackets";
|
||||||
|
static const char * FILTER = "filter";
|
||||||
|
static const char * SELECT = "select";
|
||||||
|
static const char * SERIALONLY = "serialOnly";
|
||||||
|
static const char * COUNTONLY = "countOnly";
|
||||||
|
static const char * DEVICEWITHSTATUS = "deviceWithStatus";
|
||||||
|
static const char * DEVICESWITHSTATUS = "devicesWithStatus";
|
||||||
|
static const char * DEVICES = "devices";
|
||||||
|
static const char * COUNT = "count";
|
||||||
|
static const char * SERIALNUMBERS = "serialNumbers";
|
||||||
|
static const char * CONFIGURATIONS = "configurations";
|
||||||
|
static const char * NAME = "name";
|
||||||
|
static const char * COMMANDS = "commands";
|
||||||
|
static const char * COMMANDUUID = "commandUUID";
|
||||||
|
static const char * FIRMWARES = "firmwares";
|
||||||
|
static const char * TOPIC = "topic";
|
||||||
|
static const char * REASON = "reason";
|
||||||
|
static const char * FILEUUID = "uuid";
|
||||||
|
static const char * USERID = "userId";
|
||||||
|
static const char * PASSWORD = "password";
|
||||||
|
static const char * TOKEN = "token";
|
||||||
|
static const char * SETLOGLEVEL = "setloglevel";
|
||||||
|
static const char * GETLOGLEVELS = "getloglevels";
|
||||||
|
static const char * GETSUBSYSTEMNAMES = "getsubsystemnames";
|
||||||
|
static const char * GETLOGLEVELNAMES = "getloglevelnames";
|
||||||
|
static const char * STATS = "stats";
|
||||||
|
static const char * PARAMETERS = "parameters";
|
||||||
|
static const char * VALUE = "value";
|
||||||
|
static const char * LASTONLY = "lastOnly";
|
||||||
|
static const char * NEWEST = "newest";
|
||||||
|
static const char * ACTIVESCAN = "activeScan";
|
||||||
|
static const char * LIST = "list";
|
||||||
|
static const char * TAG = "tag";
|
||||||
|
static const char * TAGLIST = "tagList";
|
||||||
|
static const char * DESCRIPTION = "description";
|
||||||
|
static const char * NOTES = "notes";
|
||||||
|
static const char * DEVICETYPE = "deviceType";
|
||||||
|
static const char * REVISION = "revision";
|
||||||
|
static const char * AGES = "ages";
|
||||||
|
static const char * REVISIONS = "revisions";
|
||||||
|
static const char * DEVICETYPES = "deviceTypes";
|
||||||
|
static const char * LATESTONLY = "latestOnly";
|
||||||
|
static const char * IDONLY = "idOnly";
|
||||||
|
static const char * REVISIONSET = "revisionSet";
|
||||||
|
static const char * DEVICESET = "deviceSet";
|
||||||
|
static const char * HISTORY = "history";
|
||||||
|
static const char * ID = "id";
|
||||||
|
static const char * VERSION = "version";
|
||||||
|
static const char * TIMES = "times";
|
||||||
|
static const char * UPTIME = "uptime";
|
||||||
|
static const char * START = "start";
|
||||||
|
|
||||||
|
static const char * NEWPASSWORD = "newPassword";
|
||||||
|
static const char * USERS = "users";
|
||||||
|
|
||||||
|
static const char * ERRORTEXT = "errorText";
|
||||||
|
static const char * ERRORCODE = "errorCode";
|
||||||
|
static const char * AVATARID = "avatarId";
|
||||||
|
static const char * UNNAMED = "(unnamed)";
|
||||||
|
static const char * UNSPECIFIED = "(unspecified)";
|
||||||
|
static const char * CONTENTDISPOSITION = "Content-Disposition";
|
||||||
|
static const char * CONTENTTYPE = "Content-Type";
|
||||||
|
|
||||||
|
static const char * REQUIREMENTS = "requirements";
|
||||||
|
static const char * PASSWORDPATTERN = "passwordPattern";
|
||||||
|
static const char * ACCESSPOLICY = "accessPolicy";
|
||||||
|
static const char * PASSWORDPOLICY = "passwordPolicy";
|
||||||
|
static const char * FORGOTPASSWORD = "forgotPassword";
|
||||||
|
static const char * ME = "me";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // UCENTRALGW_RESTAPI_PROTOCOL_H
|
||||||
73
src/RESTAPI_server.cpp
Normal file
73
src/RESTAPI_server.cpp
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-05-09.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Poco/URI.h"
|
||||||
|
|
||||||
|
#include "RESTAPI_server.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "RESTAPI_handler.h"
|
||||||
|
|
||||||
|
#include "RESTAPI_system_command.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class RESTAPI_server *RESTAPI_server::instance_ = nullptr;
|
||||||
|
|
||||||
|
RESTAPI_server::RESTAPI_server() noexcept:
|
||||||
|
SubSystemServer("RESTAPIServer", "RESTAPIServer", "ucentralfws.restapi")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int RESTAPI_server::Start() {
|
||||||
|
Logger_.information("Starting.");
|
||||||
|
|
||||||
|
for(const auto & Svr: ConfigServersList_) {
|
||||||
|
Logger_.information(Poco::format("Starting: %s:%s Keyfile:%s CertFile: %s", Svr.Address(), std::to_string(Svr.Port()),
|
||||||
|
Svr.KeyFile(),Svr.CertFile()));
|
||||||
|
|
||||||
|
auto Sock{Svr.CreateSecureSocket(Logger_)};
|
||||||
|
|
||||||
|
Svr.LogCert(Logger_);
|
||||||
|
if(!Svr.RootCA().empty())
|
||||||
|
Svr.LogCas(Logger_);
|
||||||
|
|
||||||
|
auto Params = new Poco::Net::HTTPServerParams;
|
||||||
|
Params->setMaxThreads(50);
|
||||||
|
Params->setMaxQueued(200);
|
||||||
|
Params->setKeepAlive(true);
|
||||||
|
uint64_t T = 45000;
|
||||||
|
Params->setKeepAliveTimeout(T);
|
||||||
|
Params->setMaxKeepAliveRequests(200);
|
||||||
|
Params->setTimeout(T + 10000);
|
||||||
|
|
||||||
|
auto NewServer = std::make_unique<Poco::Net::HTTPServer>(new RequestHandlerFactory, Pool_, Sock, Params);
|
||||||
|
NewServer->start();
|
||||||
|
RESTServers_.push_back(std::move(NewServer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Poco::Net::HTTPRequestHandler *RequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest & Request) {
|
||||||
|
|
||||||
|
Logger_.debug(Poco::format("REQUEST(%s): %s %s", uCentral::Utils::FormatIPv6(Request.clientAddress().toString()), Request.getMethod(), Request.getURI()));
|
||||||
|
|
||||||
|
Poco::URI uri(Request.getURI());
|
||||||
|
auto *Path = uri.getPath().c_str();
|
||||||
|
RESTAPIHandler::BindingMap Bindings;
|
||||||
|
|
||||||
|
// std::cout << "Path: " << Request.getURI() << std::endl;
|
||||||
|
|
||||||
|
return RESTAPI_Router<
|
||||||
|
RESTAPI_system_command
|
||||||
|
>(Path,Bindings,Logger_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPI_server::Stop() {
|
||||||
|
Logger_.information("Stopping ");
|
||||||
|
for( const auto & svr : RESTServers_ )
|
||||||
|
svr->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
51
src/RESTAPI_server.h
Normal file
51
src/RESTAPI_server.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-05-09.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALFWS_RESTAPI_SERVER_H
|
||||||
|
#define UCENTRALFWS_RESTAPI_SERVER_H
|
||||||
|
|
||||||
|
#include "Poco/Net/HTTPServer.h"
|
||||||
|
#include "Poco/Net/HTTPRequestHandler.h"
|
||||||
|
#include "Poco/Net/HTTPRequestHandlerFactory.h"
|
||||||
|
#include "Poco/Net/HTTPServerRequest.h"
|
||||||
|
#include "Poco/Net/NetException.h"
|
||||||
|
|
||||||
|
#include "SubSystemServer.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class RESTAPI_server : public SubSystemServer {
|
||||||
|
|
||||||
|
public:
|
||||||
|
RESTAPI_server() noexcept;
|
||||||
|
|
||||||
|
static RESTAPI_server *instance() {
|
||||||
|
if (instance_ == nullptr) {
|
||||||
|
instance_ = new RESTAPI_server;
|
||||||
|
}
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
int Start() override;
|
||||||
|
void Stop() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static RESTAPI_server *instance_;
|
||||||
|
std::vector<std::unique_ptr<Poco::Net::HTTPServer>> RESTServers_;
|
||||||
|
Poco::ThreadPool Pool_;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline RESTAPI_server * RESTAPI_server() { return RESTAPI_server::instance(); };
|
||||||
|
|
||||||
|
class RequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory {
|
||||||
|
public:
|
||||||
|
RequestHandlerFactory() :
|
||||||
|
Logger_(RESTAPI_server::instance()->Logger()){}
|
||||||
|
|
||||||
|
Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &request) override;
|
||||||
|
private:
|
||||||
|
Poco::Logger & Logger_;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //UCENTRALFWS_RESTAPI_SERVER_H
|
||||||
132
src/RESTAPI_system_command.cpp
Normal file
132
src/RESTAPI_system_command.cpp
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
#include "RESTAPI_system_command.h"
|
||||||
|
|
||||||
|
#include "Poco/Exception.h"
|
||||||
|
#include "Poco/JSON/Parser.h"
|
||||||
|
|
||||||
|
#include "Daemon.h"
|
||||||
|
#include "RESTAPI_protocol.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
void RESTAPI_system_command::handleRequest(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
|
||||||
|
if (!ContinueProcessing(Request, Response))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!IsAuthorized(Request, Response))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
|
||||||
|
DoPost(Request, Response);
|
||||||
|
else if(Request.getMethod()==Poco::Net::HTTPRequest::HTTP_GET)
|
||||||
|
DoGet(Request, Response);
|
||||||
|
|
||||||
|
BadRequest(Request, Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPI_system_command::DoPost(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
try {
|
||||||
|
Poco::JSON::Parser parser;
|
||||||
|
auto Obj = parser.parse(Request.stream()).extract<Poco::JSON::Object::Ptr>();
|
||||||
|
|
||||||
|
if (Obj->has(uCentral::RESTAPI::Protocol::COMMAND)) {
|
||||||
|
auto Command = Poco::toLower(Obj->get(uCentral::RESTAPI::Protocol::COMMAND).toString());
|
||||||
|
if (Command == uCentral::RESTAPI::Protocol::SETLOGLEVEL) {
|
||||||
|
if (Obj->has(uCentral::RESTAPI::Protocol::PARAMETERS) &&
|
||||||
|
Obj->isArray(uCentral::RESTAPI::Protocol::PARAMETERS)) {
|
||||||
|
auto ParametersBlock = Obj->getArray(uCentral::RESTAPI::Protocol::PARAMETERS);
|
||||||
|
for (const auto &i:*ParametersBlock) {
|
||||||
|
Poco::JSON::Parser pp;
|
||||||
|
auto InnerObj = pp.parse(i).extract<Poco::JSON::Object::Ptr>();
|
||||||
|
if (InnerObj->has(uCentral::RESTAPI::Protocol::TAG) &&
|
||||||
|
InnerObj->has(uCentral::RESTAPI::Protocol::VALUE)) {
|
||||||
|
auto Name = GetS(uCentral::RESTAPI::Protocol::TAG, InnerObj);
|
||||||
|
auto Value = GetS(uCentral::RESTAPI::Protocol::VALUE, InnerObj);
|
||||||
|
Daemon()->SetSubsystemLogLevel(Name, Value);
|
||||||
|
Logger_.information(Poco::format("Setting log level for %s at %s", Name, Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OK(Request, Response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (Command == uCentral::RESTAPI::Protocol::GETLOGLEVELS) {
|
||||||
|
auto CurrentLogLevels = Daemon()->GetLogLevels();
|
||||||
|
Poco::JSON::Object Result;
|
||||||
|
Poco::JSON::Array Array;
|
||||||
|
for(auto &[Name,Level]:CurrentLogLevels) {
|
||||||
|
Poco::JSON::Object Pair;
|
||||||
|
Pair.set( uCentral::RESTAPI::Protocol::TAG,Name);
|
||||||
|
Pair.set(uCentral::RESTAPI::Protocol::VALUE,Level);
|
||||||
|
Array.add(Pair);
|
||||||
|
}
|
||||||
|
Result.set(uCentral::RESTAPI::Protocol::TAGLIST,Array);
|
||||||
|
ReturnObject(Request,Result,Response);
|
||||||
|
return;
|
||||||
|
} else if (Command == uCentral::RESTAPI::Protocol::GETLOGLEVELNAMES) {
|
||||||
|
Poco::JSON::Object Result;
|
||||||
|
Poco::JSON::Array LevelNamesArray;
|
||||||
|
const Types::StringVec & LevelNames = Daemon()->GetLogLevelNames();
|
||||||
|
for(const auto &i:LevelNames)
|
||||||
|
LevelNamesArray.add(i);
|
||||||
|
Result.set(uCentral::RESTAPI::Protocol::LIST,LevelNamesArray);
|
||||||
|
ReturnObject(Request,Result,Response);
|
||||||
|
return;
|
||||||
|
} else if (Command == uCentral::RESTAPI::Protocol::GETSUBSYSTEMNAMES) {
|
||||||
|
Poco::JSON::Object Result;
|
||||||
|
Poco::JSON::Array LevelNamesArray;
|
||||||
|
const Types::StringVec & SubSystemNames = Daemon()->GetSubSystems();
|
||||||
|
for(const auto &i:SubSystemNames)
|
||||||
|
LevelNamesArray.add(i);
|
||||||
|
Result.set(uCentral::RESTAPI::Protocol::LIST,LevelNamesArray);
|
||||||
|
ReturnObject(Request,Result,Response);
|
||||||
|
return;
|
||||||
|
} else if (Command == uCentral::RESTAPI::Protocol::STATS) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(const Poco::Exception &E) {
|
||||||
|
Logger_.log(E);
|
||||||
|
}
|
||||||
|
BadRequest(Request, Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RESTAPI_system_command::DoGet(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response) {
|
||||||
|
try {
|
||||||
|
ParseParameters(Request);
|
||||||
|
auto Command = GetParameter(RESTAPI::Protocol::COMMAND, "");
|
||||||
|
if (!Poco::icompare(Command, RESTAPI::Protocol::VERSION)) {
|
||||||
|
Poco::JSON::Object Answer;
|
||||||
|
Answer.set(RESTAPI::Protocol::TAG, RESTAPI::Protocol::VERSION);
|
||||||
|
Answer.set(RESTAPI::Protocol::VALUE, Daemon()->Version());
|
||||||
|
ReturnObject(Request, Answer, Response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Poco::icompare(Command, RESTAPI::Protocol::TIMES)) {
|
||||||
|
Poco::JSON::Array Array;
|
||||||
|
Poco::JSON::Object Answer;
|
||||||
|
Poco::JSON::Object UpTimeObj;
|
||||||
|
UpTimeObj.set(RESTAPI::Protocol::TAG,RESTAPI::Protocol::UPTIME);
|
||||||
|
UpTimeObj.set(RESTAPI::Protocol::VALUE, Daemon()->uptime().totalSeconds());
|
||||||
|
Poco::JSON::Object StartObj;
|
||||||
|
StartObj.set(RESTAPI::Protocol::TAG,RESTAPI::Protocol::START);
|
||||||
|
StartObj.set(RESTAPI::Protocol::VALUE, Daemon()->startTime().epochTime());
|
||||||
|
Array.add(UpTimeObj);
|
||||||
|
Array.add(StartObj);
|
||||||
|
Answer.set(RESTAPI::Protocol::TIMES, Array);
|
||||||
|
ReturnObject(Request, Answer, Response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (const Poco::Exception &E) {
|
||||||
|
Logger_.log(E);
|
||||||
|
}
|
||||||
|
BadRequest(Request, Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
32
src/RESTAPI_system_command.h
Normal file
32
src/RESTAPI_system_command.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_RESTAPI_SYSTEM_COMMAND_H
|
||||||
|
#define UCENTRALGW_RESTAPI_SYSTEM_COMMAND_H
|
||||||
|
|
||||||
|
#include "RESTAPI_handler.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
class RESTAPI_system_command : public RESTAPIHandler {
|
||||||
|
public:
|
||||||
|
RESTAPI_system_command(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, bool Internal)
|
||||||
|
: RESTAPIHandler(bindings, L,
|
||||||
|
std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST,
|
||||||
|
Poco::Net::HTTPRequest::HTTP_GET,
|
||||||
|
Poco::Net::HTTPRequest::HTTP_OPTIONS},
|
||||||
|
Internal) {}
|
||||||
|
void handleRequest(Poco::Net::HTTPServerRequest &request,
|
||||||
|
Poco::Net::HTTPServerResponse &response) override;
|
||||||
|
static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/system"};}
|
||||||
|
void DoGet(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response);
|
||||||
|
void DoPost(Poco::Net::HTTPServerRequest &Request,
|
||||||
|
Poco::Net::HTTPServerResponse &Response);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif // UCENTRALGW_RESTAPI_SYSTEM_COMMAND_H
|
||||||
17
src/RESTAPI_utils.cpp
Normal file
17
src/RESTAPI_utils.cpp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-07-05.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "RESTAPI_utils.h"
|
||||||
|
|
||||||
|
namespace uCentral::RESTAPI_utils {
|
||||||
|
|
||||||
|
void EmbedDocument(const std::string & ObjName, Poco::JSON::Object & Obj, const std::string &ObjStr) {
|
||||||
|
std::string D = ObjStr.empty() ? "{}" : ObjStr;
|
||||||
|
Poco::JSON::Parser P;
|
||||||
|
Poco::Dynamic::Var result = P.parse(D);
|
||||||
|
const auto &DetailsObj = result.extract<Poco::JSON::Object::Ptr>();
|
||||||
|
Obj.set(ObjName, DetailsObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
216
src/RESTAPI_utils.h
Normal file
216
src/RESTAPI_utils.h
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-07-05.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_RESTAPI_UTILS_H
|
||||||
|
#define UCENTRALGW_RESTAPI_UTILS_H
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "Poco/JSON/Object.h"
|
||||||
|
#include "Poco/JSON/Parser.h"
|
||||||
|
#include "Poco/Net/HTTPServerRequest.h"
|
||||||
|
#include "uCentralTypes.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
namespace uCentral::RESTAPI_utils {
|
||||||
|
|
||||||
|
void EmbedDocument(const std::string & ObjName, Poco::JSON::Object & Obj, const std::string &ObjStr);
|
||||||
|
|
||||||
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, bool V) {
|
||||||
|
Obj.set(Field,V);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const std::string & S) {
|
||||||
|
Obj.set(Field,S);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const char * S) {
|
||||||
|
Obj.set(Field,S);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, uint64_t V) {
|
||||||
|
Obj.set(Field,V);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::StringVec &V) {
|
||||||
|
Poco::JSON::Array A;
|
||||||
|
for(const auto &i:V)
|
||||||
|
A.add(i);
|
||||||
|
Obj.set(Field,A);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::CountedMap &M) {
|
||||||
|
Poco::JSON::Array A;
|
||||||
|
for(const auto &[Key,Value]:M) {
|
||||||
|
Poco::JSON::Object O;
|
||||||
|
O.set("tag",Key);
|
||||||
|
O.set("value", Value);
|
||||||
|
A.add(O);
|
||||||
|
}
|
||||||
|
Obj.set(Field,A);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename T> void field_to_json(Poco::JSON::Object &Obj,
|
||||||
|
const char *Field,
|
||||||
|
const T &V,
|
||||||
|
std::function<std::string(const T &)> F) {
|
||||||
|
Obj.set(Field, F(V));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T> bool field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, T & V,
|
||||||
|
std::function<T(const std::string &)> F) {
|
||||||
|
if(Obj->has(Field))
|
||||||
|
V = F(Obj->get(Field).toString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, std::string &S) {
|
||||||
|
if(Obj->has(Field))
|
||||||
|
S = Obj->get(Field).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, uint64_t &V) {
|
||||||
|
if(Obj->has(Field))
|
||||||
|
V = Obj->get(Field);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, bool &V) {
|
||||||
|
if(Obj->has(Field))
|
||||||
|
V = (Obj->get(Field).toString() == "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, Types::StringVec &V) {
|
||||||
|
if(Obj->isArray(Field)) {
|
||||||
|
V.clear();
|
||||||
|
Poco::JSON::Array::Ptr A = Obj->getArray(Field);
|
||||||
|
for(const auto &i:*A) {
|
||||||
|
V.push_back(i.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> void field_to_json(Poco::JSON::Object &Obj, const char *Field, const std::vector<T> &Value) {
|
||||||
|
Poco::JSON::Array Arr;
|
||||||
|
for(const auto &i:Value) {
|
||||||
|
Poco::JSON::Object AO;
|
||||||
|
i.to_json(AO);
|
||||||
|
Arr.add(AO);
|
||||||
|
}
|
||||||
|
Obj.set(Field, Arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, std::vector<T> &Value) {
|
||||||
|
if(Obj->isArray(Field)) {
|
||||||
|
Poco::JSON::Array::Ptr Arr = Obj->getArray(Field);
|
||||||
|
for(auto &i:*Arr) {
|
||||||
|
auto InnerObj = i.extract<Poco::JSON::Object::Ptr>();
|
||||||
|
T NewItem;
|
||||||
|
NewItem.from_json(InnerObj);
|
||||||
|
Value.push_back(NewItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, T &Value) {
|
||||||
|
if(Obj->isObject(Field)) {
|
||||||
|
Poco::JSON::Object::Ptr A = Obj->getObject(Field);
|
||||||
|
Value.from_json(A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string to_string(const Types::StringVec & ObjectArray) {
|
||||||
|
Poco::JSON::Array OutputArr;
|
||||||
|
if(ObjectArray.empty())
|
||||||
|
return "[]";
|
||||||
|
for(auto const &i:ObjectArray) {
|
||||||
|
OutputArr.add(i);
|
||||||
|
}
|
||||||
|
std::ostringstream OS;
|
||||||
|
Poco::JSON::Stringifier::condense(OutputArr,OS);
|
||||||
|
return OS.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> std::string to_string(const std::vector<T> & ObjectArray) {
|
||||||
|
Poco::JSON::Array OutputArr;
|
||||||
|
if(ObjectArray.empty())
|
||||||
|
return "[]";
|
||||||
|
for(auto const &i:ObjectArray) {
|
||||||
|
Poco::JSON::Object O;
|
||||||
|
i.to_json(O);
|
||||||
|
OutputArr.add(O);
|
||||||
|
}
|
||||||
|
std::ostringstream OS;
|
||||||
|
Poco::JSON::Stringifier::condense(OutputArr,OS);
|
||||||
|
return OS.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> std::string to_string(const T & Object) {
|
||||||
|
Poco::JSON::Object OutputObj;
|
||||||
|
Object.to_json(OutputObj);
|
||||||
|
std::ostringstream OS;
|
||||||
|
Poco::JSON::Stringifier::condense(OutputObj,OS);
|
||||||
|
return OS.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Types::StringVec to_object_array(const std::string & ObjectString) {
|
||||||
|
|
||||||
|
Types::StringVec Result;
|
||||||
|
if(ObjectString.empty())
|
||||||
|
return Result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Poco::JSON::Parser P;
|
||||||
|
auto Object = P.parse(ObjectString).template extract<Poco::JSON::Array::Ptr>();
|
||||||
|
for (auto const i : *Object) {
|
||||||
|
Result.push_back(i.toString());
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> std::vector<T> to_object_array(const std::string & ObjectString) {
|
||||||
|
|
||||||
|
std::vector<T> Result;
|
||||||
|
if(ObjectString.empty())
|
||||||
|
return Result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Poco::JSON::Parser P;
|
||||||
|
auto Object = P.parse(ObjectString).template extract<Poco::JSON::Array::Ptr>();
|
||||||
|
for (auto const i : *Object) {
|
||||||
|
auto InnerObject = i.template extract<Poco::JSON::Object::Ptr>();
|
||||||
|
T Obj;
|
||||||
|
Obj.from_json(InnerObject);
|
||||||
|
Result.push_back(Obj);
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> T to_object(const std::string & ObjectString) {
|
||||||
|
T Result;
|
||||||
|
|
||||||
|
if(ObjectString.empty())
|
||||||
|
return Result;
|
||||||
|
|
||||||
|
Poco::JSON::Parser P;
|
||||||
|
auto Object = P.parse(ObjectString).template extract<Poco::JSON::Object::Ptr>();
|
||||||
|
Result.from_json(Object);
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> bool from_request(T & Obj, Poco::Net::HTTPServerRequest &Request) {
|
||||||
|
Poco::JSON::Parser IncomingParser;
|
||||||
|
auto RawObject = IncomingParser.parse(Request.stream()).extract<Poco::JSON::Object::Ptr>();
|
||||||
|
Obj.from_json(RawObject);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // UCENTRALGW_RESTAPI_UTILS_H
|
||||||
69
src/StorageService.cpp
Normal file
69
src/StorageService.cpp
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "StorageService.h"
|
||||||
|
#include "Daemon.h"
|
||||||
|
#include "Poco/Util/Application.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class Storage *Storage::instance_ = nullptr;
|
||||||
|
|
||||||
|
Storage::Storage() noexcept:
|
||||||
|
SubSystemServer("Storage", "STORAGE-SVR", "storage")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Storage::ConvertParams(const std::string & S) const {
|
||||||
|
std::string R;
|
||||||
|
|
||||||
|
R.reserve(S.size()*2+1);
|
||||||
|
|
||||||
|
if(dbType_==pgsql) {
|
||||||
|
auto Idx=1;
|
||||||
|
for(auto const & i:S)
|
||||||
|
{
|
||||||
|
if(i=='?') {
|
||||||
|
R += '$';
|
||||||
|
R.append(std::to_string(Idx++));
|
||||||
|
} else {
|
||||||
|
R += i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
R = S;
|
||||||
|
}
|
||||||
|
return R;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Storage::Start() {
|
||||||
|
SubMutexGuard Guard(Mutex_);
|
||||||
|
|
||||||
|
Logger_.setLevel(Poco::Message::PRIO_NOTICE);
|
||||||
|
Logger_.notice("Starting.");
|
||||||
|
std::string DBType = Daemon()->ConfigGetString("storage.type");
|
||||||
|
|
||||||
|
if (DBType == "sqlite") {
|
||||||
|
Setup_SQLite();
|
||||||
|
} else if (DBType == "postgresql") {
|
||||||
|
Setup_PostgreSQL();
|
||||||
|
} else if (DBType == "mysql") {
|
||||||
|
Setup_MySQL();
|
||||||
|
}
|
||||||
|
|
||||||
|
Create_Tables();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Storage::Stop() {
|
||||||
|
Logger_.notice("Stopping.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// namespace
|
||||||
78
src/StorageService.h
Normal file
78
src/StorageService.h
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRAL_USTORAGESERVICE_H
|
||||||
|
#define UCENTRAL_USTORAGESERVICE_H
|
||||||
|
|
||||||
|
#include "Poco/Data/Session.h"
|
||||||
|
#include "Poco/Data/SessionPool.h"
|
||||||
|
#include "Poco/Data/SQLite/Connector.h"
|
||||||
|
|
||||||
|
#ifndef SMALL_BUILD
|
||||||
|
#include "Poco/Data/PostgreSQL/Connector.h"
|
||||||
|
#include "Poco/Data/MySQL/Connector.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "RESTAPI_TopoObjects.h"
|
||||||
|
#include "SubSystemServer.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
class Storage : public SubSystemServer {
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum StorageType {
|
||||||
|
sqlite,
|
||||||
|
pgsql,
|
||||||
|
mysql
|
||||||
|
};
|
||||||
|
|
||||||
|
enum CommandExecutionType {
|
||||||
|
COMMAND_PENDING,
|
||||||
|
COMMAND_EXECUTED,
|
||||||
|
COMMAND_COMPLETED
|
||||||
|
};
|
||||||
|
|
||||||
|
static Storage *instance() {
|
||||||
|
if (instance_ == nullptr) {
|
||||||
|
instance_ = new Storage;
|
||||||
|
}
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int Create_Tables();
|
||||||
|
|
||||||
|
int Start() override;
|
||||||
|
void Stop() override;
|
||||||
|
int Setup_SQLite();
|
||||||
|
[[nodiscard]] std::string ConvertParams(const std::string &S) const;
|
||||||
|
|
||||||
|
#ifndef SMALL_BUILD
|
||||||
|
int Setup_MySQL();
|
||||||
|
int Setup_PostgreSQL();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Storage *instance_;
|
||||||
|
std::unique_ptr<Poco::Data::SessionPool> Pool_= nullptr;
|
||||||
|
StorageType dbType_ = sqlite;
|
||||||
|
std::unique_ptr<Poco::Data::SQLite::Connector> SQLiteConn_= nullptr;
|
||||||
|
#ifndef SMALL_BUILD
|
||||||
|
std::unique_ptr<Poco::Data::PostgreSQL::Connector> PostgresConn_= nullptr;
|
||||||
|
std::unique_ptr<Poco::Data::MySQL::Connector> MySQLConn_= nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Storage() noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Storage * Storage() { return Storage::instance(); }
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif //UCENTRAL_USTORAGESERVICE_H
|
||||||
304
src/SubSystemServer.cpp
Normal file
304
src/SubSystemServer.cpp
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "SubSystemServer.h"
|
||||||
|
#include "Daemon.h"
|
||||||
|
|
||||||
|
#include "Poco/Net/X509Certificate.h"
|
||||||
|
#include "Poco/DateTimeFormatter.h"
|
||||||
|
#include "Poco/DateTimeFormat.h"
|
||||||
|
#include "Poco/Net/PrivateKeyPassphraseHandler.h"
|
||||||
|
#include "Poco/Net/SSLManager.h"
|
||||||
|
|
||||||
|
#include "openssl/ssl.h"
|
||||||
|
|
||||||
|
#include "Daemon.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
SubSystemServer::SubSystemServer(std::string Name, const std::string &LoggingPrefix,
|
||||||
|
std::string SubSystemConfigPrefix)
|
||||||
|
: Name_(std::move(Name)), Logger_(Poco::Logger::get(LoggingPrefix)),
|
||||||
|
SubSystemConfigPrefix_(std::move(SubSystemConfigPrefix)) {
|
||||||
|
Logger_.setLevel(Poco::Message::PRIO_NOTICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SubSystemServer::initialize(Poco::Util::Application &self) {
|
||||||
|
Logger_.notice("Initializing...");
|
||||||
|
auto i = 0;
|
||||||
|
bool good = true;
|
||||||
|
|
||||||
|
while (good) {
|
||||||
|
std::string root{SubSystemConfigPrefix_ + ".host." + std::to_string(i) + "."};
|
||||||
|
|
||||||
|
std::string address{root + "address"};
|
||||||
|
if (Daemon()->ConfigGetString(address, "").empty()) {
|
||||||
|
good = false;
|
||||||
|
} else {
|
||||||
|
std::string port{root + "port"};
|
||||||
|
std::string key{root + "key"};
|
||||||
|
std::string key_password{root + "key.password"};
|
||||||
|
std::string cert{root + "cert"};
|
||||||
|
std::string name{root + "name"};
|
||||||
|
std::string backlog{root + "backlog"};
|
||||||
|
std::string rootca{root + "rootca"};
|
||||||
|
std::string issuer{root + "issuer"};
|
||||||
|
std::string clientcas(root + "clientcas");
|
||||||
|
std::string cas{root + "cas"};
|
||||||
|
|
||||||
|
std::string level{root + "security"};
|
||||||
|
Poco::Net::Context::VerificationMode M = Poco::Net::Context::VERIFY_RELAXED;
|
||||||
|
|
||||||
|
auto L = Daemon()->ConfigGetString(level, "");
|
||||||
|
|
||||||
|
if (L == "strict") {
|
||||||
|
M = Poco::Net::Context::VERIFY_STRICT;
|
||||||
|
} else if (L == "none") {
|
||||||
|
M = Poco::Net::Context::VERIFY_NONE;
|
||||||
|
} else if (L == "relaxed") {
|
||||||
|
M = Poco::Net::Context::VERIFY_RELAXED;
|
||||||
|
} else if (L == "once")
|
||||||
|
M = Poco::Net::Context::VERIFY_ONCE;
|
||||||
|
|
||||||
|
PropertiesFileServerEntry entry(Daemon()->ConfigGetString(address, ""),
|
||||||
|
Daemon()->ConfigGetInt(port, 0),
|
||||||
|
Daemon()->ConfigPath(key, ""),
|
||||||
|
Daemon()->ConfigPath(cert, ""),
|
||||||
|
Daemon()->ConfigPath(rootca, ""),
|
||||||
|
Daemon()->ConfigPath(issuer, ""),
|
||||||
|
Daemon()->ConfigPath(clientcas, ""),
|
||||||
|
Daemon()->ConfigPath(cas, ""),
|
||||||
|
Daemon()->ConfigGetString(key_password, ""),
|
||||||
|
Daemon()->ConfigGetString(name, ""), M,
|
||||||
|
(int)Daemon()->ConfigGetInt(backlog, 64));
|
||||||
|
ConfigServersList_.push_back(entry);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SubSystemServer::uninitialize() {}
|
||||||
|
|
||||||
|
void SubSystemServer::reinitialize(Poco::Util::Application &self) {
|
||||||
|
// add your own reinitialization code here
|
||||||
|
}
|
||||||
|
|
||||||
|
void SubSystemServer::defineOptions(Poco::Util::OptionSet &options) {}
|
||||||
|
|
||||||
|
class MyPrivateKeyPassphraseHandler : public Poco::Net::PrivateKeyPassphraseHandler {
|
||||||
|
public:
|
||||||
|
explicit MyPrivateKeyPassphraseHandler(const std::string &Password, Poco::Logger & Logger):
|
||||||
|
PrivateKeyPassphraseHandler(true),
|
||||||
|
Logger_(Logger),
|
||||||
|
Password_(Password) {}
|
||||||
|
void onPrivateKeyRequested(const void * pSender,std::string & privateKey) {
|
||||||
|
Logger_.information("Returning key passphrase.");
|
||||||
|
privateKey = Password_;
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
std::string Password_;
|
||||||
|
Poco::Logger & Logger_;
|
||||||
|
};
|
||||||
|
|
||||||
|
Poco::Net::SecureServerSocket PropertiesFileServerEntry::CreateSecureSocket(Poco::Logger &L) const {
|
||||||
|
Poco::Net::Context::Params P;
|
||||||
|
|
||||||
|
P.verificationMode = level_;
|
||||||
|
P.verificationDepth = 9;
|
||||||
|
P.loadDefaultCAs = root_ca_.empty();
|
||||||
|
P.cipherList = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH";
|
||||||
|
P.dhUse2048Bits = true;
|
||||||
|
P.caLocation = cas_;
|
||||||
|
|
||||||
|
auto Context = Poco::AutoPtr<Poco::Net::Context>(new Poco::Net::Context(Poco::Net::Context::TLS_SERVER_USE, P));
|
||||||
|
|
||||||
|
if(!key_file_password_.empty()) {
|
||||||
|
auto PassphraseHandler = Poco::SharedPtr<MyPrivateKeyPassphraseHandler>( new MyPrivateKeyPassphraseHandler(key_file_password_,L));
|
||||||
|
Poco::Net::SSLManager::instance().initializeServer(PassphraseHandler, nullptr,Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cert_file_.empty() && !key_file_.empty()) {
|
||||||
|
Poco::Crypto::X509Certificate Cert(cert_file_);
|
||||||
|
Poco::Crypto::X509Certificate Root(root_ca_);
|
||||||
|
|
||||||
|
Context->useCertificate(Cert);
|
||||||
|
Context->addChainCertificate(Root);
|
||||||
|
|
||||||
|
Context->addCertificateAuthority(Root);
|
||||||
|
|
||||||
|
if (level_ == Poco::Net::Context::VERIFY_STRICT) {
|
||||||
|
if (issuer_cert_file_.empty()) {
|
||||||
|
L.fatal("In strict mode, you must supply ans issuer certificate");
|
||||||
|
}
|
||||||
|
if (client_cas_.empty()) {
|
||||||
|
L.fatal("In strict mode, client cas must be supplied");
|
||||||
|
}
|
||||||
|
Poco::Crypto::X509Certificate Issuing(issuer_cert_file_);
|
||||||
|
Context->addChainCertificate(Issuing);
|
||||||
|
Context->addCertificateAuthority(Issuing);
|
||||||
|
}
|
||||||
|
|
||||||
|
Poco::Crypto::RSAKey Key("", key_file_, key_file_password_);
|
||||||
|
Context->usePrivateKey(Key);
|
||||||
|
|
||||||
|
SSL_CTX *SSLCtx = Context->sslContext();
|
||||||
|
if (!SSL_CTX_check_private_key(SSLCtx)) {
|
||||||
|
L.fatal(Poco::format("Wrong Certificate(%s) for Key(%s)", cert_file_, key_file_));
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX_set_verify(SSLCtx, SSL_VERIFY_PEER, nullptr);
|
||||||
|
|
||||||
|
if (level_ == Poco::Net::Context::VERIFY_STRICT) {
|
||||||
|
SSL_CTX_set_client_CA_list(SSLCtx, SSL_load_client_CA_file(client_cas_.c_str()));
|
||||||
|
}
|
||||||
|
SSL_CTX_enable_ct(SSLCtx, SSL_CT_VALIDATION_STRICT);
|
||||||
|
SSL_CTX_dane_enable(SSLCtx);
|
||||||
|
|
||||||
|
Context->enableSessionCache();
|
||||||
|
Context->setSessionCacheSize(0);
|
||||||
|
Context->setSessionTimeout(10);
|
||||||
|
Context->enableExtendedCertificateVerification(true);
|
||||||
|
Context->disableStatelessSessionResumption();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address_ == "*") {
|
||||||
|
Poco::Net::IPAddress Addr(Poco::Net::IPAddress::wildcard(
|
||||||
|
Poco::Net::Socket::supportsIPv6() ? Poco::Net::AddressFamily::IPv6
|
||||||
|
: Poco::Net::AddressFamily::IPv4));
|
||||||
|
Poco::Net::SocketAddress SockAddr(Addr, port_);
|
||||||
|
|
||||||
|
return Poco::Net::SecureServerSocket(SockAddr, backlog_, Context);
|
||||||
|
} else {
|
||||||
|
Poco::Net::IPAddress Addr(address_);
|
||||||
|
Poco::Net::SocketAddress SockAddr(Addr, port_);
|
||||||
|
|
||||||
|
return Poco::Net::SecureServerSocket(SockAddr, backlog_, Context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertiesFileServerEntry::LogCertInfo(Poco::Logger &L,
|
||||||
|
const Poco::Crypto::X509Certificate &C) {
|
||||||
|
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
L.information(Poco::format("> Issuer: %s", C.issuerName()));
|
||||||
|
L.information("---------------------------------------------------------------------------------------------");
|
||||||
|
L.information(Poco::format("> Common Name: %s",
|
||||||
|
C.issuerName(Poco::Crypto::X509Certificate::NID_COMMON_NAME)));
|
||||||
|
L.information(Poco::format("> Country: %s",
|
||||||
|
C.issuerName(Poco::Crypto::X509Certificate::NID_COUNTRY)));
|
||||||
|
L.information(Poco::format("> Locality: %s",
|
||||||
|
C.issuerName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME)));
|
||||||
|
L.information(Poco::format("> State/Prov: %s",
|
||||||
|
C.issuerName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE)));
|
||||||
|
L.information(Poco::format("> Org name: %s",
|
||||||
|
C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME)));
|
||||||
|
L.information(
|
||||||
|
Poco::format("> Org unit: %s",
|
||||||
|
C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME)));
|
||||||
|
L.information(
|
||||||
|
Poco::format("> Email: %s",
|
||||||
|
C.issuerName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS)));
|
||||||
|
L.information(Poco::format("> Serial#: %s",
|
||||||
|
C.issuerName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER)));
|
||||||
|
L.information("---------------------------------------------------------------------------------------------");
|
||||||
|
L.information(Poco::format("> Subject: %s", C.subjectName()));
|
||||||
|
L.information("---------------------------------------------------------------------------------------------");
|
||||||
|
L.information(Poco::format("> Common Name: %s",
|
||||||
|
C.subjectName(Poco::Crypto::X509Certificate::NID_COMMON_NAME)));
|
||||||
|
L.information(Poco::format("> Country: %s",
|
||||||
|
C.subjectName(Poco::Crypto::X509Certificate::NID_COUNTRY)));
|
||||||
|
L.information(Poco::format("> Locality: %s",
|
||||||
|
C.subjectName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME)));
|
||||||
|
L.information(
|
||||||
|
Poco::format("> State/Prov: %s",
|
||||||
|
C.subjectName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE)));
|
||||||
|
L.information(
|
||||||
|
Poco::format("> Org name: %s",
|
||||||
|
C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME)));
|
||||||
|
L.information(
|
||||||
|
Poco::format("> Org unit: %s",
|
||||||
|
C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME)));
|
||||||
|
L.information(
|
||||||
|
Poco::format("> Email: %s",
|
||||||
|
C.subjectName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS)));
|
||||||
|
L.information(Poco::format("> Serial#: %s",
|
||||||
|
C.subjectName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER)));
|
||||||
|
L.information("---------------------------------------------------------------------------------------------");
|
||||||
|
L.information(Poco::format("> Signature Algo: %s", C.signatureAlgorithm()));
|
||||||
|
auto From = Poco::DateTimeFormatter::format(C.validFrom(), Poco::DateTimeFormat::HTTP_FORMAT);
|
||||||
|
L.information(Poco::format("> Valid from: %s", From));
|
||||||
|
auto Expires =
|
||||||
|
Poco::DateTimeFormatter::format(C.expiresOn(), Poco::DateTimeFormat::HTTP_FORMAT);
|
||||||
|
L.information(Poco::format("> Expires on: %s", Expires));
|
||||||
|
L.information(Poco::format("> Version: %d", (int)C.version()));
|
||||||
|
L.information(Poco::format("> Serial #: %s", C.serialNumber()));
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertiesFileServerEntry::LogCert(Poco::Logger &L) const {
|
||||||
|
try {
|
||||||
|
Poco::Crypto::X509Certificate C(cert_file_);
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
L.information(Poco::format("Certificate Filename: %s", cert_file_));
|
||||||
|
LogCertInfo(L, C);
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
|
||||||
|
if (!issuer_cert_file_.empty()) {
|
||||||
|
Poco::Crypto::X509Certificate C1(issuer_cert_file_);
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
L.information(Poco::format("Issues Certificate Filename: %s", issuer_cert_file_));
|
||||||
|
LogCertInfo(L, C1);
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!client_cas_.empty()) {
|
||||||
|
std::vector<Poco::Crypto::X509Certificate> Certs =
|
||||||
|
Poco::Net::X509Certificate::readPEM(client_cas_);
|
||||||
|
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
L.information(Poco::format("Client CAs Filename: %s", client_cas_));
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
auto i = 1;
|
||||||
|
for (const auto &C3 : Certs) {
|
||||||
|
L.information(Poco::format(" Index: %d", i));
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
LogCertInfo(L, C3);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const Poco::Exception &E) {
|
||||||
|
L.log(E);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertiesFileServerEntry::LogCas(Poco::Logger &L) const {
|
||||||
|
try {
|
||||||
|
std::vector<Poco::Crypto::X509Certificate> Certs =
|
||||||
|
Poco::Net::X509Certificate::readPEM(root_ca_);
|
||||||
|
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
L.information(Poco::format("CA Filename: %s", root_ca_));
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
auto i = 1;
|
||||||
|
for (const auto &C : Certs) {
|
||||||
|
L.information(Poco::format(" Index: %d", i));
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
LogCertInfo(L, C);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
L.information("=============================================================================================");
|
||||||
|
} catch (const Poco::Exception &E) {
|
||||||
|
L.log(E);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/SubSystemServer.h
Normal file
97
src/SubSystemServer.h
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRAL_SUBSYSTEMSERVER_H
|
||||||
|
#define UCENTRAL_SUBSYSTEMSERVER_H
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "Poco/Util/Application.h"
|
||||||
|
#include "Poco/Util/Option.h"
|
||||||
|
#include "Poco/Util/OptionSet.h"
|
||||||
|
#include "Poco/Util/HelpFormatter.h"
|
||||||
|
#include "Poco/Logger.h"
|
||||||
|
#include "Poco/Net/SecureServerSocket.h"
|
||||||
|
|
||||||
|
#include "Poco/Net/X509Certificate.h"
|
||||||
|
|
||||||
|
using SubMutex = std::recursive_mutex;
|
||||||
|
using SubMutexGuard = std::lock_guard<SubMutex>;
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
class PropertiesFileServerEntry {
|
||||||
|
public:
|
||||||
|
PropertiesFileServerEntry(std::string Address, uint32_t port, std::string Key_file,
|
||||||
|
std::string Cert_file, std::string RootCa, std::string Issuer,
|
||||||
|
std::string ClientCas, std::string Cas,
|
||||||
|
std::string Key_file_password = "", std::string Name = "",
|
||||||
|
Poco::Net::Context::VerificationMode M =
|
||||||
|
Poco::Net::Context::VerificationMode::VERIFY_RELAXED,
|
||||||
|
int backlog = 64)
|
||||||
|
: address_(std::move(Address)), port_(port), key_file_(std::move(Key_file)),
|
||||||
|
cert_file_(std::move(Cert_file)), root_ca_(std::move(RootCa)),
|
||||||
|
issuer_cert_file_(std::move(Issuer)), client_cas_(std::move(ClientCas)),
|
||||||
|
cas_(std::move(Cas)), key_file_password_(std::move(Key_file_password)),
|
||||||
|
name_(std::move(Name)), level_(M), backlog_(backlog){};
|
||||||
|
|
||||||
|
[[nodiscard]] const std::string &Address() const { return address_; };
|
||||||
|
[[nodiscard]] uint32_t Port() const { return port_; };
|
||||||
|
[[nodiscard]] const std::string &KeyFile() const { return key_file_; };
|
||||||
|
[[nodiscard]] const std::string &CertFile() const { return cert_file_; };
|
||||||
|
[[nodiscard]] const std::string &RootCA() const { return root_ca_; };
|
||||||
|
[[nodiscard]] const std::string &KeyFilePassword() const { return key_file_password_; };
|
||||||
|
[[nodiscard]] const std::string &IssuerCertFile() const { return issuer_cert_file_; };
|
||||||
|
[[nodiscard]] const std::string &Name() const { return name_; };
|
||||||
|
[[nodiscard]] Poco::Net::SecureServerSocket CreateSecureSocket(Poco::Logger &L) const;
|
||||||
|
[[nodiscard]] int Backlog() const { return backlog_; }
|
||||||
|
void LogCert(Poco::Logger &L) const;
|
||||||
|
void LogCas(Poco::Logger &L) const;
|
||||||
|
static void LogCertInfo(Poco::Logger &L, const Poco::Crypto::X509Certificate &C);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string address_;
|
||||||
|
std::string cert_file_;
|
||||||
|
std::string key_file_;
|
||||||
|
std::string root_ca_;
|
||||||
|
std::string key_file_password_;
|
||||||
|
std::string issuer_cert_file_;
|
||||||
|
std::string client_cas_;
|
||||||
|
std::string cas_;
|
||||||
|
uint32_t port_;
|
||||||
|
std::string name_;
|
||||||
|
int backlog_;
|
||||||
|
Poco::Net::Context::VerificationMode level_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SubSystemServer : public Poco::Util::Application::Subsystem {
|
||||||
|
|
||||||
|
public:
|
||||||
|
SubSystemServer(std::string Name, const std::string &LoggingName, std::string SubSystemPrefix);
|
||||||
|
void initialize(Poco::Util::Application &self) override;
|
||||||
|
void uninitialize() override;
|
||||||
|
void reinitialize(Poco::Util::Application &self) override;
|
||||||
|
void defineOptions(Poco::Util::OptionSet &options) override;
|
||||||
|
inline const std::string & Name() const { return Name_; };
|
||||||
|
const char * name() const override { return Name_.c_str(); }
|
||||||
|
|
||||||
|
const PropertiesFileServerEntry &Host(int index) { return ConfigServersList_[index]; };
|
||||||
|
Poco::Logger &Logger() { return Logger_; };
|
||||||
|
void SetLoggingLevel(Poco::Message::Priority NewPriority) { Logger_.setLevel(NewPriority); }
|
||||||
|
int GetLoggingLevel() { return Logger_.getLevel(); }
|
||||||
|
virtual int Start() = 0;
|
||||||
|
virtual void Stop() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
SubMutex Mutex_{};
|
||||||
|
Poco::Logger &Logger_;
|
||||||
|
std::string Name_;
|
||||||
|
std::vector<PropertiesFileServerEntry> ConfigServersList_;
|
||||||
|
std::string SubSystemConfigPrefix_;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif //UCENTRAL_SUBSYSTEMSERVER_H
|
||||||
554
src/Utils.cpp
Normal file
554
src/Utils.cpp
Normal file
@@ -0,0 +1,554 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <fstream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <regex>
|
||||||
|
#include <random>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include "Poco/Exception.h"
|
||||||
|
#include "Poco/DateTimeFormat.h"
|
||||||
|
#include "Poco/DateTimeFormatter.h"
|
||||||
|
#include "Poco/DateTime.h"
|
||||||
|
#include "Poco/DateTimeParser.h"
|
||||||
|
#include "Poco/StringTokenizer.h"
|
||||||
|
#include "Poco/Message.h"
|
||||||
|
#include "Poco/File.h"
|
||||||
|
#include "Poco/StreamCopier.h"
|
||||||
|
#include "Poco/Path.h"
|
||||||
|
|
||||||
|
#include "uCentralProtocol.h"
|
||||||
|
#include "Daemon.h"
|
||||||
|
|
||||||
|
namespace uCentral::Utils {
|
||||||
|
|
||||||
|
[[nodiscard]] bool ValidSerialNumber(const std::string &Serial) {
|
||||||
|
return ((Serial.size() < uCentralProtocol::SERIAL_NUMBER_LENGTH) &&
|
||||||
|
std::all_of(Serial.begin(),Serial.end(),[](auto i){return std::isxdigit(i);}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<std::string> Split(const std::string &List, char Delimiter ) {
|
||||||
|
std::vector<std::string> ReturnList;
|
||||||
|
|
||||||
|
unsigned long P=0;
|
||||||
|
|
||||||
|
while(P<List.size())
|
||||||
|
{
|
||||||
|
unsigned long P2 = List.find_first_of(Delimiter, P);
|
||||||
|
if(P2==std::string::npos) {
|
||||||
|
ReturnList.push_back(List.substr(P));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ReturnList.push_back(List.substr(P,P2-P));
|
||||||
|
P=P2+1;
|
||||||
|
}
|
||||||
|
return ReturnList;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string FormatIPv6(const std::string & I )
|
||||||
|
{
|
||||||
|
if(I.substr(0,8) == "[::ffff:")
|
||||||
|
{
|
||||||
|
unsigned long PClosingBracket = I.find_first_of(']');
|
||||||
|
|
||||||
|
std::string ip = I.substr(8, PClosingBracket-8);
|
||||||
|
std::string port = I.substr(PClosingBracket+1);
|
||||||
|
return ip + port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return I;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string SerialToMAC(const std::string &Serial) {
|
||||||
|
std::string R = Serial;
|
||||||
|
|
||||||
|
if(R.size()<12)
|
||||||
|
padTo(R,12,'0');
|
||||||
|
else if (R.size()>12)
|
||||||
|
R = R.substr(0,12);
|
||||||
|
|
||||||
|
char buf[18];
|
||||||
|
|
||||||
|
buf[0] = R[0]; buf[1] = R[1] ; buf[2] = ':' ;
|
||||||
|
buf[3] = R[2] ; buf[4] = R[3]; buf[5] = ':' ;
|
||||||
|
buf[6] = R[4]; buf[7] = R[5] ; buf[8] = ':' ;
|
||||||
|
buf[9] = R[6] ; buf[10]= R[7]; buf[11] = ':';
|
||||||
|
buf[12] = R[8] ; buf[13]= R[9]; buf[14] = ':';
|
||||||
|
buf[15] = R[10] ; buf[16]= R[11];buf[17] = 0;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string ToHex(const std::vector<unsigned char> & B) {
|
||||||
|
std::string R;
|
||||||
|
R.reserve(B.size()*2);
|
||||||
|
|
||||||
|
static const char hex[] = "0123456789abcdef";
|
||||||
|
|
||||||
|
for(const auto &i:B)
|
||||||
|
{
|
||||||
|
R += (hex[ (i & 0xf0) >> 4]);
|
||||||
|
R += (hex[ (i & 0x0f) ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return R;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static const char kEncodeLookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
inline static const char kPadCharacter = '=';
|
||||||
|
|
||||||
|
std::string base64encode(const byte *input, unsigned long size) {
|
||||||
|
std::string encoded;
|
||||||
|
encoded.reserve(((size / 3) + (size % 3 > 0)) * 4);
|
||||||
|
|
||||||
|
std::uint32_t temp;
|
||||||
|
|
||||||
|
std::size_t i;
|
||||||
|
|
||||||
|
int ee = (int)(size/3);
|
||||||
|
|
||||||
|
for (i = 0; i < 3*ee; ++i) {
|
||||||
|
temp = input[i++] << 16;
|
||||||
|
temp += input[i++] << 8;
|
||||||
|
temp += input[i];
|
||||||
|
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
|
||||||
|
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
|
||||||
|
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
|
||||||
|
encoded.append(1, kEncodeLookup[(temp & 0x0000003F)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (size % 3) {
|
||||||
|
case 1:
|
||||||
|
temp = input[i] << 16;
|
||||||
|
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
|
||||||
|
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
|
||||||
|
encoded.append(2, kPadCharacter);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
temp = input[i++] << 16;
|
||||||
|
temp += input[i] << 8;
|
||||||
|
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
|
||||||
|
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
|
||||||
|
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
|
||||||
|
encoded.append(1, kPadCharacter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<byte> base64decode(const std::string& input)
|
||||||
|
{
|
||||||
|
if(input.length() % 4)
|
||||||
|
throw std::runtime_error("Invalid base64 length!");
|
||||||
|
|
||||||
|
std::size_t padding{};
|
||||||
|
|
||||||
|
if(input.length())
|
||||||
|
{
|
||||||
|
if(input[input.length() - 1] == kPadCharacter) padding++;
|
||||||
|
if(input[input.length() - 2] == kPadCharacter) padding++;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<byte> decoded;
|
||||||
|
decoded.reserve(((input.length() / 4) * 3) - padding);
|
||||||
|
|
||||||
|
std::uint32_t temp{};
|
||||||
|
auto it = input.begin();
|
||||||
|
|
||||||
|
while(it < input.end())
|
||||||
|
{
|
||||||
|
for(std::size_t i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
temp <<= 6;
|
||||||
|
if (*it >= 0x41 && *it <= 0x5A) temp |= *it - 0x41;
|
||||||
|
else if(*it >= 0x61 && *it <= 0x7A) temp |= *it - 0x47;
|
||||||
|
else if(*it >= 0x30 && *it <= 0x39) temp |= *it + 0x04;
|
||||||
|
else if(*it == 0x2B) temp |= 0x3E;
|
||||||
|
else if(*it == 0x2F) temp |= 0x3F;
|
||||||
|
else if(*it == kPadCharacter)
|
||||||
|
{
|
||||||
|
switch(input.end() - it)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
decoded.push_back((temp >> 16) & 0x000000FF);
|
||||||
|
decoded.push_back((temp >> 8 ) & 0x000000FF);
|
||||||
|
return decoded;
|
||||||
|
case 2:
|
||||||
|
decoded.push_back((temp >> 10) & 0x000000FF);
|
||||||
|
return decoded;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Invalid padding in base64!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else throw std::runtime_error("Invalid character in base64!");
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded.push_back((temp >> 16) & 0x000000FF);
|
||||||
|
decoded.push_back((temp >> 8 ) & 0x000000FF);
|
||||||
|
decoded.push_back((temp ) & 0x000000FF);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string to_RFC3339(uint64_t t)
|
||||||
|
{
|
||||||
|
if(t==0)
|
||||||
|
return "";
|
||||||
|
return Poco::DateTimeFormatter::format(Poco::DateTime(Poco::Timestamp::fromEpochTime(t)), Poco::DateTimeFormat::ISO8601_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t from_RFC3339(const std::string &TimeString)
|
||||||
|
{
|
||||||
|
if(TimeString.empty() || TimeString=="0")
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
int TZ;
|
||||||
|
Poco::DateTime DT = Poco::DateTimeParser::parse(Poco::DateTimeFormat::ISO8601_FORMAT,TimeString,TZ);
|
||||||
|
return DT.timestamp().epochTime();
|
||||||
|
}
|
||||||
|
catch( const Poco::Exception & E )
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParseTime(const std::string &Time, int & Hours, int & Minutes, int & Seconds) {
|
||||||
|
Poco::StringTokenizer TimeTokens(Time,":",Poco::StringTokenizer::TOK_TRIM);
|
||||||
|
|
||||||
|
Hours = Minutes = Hours = 0 ;
|
||||||
|
if(TimeTokens.count()==1) {
|
||||||
|
Hours = std::atoi(TimeTokens[0].c_str());
|
||||||
|
} else if(TimeTokens.count()==2) {
|
||||||
|
Hours = std::atoi(TimeTokens[0].c_str());
|
||||||
|
Minutes = std::atoi(TimeTokens[1].c_str());
|
||||||
|
} else if(TimeTokens.count()==3) {
|
||||||
|
Hours = std::atoi(TimeTokens[0].c_str());
|
||||||
|
Minutes = std::atoi(TimeTokens[1].c_str());
|
||||||
|
Seconds = std::atoi(TimeTokens[2].c_str());
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ParseDate(const std::string &Time, int & Year, int & Month, int & Day) {
|
||||||
|
Poco::StringTokenizer DateTokens(Time,"-",Poco::StringTokenizer::TOK_TRIM);
|
||||||
|
|
||||||
|
Year = Month = Day = 0 ;
|
||||||
|
if(DateTokens.count()==3) {
|
||||||
|
Year = std::atoi(DateTokens[0].c_str());
|
||||||
|
Month = std::atoi(DateTokens[1].c_str());
|
||||||
|
Day = std::atoi(DateTokens[2].c_str());
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CompareTime( int H1, int H2, int M1, int M2, int S1, int S2) {
|
||||||
|
if(H1<H2)
|
||||||
|
return true;
|
||||||
|
if(H1>H2)
|
||||||
|
return false;
|
||||||
|
if(M1<M2)
|
||||||
|
return true;
|
||||||
|
if(M2>M1)
|
||||||
|
return false;
|
||||||
|
if(S1<=S2)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string LogLevelToString(int Level) {
|
||||||
|
switch(Level) {
|
||||||
|
case Poco::Message::PRIO_DEBUG: return "debug";
|
||||||
|
case Poco::Message::PRIO_INFORMATION: return "information";
|
||||||
|
case Poco::Message::PRIO_FATAL: return "fatal";
|
||||||
|
case Poco::Message::PRIO_WARNING: return "warning";
|
||||||
|
case Poco::Message::PRIO_NOTICE: return "notice";
|
||||||
|
case Poco::Message::PRIO_CRITICAL: return "critical";
|
||||||
|
case Poco::Message::PRIO_ERROR: return "error";
|
||||||
|
case Poco::Message::PRIO_TRACE: return "trace";
|
||||||
|
default: return "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SerialNumberMatch(const std::string &S1, const std::string &S2, int Bits) {
|
||||||
|
auto S1_i = SerialNumberToInt(S1);
|
||||||
|
auto S2_i = SerialNumberToInt(S2);
|
||||||
|
return ((S1_i>>Bits)==(S2_i>>Bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t SerialNumberToInt(const std::string & S) {
|
||||||
|
uint64_t R=0;
|
||||||
|
|
||||||
|
for(const auto &i:S)
|
||||||
|
if(i>='0' && i<='9') {
|
||||||
|
R <<= 4;
|
||||||
|
R += (i-'0');
|
||||||
|
} else if(i>='a' && i<='f') {
|
||||||
|
R <<= 4;
|
||||||
|
R += (i-'a') + 10 ;
|
||||||
|
} else if(i>='A' && i<='F') {
|
||||||
|
R <<= 4;
|
||||||
|
R += (i-'A') + 10 ;
|
||||||
|
}
|
||||||
|
return R;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t SerialNumberToOUI(const std::string & S) {
|
||||||
|
uint64_t Result = 0 ;
|
||||||
|
int Digits=0;
|
||||||
|
|
||||||
|
for(const auto &i:S) {
|
||||||
|
if(std::isxdigit(i)) {
|
||||||
|
if(i>='0' && i<='9') {
|
||||||
|
Result <<=4;
|
||||||
|
Result += i-'0';
|
||||||
|
} else if(i>='A' && i<='F') {
|
||||||
|
Result <<=4;
|
||||||
|
Result += i-'A'+10;
|
||||||
|
} else if(i>='a' && i<='f') {
|
||||||
|
Result <<=4;
|
||||||
|
Result += i-'a'+10;
|
||||||
|
}
|
||||||
|
Digits++;
|
||||||
|
if(Digits==6)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GetDefaultMacAsInt64() {
|
||||||
|
uint64_t Result=0;
|
||||||
|
auto IFaceList = Poco::Net::NetworkInterface::list();
|
||||||
|
|
||||||
|
for(const auto &iface:IFaceList) {
|
||||||
|
if(iface.isRunning() && !iface.isLoopback()) {
|
||||||
|
auto MAC = iface.macAddress();
|
||||||
|
for (auto const &i : MAC) {
|
||||||
|
Result <<= 8;
|
||||||
|
Result += (uint8_t)i;
|
||||||
|
}
|
||||||
|
if (Result != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveSystemId(uint64_t Id) {
|
||||||
|
try {
|
||||||
|
std::ofstream O;
|
||||||
|
O.open(Daemon()->DataDir() + "/system.id",std::ios::binary | std::ios::trunc);
|
||||||
|
O << Id;
|
||||||
|
O.close();
|
||||||
|
} catch (...)
|
||||||
|
{
|
||||||
|
std::cout << "Could not save system ID" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t InitializeSystemId() {
|
||||||
|
std::random_device RDev;
|
||||||
|
std::srand(RDev());
|
||||||
|
std::chrono::high_resolution_clock Clock;
|
||||||
|
auto Now = Clock.now().time_since_epoch().count();
|
||||||
|
auto S = (GetDefaultMacAsInt64() + std::rand() + Now) ;
|
||||||
|
SaveSystemId(S);
|
||||||
|
std::cout << "ID: " << S << std::endl;
|
||||||
|
return S;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GetSystemId() {
|
||||||
|
uint64_t ID=0;
|
||||||
|
|
||||||
|
// if the system ID file exists, open and read it.
|
||||||
|
Poco::File SID( Daemon()->DataDir() + "/system.id");
|
||||||
|
try {
|
||||||
|
if (SID.exists()) {
|
||||||
|
std::ifstream I;
|
||||||
|
I.open(SID.path());
|
||||||
|
I >> ID;
|
||||||
|
I.close();
|
||||||
|
if (ID == 0)
|
||||||
|
return InitializeSystemId();
|
||||||
|
return ID;
|
||||||
|
} else {
|
||||||
|
return InitializeSystemId();
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
return InitializeSystemId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ValidEMailAddress(const std::string &email) {
|
||||||
|
// define a regular expression
|
||||||
|
const std::regex pattern
|
||||||
|
("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+");
|
||||||
|
|
||||||
|
// try to match the string with the regular expression
|
||||||
|
return std::regex_match(email, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string LoadFile( const Poco::File & F) {
|
||||||
|
std::string Result;
|
||||||
|
try {
|
||||||
|
std::ostringstream OS;
|
||||||
|
std::ifstream IF(F.path());
|
||||||
|
Poco::StreamCopier::copyStream(IF, OS);
|
||||||
|
Result = OS.str();
|
||||||
|
} catch (...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReplaceVariables( std::string & Content , const Types::StringPairVec & P) {
|
||||||
|
for(const auto &[Variable,Value]:P) {
|
||||||
|
Poco::replaceInPlace(Content,"${" + Variable + "}", Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaTypeEncoding FindMediaType(const Poco::File &F) {
|
||||||
|
const auto E = Poco::Path(F.path()).getExtension();
|
||||||
|
if(E=="png")
|
||||||
|
return MediaTypeEncoding{ .Encoding = BINARY,
|
||||||
|
.ContentType = "image/png" };
|
||||||
|
if(E=="gif")
|
||||||
|
return MediaTypeEncoding{ .Encoding = BINARY,
|
||||||
|
.ContentType = "image/gif" };
|
||||||
|
if(E=="jpeg" || E=="jpg")
|
||||||
|
return MediaTypeEncoding{ .Encoding = BINARY,
|
||||||
|
.ContentType = "image/jpeg" };
|
||||||
|
if(E=="svg" || E=="svgz")
|
||||||
|
return MediaTypeEncoding{ .Encoding = PLAIN,
|
||||||
|
.ContentType = "image/svg+xml" };
|
||||||
|
if(E=="html")
|
||||||
|
return MediaTypeEncoding{ .Encoding = PLAIN,
|
||||||
|
.ContentType = "text/html" };
|
||||||
|
if(E=="css")
|
||||||
|
return MediaTypeEncoding{ .Encoding = PLAIN,
|
||||||
|
.ContentType = "text/css" };
|
||||||
|
if(E=="js")
|
||||||
|
return MediaTypeEncoding{ .Encoding = PLAIN,
|
||||||
|
.ContentType = "application/javascript" };
|
||||||
|
return MediaTypeEncoding{ .Encoding = BINARY,
|
||||||
|
.ContentType = "application/octet-stream" };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BinaryFileToHexString(const Poco::File &F) {
|
||||||
|
static const char hex[] = "0123456789abcdef";
|
||||||
|
std::string Result;
|
||||||
|
try {
|
||||||
|
std::ifstream IF(F.path());
|
||||||
|
|
||||||
|
int Count = 0;
|
||||||
|
while (IF.good()) {
|
||||||
|
if (Count)
|
||||||
|
Result += ", ";
|
||||||
|
if ((Count % 32) == 0)
|
||||||
|
Result += "\r\n";
|
||||||
|
Count++;
|
||||||
|
unsigned char C = IF.get();
|
||||||
|
Result += "0x";
|
||||||
|
Result += (char) (hex[(C & 0xf0) >> 4]);
|
||||||
|
Result += (char) (hex[(C & 0x0f)]);
|
||||||
|
}
|
||||||
|
} catch(...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SecondsToNiceText(uint64_t Seconds) {
|
||||||
|
std::string Result;
|
||||||
|
int Days = Seconds / (24*60*60);
|
||||||
|
Seconds -= Days * (24*60*60);
|
||||||
|
int Hours= Seconds / (60*60);
|
||||||
|
Seconds -= Hours * (60*60);
|
||||||
|
int Minutes = Seconds / 60;
|
||||||
|
Seconds -= Minutes * 60;
|
||||||
|
Result = std::to_string(Days) +" days, " + std::to_string(Hours) + ":" + std::to_string(Minutes) + ":" + std::to_string(Seconds);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cidr_match(const in_addr &addr, const in_addr &net, uint8_t bits) {
|
||||||
|
if (bits == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !((addr.s_addr ^ net.s_addr) & htonl(0xFFFFFFFFu << (32 - bits)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cidr6_match(const in6_addr &address, const in6_addr &network, uint8_t bits) {
|
||||||
|
#ifdef LINUX
|
||||||
|
const uint32_t *a = address.s6_addr32;
|
||||||
|
const uint32_t *n = network.s6_addr32;
|
||||||
|
#else
|
||||||
|
const uint32_t *a = address.__u6_addr.__u6_addr32;
|
||||||
|
const uint32_t *n = network.__u6_addr.__u6_addr32;
|
||||||
|
#endif
|
||||||
|
int bits_whole, bits_incomplete;
|
||||||
|
bits_whole = bits >> 5; // number of whole u32
|
||||||
|
bits_incomplete = bits & 0x1F; // number of bits in incomplete u32
|
||||||
|
if (bits_whole) {
|
||||||
|
if (memcmp(a, n, bits_whole << 2)!=0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bits_incomplete) {
|
||||||
|
uint32_t mask = htonl((0xFFFFFFFFu) << (32 - bits_incomplete));
|
||||||
|
if ((a[bits_whole] ^ n[bits_whole]) & mask) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ConvertStringToLong(const char *S, unsigned long &L) {
|
||||||
|
char *end;
|
||||||
|
L = std::strtol(S,&end,10);
|
||||||
|
return end != S;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPinRange(const std::string &Range, const Poco::Net::IPAddress &IP) {
|
||||||
|
Poco::StringTokenizer TimeTokens(Range,"/",Poco::StringTokenizer::TOK_TRIM);
|
||||||
|
|
||||||
|
Poco::Net::IPAddress RangeIP;
|
||||||
|
if(Poco::Net::IPAddress::tryParse(TimeTokens[0],RangeIP)) {
|
||||||
|
if(TimeTokens.count()==2) {
|
||||||
|
if (RangeIP.family() == Poco::Net::IPAddress::IPv4) {
|
||||||
|
unsigned long MaskLength;
|
||||||
|
if (ConvertStringToLong(TimeTokens[1].c_str(), MaskLength)) {
|
||||||
|
return cidr_match(*static_cast<const in_addr *>(RangeIP.addr()),
|
||||||
|
*static_cast<const in_addr *>(IP.addr()), MaskLength);
|
||||||
|
}
|
||||||
|
} else if (RangeIP.family() == Poco::Net::IPAddress::IPv6) {
|
||||||
|
unsigned long MaskLength;
|
||||||
|
if (ConvertStringToLong(TimeTokens[1].c_str(), MaskLength)) {
|
||||||
|
return cidr6_match(*static_cast<const in6_addr *>(RangeIP.addr()),
|
||||||
|
*static_cast<const in6_addr *>(IP.addr()), MaskLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
77
src/Utils.h
Normal file
77
src/Utils.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_UTILS_H
|
||||||
|
#define UCENTRALGW_UTILS_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "Poco/Net/NetworkInterface.h"
|
||||||
|
#include "Poco/Net/IPAddress.h"
|
||||||
|
#include "Poco/String.h"
|
||||||
|
#include "Poco/File.h"
|
||||||
|
#include "uCentralTypes.h"
|
||||||
|
|
||||||
|
#define DBGLINE { std::cout << __FILE__ << ":" << __func__ << ":" << __LINE__ << std::endl; };
|
||||||
|
|
||||||
|
namespace uCentral::Utils {
|
||||||
|
|
||||||
|
enum MediaTypeEncodings {
|
||||||
|
PLAIN,
|
||||||
|
BINARY,
|
||||||
|
BASE64
|
||||||
|
};
|
||||||
|
struct MediaTypeEncoding {
|
||||||
|
MediaTypeEncodings Encoding=PLAIN;
|
||||||
|
std::string ContentType;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<std::string> Split(const std::string &List, char Delimiter=',');
|
||||||
|
[[nodiscard]] std::string FormatIPv6(const std::string & I );
|
||||||
|
inline void padTo(std::string& str, size_t num, char paddingChar = '\0') {
|
||||||
|
str.append(num - str.length() % num, paddingChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string SerialToMAC(const std::string &Serial);
|
||||||
|
[[nodiscard]] std::string ToHex(const std::vector<unsigned char> & B);
|
||||||
|
|
||||||
|
using byte = std::uint8_t;
|
||||||
|
|
||||||
|
[[nodiscard]] std::string base64encode(const byte *input, unsigned long size);
|
||||||
|
std::vector<byte> base64decode(const std::string& input);
|
||||||
|
|
||||||
|
// [[nodiscard]] std::string to_RFC3339(uint64_t t);
|
||||||
|
// [[nodiscard]] uint64_t from_RFC3339(const std::string &t);
|
||||||
|
|
||||||
|
bool ParseTime(const std::string &Time, int & Hours, int & Minutes, int & Seconds);
|
||||||
|
bool ParseDate(const std::string &Time, int & Year, int & Month, int & Day);
|
||||||
|
bool CompareTime( int H1, int H2, int M1, int M2, int S1, int S2);
|
||||||
|
|
||||||
|
[[nodiscard]] bool ValidSerialNumber(const std::string &Serial);
|
||||||
|
[[nodiscard]] std::string LogLevelToString(int Level);
|
||||||
|
|
||||||
|
[[nodiscard]] bool SerialNumberMatch(const std::string &S1, const std::string &S2, int extrabits=2);
|
||||||
|
[[nodiscard]] uint64_t SerialNumberToInt(const std::string & S);
|
||||||
|
[[nodiscard]] uint64_t SerialNumberToOUI(const std::string & S);
|
||||||
|
|
||||||
|
[[nodiscard]] uint64_t GetDefaultMacAsInt64();
|
||||||
|
[[nodiscard]] uint64_t GetSystemId();
|
||||||
|
|
||||||
|
[[nodiscard]] bool ValidEMailAddress(const std::string &E);
|
||||||
|
[[nodiscard]] std::string LoadFile( const Poco::File & F);
|
||||||
|
void ReplaceVariables( std::string & Content , const Types::StringPairVec & P);
|
||||||
|
|
||||||
|
[[nodiscard]] MediaTypeEncoding FindMediaType(const Poco::File &F);
|
||||||
|
[[nodiscard]] std::string BinaryFileToHexString( const Poco::File &F);
|
||||||
|
|
||||||
|
[[nodiscard]] std::string SecondsToNiceText(uint64_t Seconds);
|
||||||
|
|
||||||
|
[[nodiscard]] bool IPinRange(const std::string &Range, const Poco::Net::IPAddress &IP);
|
||||||
|
}
|
||||||
|
#endif // UCENTRALGW_UTILS_H
|
||||||
46
src/storage_mysql.cpp
Normal file
46
src/storage_mysql.cpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Daemon.h"
|
||||||
|
#include "StorageService.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
#ifdef SMALL_BUILD
|
||||||
|
int Service::Setup_MySQL() { uCentral::instance()->exit(Poco::Util::Application::EXIT_CONFIG);}
|
||||||
|
#else
|
||||||
|
|
||||||
|
int Storage::Setup_MySQL() {
|
||||||
|
|
||||||
|
dbType_ = mysql ;
|
||||||
|
|
||||||
|
Logger_.notice("MySQL Storage enabled.");
|
||||||
|
auto NumSessions = Daemon()->ConfigGetInt("storage.type.mysql.maxsessions", 64);
|
||||||
|
auto IdleTime = Daemon()->ConfigGetInt("storage.type.mysql.idletime", 60);
|
||||||
|
auto Host = Daemon()->ConfigGetString("storage.type.mysql.host");
|
||||||
|
auto Username = Daemon()->ConfigGetString("storage.type.mysql.username");
|
||||||
|
auto Password = Daemon()->ConfigGetString("storage.type.mysql.password");
|
||||||
|
auto Database = Daemon()->ConfigGetString("storage.type.mysql.database");
|
||||||
|
auto Port = Daemon()->ConfigGetString("storage.type.mysql.port");
|
||||||
|
|
||||||
|
std::string ConnectionStr =
|
||||||
|
"host=" + Host +
|
||||||
|
";user=" + Username +
|
||||||
|
";password=" + Password +
|
||||||
|
";db=" + Database +
|
||||||
|
";port=" + Port +
|
||||||
|
";compress=true;auto-reconnect=true";
|
||||||
|
|
||||||
|
MySQLConn_ = std::make_unique<Poco::Data::MySQL::Connector>();
|
||||||
|
MySQLConn_->registerConnector();
|
||||||
|
Pool_ = std::make_unique<Poco::Data::SessionPool>(MySQLConn_->name(), ConnectionStr, 4, NumSessions, IdleTime);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
47
src/storage_pgql.cpp
Normal file
47
src/storage_pgql.cpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Daemon.h"
|
||||||
|
#include "StorageService.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
#ifdef SMALL_BUILD
|
||||||
|
int Service::Setup_PostgreSQL() { uCentral::instance()->exit(Poco::Util::Application::EXIT_CONFIG);}
|
||||||
|
#else
|
||||||
|
int Storage::Setup_PostgreSQL() {
|
||||||
|
Logger_.notice("PostgreSQL Storage enabled.");
|
||||||
|
|
||||||
|
dbType_ = pgsql ;
|
||||||
|
|
||||||
|
auto NumSessions = Daemon()->ConfigGetInt("storage.type.postgresql.maxsessions", 64);
|
||||||
|
auto IdleTime = Daemon()->ConfigGetInt("storage.type.postgresql.idletime", 60);
|
||||||
|
auto Host = Daemon()->ConfigGetString("storage.type.postgresql.host");
|
||||||
|
auto Username = Daemon()->ConfigGetString("storage.type.postgresql.username");
|
||||||
|
auto Password = Daemon()->ConfigGetString("storage.type.postgresql.password");
|
||||||
|
auto Database = Daemon()->ConfigGetString("storage.type.postgresql.database");
|
||||||
|
auto Port = Daemon()->ConfigGetString("storage.type.postgresql.port");
|
||||||
|
auto ConnectionTimeout = Daemon()->ConfigGetString("storage.type.postgresql.connectiontimeout");
|
||||||
|
|
||||||
|
std::string ConnectionStr =
|
||||||
|
"host=" + Host +
|
||||||
|
" user=" + Username +
|
||||||
|
" password=" + Password +
|
||||||
|
" dbname=" + Database +
|
||||||
|
" port=" + Port +
|
||||||
|
" connect_timeout=" + ConnectionTimeout;
|
||||||
|
|
||||||
|
PostgresConn_ = std::make_unique<Poco::Data::PostgreSQL::Connector>();
|
||||||
|
PostgresConn_->registerConnector();
|
||||||
|
Pool_ = std::make_unique<Poco::Data::SessionPool>(PostgresConn_->name(), ConnectionStr, 4, NumSessions, IdleTime);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
26
src/storage_sqlite.cpp
Normal file
26
src/storage_sqlite.cpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Daemon.h"
|
||||||
|
#include "StorageService.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
int Storage::Setup_SQLite() {
|
||||||
|
Logger_.notice("SQLite Storage enabled.");
|
||||||
|
|
||||||
|
auto DBName = Daemon()->DataDir() + "/" + Daemon()->ConfigGetString("storage.type.sqlite.db");
|
||||||
|
auto NumSessions = Daemon()->ConfigGetInt("storage.type.sqlite.maxsessions", 64);
|
||||||
|
auto IdleTime = Daemon()->ConfigGetInt("storage.type.sqlite.idletime", 60);
|
||||||
|
|
||||||
|
SQLiteConn_ = std::make_unique<Poco::Data::SQLite::Connector>();
|
||||||
|
SQLiteConn_->registerConnector();
|
||||||
|
Pool_ = std::make_unique<Poco::Data::SessionPool>(SQLiteConn_->name(), DBName, 4, NumSessions, IdleTime);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/storage_tables.cpp
Normal file
17
src/storage_tables.cpp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "StorageService.h"
|
||||||
|
|
||||||
|
namespace uCentral {
|
||||||
|
|
||||||
|
int Storage::Create_Tables() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
129
src/uCentralProtocol.h
Normal file
129
src/uCentralProtocol.h
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
//
|
||||||
|
// License type: BSD 3-Clause License
|
||||||
|
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
// Created by Stephane Bourque on 2021-03-04.
|
||||||
|
// Arilia Wireless Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_UCENTRALPROTOCOL_H
|
||||||
|
#define UCENTRALGW_UCENTRALPROTOCOL_H
|
||||||
|
|
||||||
|
#include "Poco/String.h"
|
||||||
|
|
||||||
|
namespace uCentral::uCentralProtocol {
|
||||||
|
|
||||||
|
const int SERIAL_NUMBER_LENGTH = 30;
|
||||||
|
|
||||||
|
// vocabulary used in the PROTOCOL.md file
|
||||||
|
static const char * JSONRPC = "jsonrpc";
|
||||||
|
static const char * ID = "id";
|
||||||
|
static const char * UUID = "uuid";
|
||||||
|
static const char * JSONRPC_VERSION = "2.0";
|
||||||
|
static const char * METHOD = "method";
|
||||||
|
static const char * PARAMS = "params";
|
||||||
|
static const char * SERIAL = "serial";
|
||||||
|
static const char * FIRMWARE = "firmware";
|
||||||
|
static const char * CONNECT = "connect";
|
||||||
|
static const char * STATE = "state";
|
||||||
|
static const char * HEALTHCHECK = "healthcheck";
|
||||||
|
static const char * LOG = "log";
|
||||||
|
static const char * CRASHLOG = "crashlog";
|
||||||
|
static const char * PING = "ping";
|
||||||
|
static const char * CFGPENDING = "cfgpending";
|
||||||
|
static const char * RECOVERY = "recovery";
|
||||||
|
static const char * COMPRESS_64 = "compress_64";
|
||||||
|
static const char * CAPABILITIES = "capabilities";
|
||||||
|
static const char * REQUEST_UUID = "request_uuid";
|
||||||
|
static const char * SANITY = "sanity";
|
||||||
|
static const char * DATA = "data";
|
||||||
|
static const char * LOGLINES = "loglines";
|
||||||
|
static const char * SEVERITY = "severity";
|
||||||
|
static const char * ACTIVE = "active";
|
||||||
|
static const char * REBOOT = "reboot";
|
||||||
|
static const char * WHEN = "when";
|
||||||
|
static const char * CONFIG = "config";
|
||||||
|
static const char * EMPTY_JSON_DOC = "{}";
|
||||||
|
static const char * RESULT = "result";
|
||||||
|
static const char * REQUEST = "request";
|
||||||
|
static const char * PERFORM = "perform";
|
||||||
|
static const char * CONFIGURE = "configure";
|
||||||
|
static const char * PENDING = "pending";
|
||||||
|
static const char * SUBMITTED_BY_SYSTEM = "*system";
|
||||||
|
static const char * URI = "uri";
|
||||||
|
static const char * COMMAND = "command";
|
||||||
|
static const char * PAYLOAD = "payload";
|
||||||
|
static const char * KEEP_REDIRECTOR = "keep_redirector";
|
||||||
|
static const char * DURATION = "duration";
|
||||||
|
static const char * PATTERN = "pattern";
|
||||||
|
static const char * LEDS = "leds";
|
||||||
|
static const char * ON = "on";
|
||||||
|
static const char * OFF = "off";
|
||||||
|
static const char * BLINK = "blink";
|
||||||
|
static const char * PACKETS = "packets";
|
||||||
|
static const char * NETWORK = "network";
|
||||||
|
static const char * INTERFACE = "interface";
|
||||||
|
static const char * TRACE = "trace";
|
||||||
|
static const char * WIFISCAN = "wifiscan";
|
||||||
|
static const char * TYPES = "types";
|
||||||
|
static const char * EVENT = "event";
|
||||||
|
static const char * MESSAGE = "message";
|
||||||
|
static const char * RTTY = "rtty";
|
||||||
|
static const char * TOKEN = "token";
|
||||||
|
static const char * SERVER = "server";
|
||||||
|
static const char * PORT = "port";
|
||||||
|
static const char * USER = "user";
|
||||||
|
static const char * TIMEOUT = "timeout";
|
||||||
|
static const char * UPGRADE = "upgrade";
|
||||||
|
static const char * FACTORY = "factory";
|
||||||
|
static const char * VERBOSE = "verbose";
|
||||||
|
static const char * BANDS = "bands";
|
||||||
|
static const char * CHANNELS = "channels";
|
||||||
|
static const char * PASSWORD = "password";
|
||||||
|
static const char * DEVICEUPDATE = "deviceupdate";
|
||||||
|
|
||||||
|
static const char * SERIALNUMBER = "serialNumber";
|
||||||
|
static const char * COMPATIBLE = "compatible";
|
||||||
|
static const char * DISCONNECTION = "disconnection";
|
||||||
|
static const char * TIMESTAMP = "timestamp";
|
||||||
|
static const char * SYSTEM = "system";
|
||||||
|
static const char * HOST = "host";
|
||||||
|
|
||||||
|
enum EVENT_MSG {
|
||||||
|
ET_UNKNOWN,
|
||||||
|
ET_CONNECT,
|
||||||
|
ET_STATE,
|
||||||
|
ET_HEALTHCHECK,
|
||||||
|
ET_LOG,
|
||||||
|
ET_CRASHLOG,
|
||||||
|
ET_PING,
|
||||||
|
ET_CFGPENDING,
|
||||||
|
ET_RECOVERY,
|
||||||
|
ET_DEVICEUPDATE
|
||||||
|
};
|
||||||
|
|
||||||
|
static EVENT_MSG EventFromString(const std::string & Method) {
|
||||||
|
if (!Poco::icompare(Method, CONNECT)) {
|
||||||
|
return ET_CONNECT;
|
||||||
|
} else if (!Poco::icompare(Method, STATE)) {
|
||||||
|
return ET_STATE;
|
||||||
|
} else if (!Poco::icompare(Method, HEALTHCHECK)) {
|
||||||
|
return ET_HEALTHCHECK;
|
||||||
|
} else if (!Poco::icompare(Method, LOG)) {
|
||||||
|
return ET_LOG;
|
||||||
|
} else if (!Poco::icompare(Method, CRASHLOG)) {
|
||||||
|
return ET_CRASHLOG;
|
||||||
|
} else if (!Poco::icompare(Method, PING)) {
|
||||||
|
return ET_PING;
|
||||||
|
} else if (!Poco::icompare(Method, CFGPENDING)) {
|
||||||
|
return ET_CFGPENDING;
|
||||||
|
} else if (!Poco::icompare(Method, RECOVERY)) {
|
||||||
|
return ET_RECOVERY;
|
||||||
|
} else if (!Poco::icompare(Method, DEVICEUPDATE)) {
|
||||||
|
return ET_DEVICEUPDATE;
|
||||||
|
} else
|
||||||
|
return ET_UNKNOWN;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // UCENTRALGW_UCENTRALPROTOCOL_H
|
||||||
40
src/uCentralTypes.h
Normal file
40
src/uCentralTypes.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Created by stephane bourque on 2021-06-13.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UCENTRALGW_UCENTRALTYPES_H
|
||||||
|
#define UCENTRALGW_UCENTRALTYPES_H
|
||||||
|
|
||||||
|
#include "SubSystemServer.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <functional>
|
||||||
|
#include <list>
|
||||||
|
#include <utility>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace uCentral::Types {
|
||||||
|
typedef std::pair<std::string,std::string> StringPair;
|
||||||
|
typedef std::vector<StringPair> StringPairVec;
|
||||||
|
typedef std::queue<StringPair> StringPairQueue;
|
||||||
|
typedef std::vector<std::string> StringVec;
|
||||||
|
typedef std::set<std::string> StringSet;
|
||||||
|
typedef std::vector<SubSystemServer*> SubSystemVec;
|
||||||
|
typedef std::map<std::string,std::set<std::string>> StringMapStringSet;
|
||||||
|
typedef std::function<void(std::string, std::string)> TopicNotifyFunction;
|
||||||
|
typedef std::list<std::pair<TopicNotifyFunction,int>> TopicNotifyFunctionList;
|
||||||
|
typedef std::map<std::string, TopicNotifyFunctionList> NotifyTable;
|
||||||
|
typedef std::map<std::string,uint64_t> CountedMap;
|
||||||
|
|
||||||
|
inline void UpdateCountedMap(CountedMap &M, const std::string &S ) {
|
||||||
|
auto it = M.find(S);
|
||||||
|
if(it==M.end())
|
||||||
|
M[S]=1;
|
||||||
|
else
|
||||||
|
it->second += 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // UCENTRALGW_UCENTRALTYPES_H
|
||||||
131
ucentralprov.properties
Normal file
131
ucentralprov.properties
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#
|
||||||
|
# uCentral protocol server for devices. This is where you point
|
||||||
|
# all your devices. You can replace the * for address by the specific
|
||||||
|
# address of one of your interfaces
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# REST API access
|
||||||
|
#
|
||||||
|
ucentralfws.restapi.host.0.backlog = 100
|
||||||
|
ucentralfws.restapi.host.0.security = relaxed
|
||||||
|
ucentralfws.restapi.host.0.rootca = $UCENTRALTOPO_ROOT/certs/restapi-ca.pem
|
||||||
|
ucentralfws.restapi.host.0.address = *
|
||||||
|
ucentralfws.restapi.host.0.port = 16005
|
||||||
|
ucentralfws.restapi.host.0.cert = $UCENTRALTOPO_ROOT/certs/restapi-cert.pem
|
||||||
|
ucentralfws.restapi.host.0.key = $UCENTRALTOPO_ROOT/certs/restapi-key.pem
|
||||||
|
ucentralfws.restapi.host.0.key.password = mypassword
|
||||||
|
|
||||||
|
ucentral.internal.restapi.host.0.backlog = 100
|
||||||
|
ucentral.internal.restapi.host.0.security = relaxed
|
||||||
|
ucentral.internal.restapi.host.0.rootca = $UCENTRALTOPO_ROOT/certs/restapi-ca.pem
|
||||||
|
ucentral.internal.restapi.host.0.address = *
|
||||||
|
ucentral.internal.restapi.host.0.port = 17005
|
||||||
|
ucentral.internal.restapi.host.0.cert = $UCENTRALTOPO_ROOT/certs/restapi-cert.pem
|
||||||
|
ucentral.internal.restapi.host.0.key = $UCENTRALTOPO_ROOT/certs/restapi-key.pem
|
||||||
|
ucentral.internal.restapi.host.0.key.password = mypassword
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generic section that all microservices must have
|
||||||
|
#
|
||||||
|
ucentral.service.key = $UCENTRALTOPO_ROOT/certs/restapi-key.pem
|
||||||
|
ucentral.service.key.password = mypassword
|
||||||
|
ucentral.system.data = $UCENTRALTOPO_ROOT/data
|
||||||
|
ucentral.system.debug = false
|
||||||
|
ucentral.system.uri.private = https://localhost:17005
|
||||||
|
ucentral.system.uri.public = https://ucentral.dpaas.arilia.com:16005
|
||||||
|
ucentral.system.commandchannel = /tmp/app.ucentraltopo
|
||||||
|
ucentral.system.uri.ui = ucentral-ui.arilia.com
|
||||||
|
|
||||||
|
#############################
|
||||||
|
# Generic information for all micro services
|
||||||
|
#############################
|
||||||
|
#
|
||||||
|
# NLB Support
|
||||||
|
#
|
||||||
|
alb.enable = true
|
||||||
|
alb.port = 16104
|
||||||
|
|
||||||
|
#
|
||||||
|
# Kafka
|
||||||
|
#
|
||||||
|
ucentral.kafka.group.id = topology
|
||||||
|
ucentral.kafka.client.id = topology1
|
||||||
|
ucentral.kafka.enable = true
|
||||||
|
ucentral.kafka.brokerlist = a1.arilia.com:9092
|
||||||
|
# ucentral.kafka.brokerlist = debfarm1-node-c.arilia.com:9092
|
||||||
|
ucentral.kafka.auto.commit = false
|
||||||
|
ucentral.kafka.queue.buffering.max.ms = 50
|
||||||
|
|
||||||
|
#
|
||||||
|
# This section select which form of persistence you need
|
||||||
|
# Only one selected at a time. If you select multiple, this service will die if a horrible
|
||||||
|
# death and might make your beer flat.
|
||||||
|
#
|
||||||
|
storage.type = sqlite
|
||||||
|
#storage.type = postgresql
|
||||||
|
#storage.type = mysql
|
||||||
|
#storage.type = odbc
|
||||||
|
|
||||||
|
storage.type.sqlite.db = firmware.db
|
||||||
|
storage.type.sqlite.idletime = 120
|
||||||
|
storage.type.sqlite.maxsessions = 128
|
||||||
|
|
||||||
|
storage.type.postgresql.maxsessions = 64
|
||||||
|
storage.type.postgresql.idletime = 60
|
||||||
|
storage.type.postgresql.host = localhost
|
||||||
|
storage.type.postgresql.username = stephb
|
||||||
|
storage.type.postgresql.password = snoopy99
|
||||||
|
storage.type.postgresql.database = ucentral
|
||||||
|
storage.type.postgresql.port = 5432
|
||||||
|
storage.type.postgresql.connectiontimeout = 60
|
||||||
|
|
||||||
|
storage.type.mysql.maxsessions = 64
|
||||||
|
storage.type.mysql.idletime = 60
|
||||||
|
storage.type.mysql.host = localhost
|
||||||
|
storage.type.mysql.username = stephb
|
||||||
|
storage.type.mysql.password = snoopy99
|
||||||
|
storage.type.mysql.database = ucentral
|
||||||
|
storage.type.mysql.port = 3306
|
||||||
|
storage.type.mysql.connectiontimeout = 60
|
||||||
|
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# Logging: please leave as is for now.
|
||||||
|
#
|
||||||
|
########################################################################
|
||||||
|
logging.formatters.f1.class = PatternFormatter
|
||||||
|
logging.formatters.f1.pattern = %s: [%p] %t
|
||||||
|
logging.formatters.f1.times = UTC
|
||||||
|
logging.channels.c1.class = ConsoleChannel
|
||||||
|
logging.channels.c1.formatter = f1
|
||||||
|
|
||||||
|
# This is where the logs will be written. This path MUST exist
|
||||||
|
logging.channels.c2.class = FileChannel
|
||||||
|
logging.channels.c2.path = $UCENTRALTOPO_ROOT/logs/log
|
||||||
|
logging.channels.c2.formatter.class = PatternFormatter
|
||||||
|
logging.channels.c2.formatter.pattern = %Y-%m-%d %H:%M:%S %s: [%p] %t
|
||||||
|
logging.channels.c2.rotation = 20 M
|
||||||
|
logging.channels.c2.archive = timestamp
|
||||||
|
logging.channels.c2.purgeCount = 20
|
||||||
|
logging.channels.c3.class = ConsoleChannel
|
||||||
|
logging.channels.c3.pattern = %s: [%p] %t
|
||||||
|
|
||||||
|
# External Channel
|
||||||
|
logging.loggers.root.channel = c2
|
||||||
|
logging.loggers.root.level = debug
|
||||||
|
|
||||||
|
# Inline Channel with PatternFormatter
|
||||||
|
# logging.loggers.l1.name = logger1
|
||||||
|
# logging.loggers.l1.channel.class = ConsoleChannel
|
||||||
|
# logging.loggers.l1.channel.pattern = %s: [%p] %t
|
||||||
|
# logging.loggers.l1.level = information
|
||||||
|
# SplitterChannel
|
||||||
|
# logging.channels.splitter.class = SplitterChannel
|
||||||
|
# logging.channels.splitter.channels = l1,l2
|
||||||
|
# logging.loggers.l2.name = logger2
|
||||||
|
# logging.loggers.l2.channel = splitter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user