commit 3f14f54134c1aff6f0ce0d624b3ed99bf19bf6e7 Author: stephb9959 Date: Wed Jul 28 14:34:18 2021 -0700 Initial diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..563d4ef --- /dev/null +++ b/CMakeLists.txt @@ -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 ) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..93416d6 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# ucentral-topo \ No newline at end of file diff --git a/set_env.sh b/set_env.sh new file mode 100755 index 0000000..e756885 --- /dev/null +++ b/set_env.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +export UCENTRALPROV_CONFIG=`pwd` +export UCENTRALPROV_ROOT=`pwd` diff --git a/src/ALBHealthCheckServer.h b/src/ALBHealthCheckServer.h new file mode 100644 index 0000000..94735cd --- /dev/null +++ b/src/ALBHealthCheckServer.h @@ -0,0 +1,114 @@ +// +// Created by stephane bourque on 2021-06-04. +// + +#ifndef UCENTRALGW_ALBHEALTHCHECKSERVER_H +#define UCENTRALGW_ALBHEALTHCHECKSERVER_H + +#include +#include +#include +#include + +#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(Port_); + auto Params = new Poco::Net::HTTPServerParams; + Server_ = std::make_unique(new ALBRequestHandlerFactory(Logger_), *Socket_, Params); + Server_->start(); + } + + return 0; + } + + void Stop() { + if(Server_) + Server_->stop(); + } + + private: + static ALBHealthCheckServer *instance_; + std::unique_ptr Server_; + std::unique_ptr Socket_; + int Port_ = 0; + }; + + inline ALBHealthCheckServer * ALBHealthCheckServer() { return ALBHealthCheckServer::instance(); } + inline class ALBHealthCheckServer * ALBHealthCheckServer::instance_ = nullptr; +} + +#endif // UCENTRALGW_ALBHEALTHCHECKSERVER_H diff --git a/src/AuthClient.cpp b/src/AuthClient.cpp new file mode 100644 index 0000000..f642e04 --- /dev/null +++ b/src/AuthClient.cpp @@ -0,0 +1,59 @@ +// +// Created by stephane bourque on 2021-06-30. +// +#include + +#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_)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; + } +} \ No newline at end of file diff --git a/src/AuthClient.h b/src/AuthClient.h new file mode 100644 index 0000000..55b3475 --- /dev/null +++ b/src/AuthClient.h @@ -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 diff --git a/src/Daemon.cpp b/src/Daemon.cpp new file mode 100644 index 0000000..30c04e0 --- /dev/null +++ b/src/Daemon.cpp @@ -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 +#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 \ No newline at end of file diff --git a/src/Daemon.h b/src/Daemon.h new file mode 100644 index 0000000..6a4befa --- /dev/null +++ b/src/Daemon.h @@ -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 +#include +#include +#include +#include + +#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 diff --git a/src/Dashboard.cpp b/src/Dashboard.cpp new file mode 100644 index 0000000..eaec35b --- /dev/null +++ b/src/Dashboard.cpp @@ -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; + } + } +} diff --git a/src/Dashboard.h b/src/Dashboard.h new file mode 100644 index 0000000..94b163b --- /dev/null +++ b/src/Dashboard.h @@ -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 diff --git a/src/KafkaManager.cpp b/src/KafkaManager.cpp new file mode 100644 index 0000000..8e10998 --- /dev/null +++ b/src/KafkaManager.cpp @@ -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 + +#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([this]() { this->ProducerThr(); }); + ConsumerThr_ = std::make_unique([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 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 diff --git a/src/KafkaManager.h b/src/KafkaManager.h new file mode 100644 index 0000000..6d93d8f --- /dev/null +++ b/src/KafkaManager.h @@ -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 +#include + +#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 Queue_; + std::string SystemInfoWrapper_; + std::unique_ptr ConsumerThr_; + std::unique_ptr ProducerThr_; + int FunctionId_=1; + Types::NotifyTable Notifiers_; + std::unique_ptr Config_; + + KafkaManager() noexcept; + }; + + inline KafkaManager * KafkaManager() { return KafkaManager::instance(); } +} // NameSpace + +#endif // UCENTRALGW_KAFKAMANAGER_H diff --git a/src/Kafka_topics.h b/src/Kafka_topics.h new file mode 100644 index 0000000..e6636b3 --- /dev/null +++ b/src/Kafka_topics.h @@ -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 diff --git a/src/MicroService.cpp b/src/MicroService.cpp new file mode 100644 index 0000000..a344f5d --- /dev/null +++ b/src/MicroService.cpp @@ -0,0 +1,491 @@ +// +// Created by stephane bourque on 2021-06-22. +// +#include +#include + +#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(); + 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(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(this, &MicroService::handleHelp))); + + options.addOption( + Poco::Util::Option("file", "", "specify the configuration file") + .required(false) + .repeatable(false) + .argument("file") + .callback(Poco::Util::OptionCallback(this, &MicroService::handleConfig))); + + options.addOption( + Poco::Util::Option("debug", "", "to run in debug, set to true") + .required(false) + .repeatable(false) + .callback(Poco::Util::OptionCallback(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(this, &MicroService::handleLogs))); + + options.addOption( + Poco::Util::Option("version", "", "get the version and quit.") + .required(false) + .repeatable(false) + .callback(Poco::Util::OptionCallback(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; + } +} \ No newline at end of file diff --git a/src/MicroService.h b/src/MicroService.h new file mode 100644 index 0000000..7dd02e8 --- /dev/null +++ b/src/MicroService.h @@ -0,0 +1,174 @@ +// +// Created by stephane bourque on 2021-06-22. +// + +#ifndef UCENTRALGW_MICROSERVICE_H +#define UCENTRALGW_MICROSERVICE_H + +#include +#include +#include +#include +#include + +#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 MicroServiceMetaMap; + typedef std::vector 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 & 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 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 diff --git a/src/OpenAPIRequest.cpp b/src/OpenAPIRequest.cpp new file mode 100644 index 0000000..1f9d9fd --- /dev/null +++ b/src/OpenAPIRequest.cpp @@ -0,0 +1,68 @@ +// +// Created by stephane bourque on 2021-07-01. +// +#include + +#include "OpenAPIRequest.h" + +#include "Poco/Net/HTTPSClientSession.h" +#include +#include +#include +#include +#include +#include +#include +#include +#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(); + } + return Response.getStatus(); + } + } + catch (const Poco::Exception &E) + { + std::cerr << E.displayText() << std::endl; + } + return -1; + } +} diff --git a/src/OpenAPIRequest.h b/src/OpenAPIRequest.h new file mode 100644 index 0000000..8fe6c81 --- /dev/null +++ b/src/OpenAPIRequest.h @@ -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 diff --git a/src/RESTAPI_InternalServer.cpp b/src/RESTAPI_InternalServer.cpp new file mode 100644 index 0000000..fce636d --- /dev/null +++ b/src/RESTAPI_InternalServer.cpp @@ -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(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_); + } + +} \ No newline at end of file diff --git a/src/RESTAPI_InternalServer.h b/src/RESTAPI_InternalServer.h new file mode 100644 index 0000000..f6a220b --- /dev/null +++ b/src/RESTAPI_InternalServer.h @@ -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> 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 diff --git a/src/RESTAPI_SecurityObjects.cpp b/src/RESTAPI_SecurityObjects.cpp new file mode 100644 index 0000000..705b0dc --- /dev/null +++ b/src/RESTAPI_SecurityObjects.cpp @@ -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(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(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(Obj,"access", access, ResourceAccessTypeToString); + } + + bool ProfileAction::from_json(Poco::JSON::Object::Ptr Obj) { + try { + field_from_json(Obj,"resource",resource); + field_from_json(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; + } +} + diff --git a/src/RESTAPI_SecurityObjects.h b/src/RESTAPI_SecurityObjects.h new file mode 100644 index 0000000..33c762e --- /dev/null +++ b/src/RESTAPI_SecurityObjects.h @@ -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 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 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 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 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 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 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 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 \ No newline at end of file diff --git a/src/RESTAPI_TopoObjects.cpp b/src/RESTAPI_TopoObjects.cpp new file mode 100644 index 0000000..ae3c098 --- /dev/null +++ b/src/RESTAPI_TopoObjects.cpp @@ -0,0 +1,10 @@ +// +// Created by stephane bourque on 2021-07-25. +// + +#include "RESTAPI_TopoObjects.h" + +namespace uCentral::TopoObjects { + void Report::reset() { + } +} \ No newline at end of file diff --git a/src/RESTAPI_TopoObjects.h b/src/RESTAPI_TopoObjects.h new file mode 100644 index 0000000..029dff1 --- /dev/null +++ b/src/RESTAPI_TopoObjects.h @@ -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 diff --git a/src/RESTAPI_handler.cpp b/src/RESTAPI_handler.cpp new file mode 100644 index 0000000..5b36c0c --- /dev/null +++ b/src/RESTAPI_handler.cpp @@ -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 +#include +#include +#include +#include +#include + +#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 & EndPoints, BindingMap &bindings) { + std::string Param, Value; + + bindings.clear(); + std::vector PathItems = uCentral::Utils::Split(Request, '/'); + + for(const auto &EndPoint:EndPoints) { + std::vector 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 &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); + } + + +} \ No newline at end of file diff --git a/src/RESTAPI_handler.h b/src/RESTAPI_handler.h new file mode 100644 index 0000000..3f5d8af --- /dev/null +++ b/src/RESTAPI_handler.h @@ -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 BindingMap; + + RESTAPIHandler(BindingMap map, Poco::Logger &l, std::vector 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 & 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 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{}) {} + void handleRequest(Poco::Net::HTTPServerRequest &Request, + Poco::Net::HTTPServerResponse &Response) override { + if (!IsAuthorized(Request, Response)) + return; + BadRequest(Request, Response); + } + }; + + template + 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 + 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(RequestedPath, Bindings, Logger); + } + } + + template + 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(RequestedPath, Bindings, Logger); + } + } + + +} + +#endif //UCENTRAL_RESTAPI_HANDLER_H diff --git a/src/RESTAPI_protocol.h b/src/RESTAPI_protocol.h new file mode 100644 index 0000000..c7c8d49 --- /dev/null +++ b/src/RESTAPI_protocol.h @@ -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 diff --git a/src/RESTAPI_server.cpp b/src/RESTAPI_server.cpp new file mode 100644 index 0000000..0723d09 --- /dev/null +++ b/src/RESTAPI_server.cpp @@ -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(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 \ No newline at end of file diff --git a/src/RESTAPI_server.h b/src/RESTAPI_server.h new file mode 100644 index 0000000..317f524 --- /dev/null +++ b/src/RESTAPI_server.h @@ -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> 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 diff --git a/src/RESTAPI_system_command.cpp b/src/RESTAPI_system_command.cpp new file mode 100644 index 0000000..23b8467 --- /dev/null +++ b/src/RESTAPI_system_command.cpp @@ -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(); + + 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(); + 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); + } + +} \ No newline at end of file diff --git a/src/RESTAPI_system_command.h b/src/RESTAPI_system_command.h new file mode 100644 index 0000000..8295cbe --- /dev/null +++ b/src/RESTAPI_system_command.h @@ -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{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 PathName() { return std::list{"/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 diff --git a/src/RESTAPI_utils.cpp b/src/RESTAPI_utils.cpp new file mode 100644 index 0000000..8ca0800 --- /dev/null +++ b/src/RESTAPI_utils.cpp @@ -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(); + Obj.set(ObjName, DetailsObj); + } +} + diff --git a/src/RESTAPI_utils.h b/src/RESTAPI_utils.h new file mode 100644 index 0000000..48fc1e2 --- /dev/null +++ b/src/RESTAPI_utils.h @@ -0,0 +1,216 @@ +// +// Created by stephane bourque on 2021-07-05. +// + +#ifndef UCENTRALGW_RESTAPI_UTILS_H +#define UCENTRALGW_RESTAPI_UTILS_H +#include + +#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 void field_to_json(Poco::JSON::Object &Obj, + const char *Field, + const T &V, + std::function F) { + Obj.set(Field, F(V)); + } + + template bool field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, T & V, + std::function 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 void field_to_json(Poco::JSON::Object &Obj, const char *Field, const std::vector &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 void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, std::vector &Value) { + if(Obj->isArray(Field)) { + Poco::JSON::Array::Ptr Arr = Obj->getArray(Field); + for(auto &i:*Arr) { + auto InnerObj = i.extract(); + T NewItem; + NewItem.from_json(InnerObj); + Value.push_back(NewItem); + } + } + } + + template 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 std::string to_string(const std::vector & 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 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(); + for (auto const i : *Object) { + Result.push_back(i.toString()); + } + } catch (...) { + + } + return Result; + } + + template std::vector to_object_array(const std::string & ObjectString) { + + std::vector Result; + if(ObjectString.empty()) + return Result; + + try { + Poco::JSON::Parser P; + auto Object = P.parse(ObjectString).template extract(); + for (auto const i : *Object) { + auto InnerObject = i.template extract(); + T Obj; + Obj.from_json(InnerObject); + Result.push_back(Obj); + } + } catch (...) { + + } + return Result; + } + + template 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(); + Result.from_json(Object); + + return Result; + } + + template bool from_request(T & Obj, Poco::Net::HTTPServerRequest &Request) { + Poco::JSON::Parser IncomingParser; + auto RawObject = IncomingParser.parse(Request.stream()).extract(); + Obj.from_json(RawObject); + return true; + } +} + +#endif // UCENTRALGW_RESTAPI_UTILS_H diff --git a/src/StorageService.cpp b/src/StorageService.cpp new file mode 100644 index 0000000..cc00992 --- /dev/null +++ b/src/StorageService.cpp @@ -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 \ No newline at end of file diff --git a/src/StorageService.h b/src/StorageService.h new file mode 100644 index 0000000..bff7579 --- /dev/null +++ b/src/StorageService.h @@ -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 Pool_= nullptr; + StorageType dbType_ = sqlite; + std::unique_ptr SQLiteConn_= nullptr; +#ifndef SMALL_BUILD + std::unique_ptr PostgresConn_= nullptr; + std::unique_ptr MySQLConn_= nullptr; +#endif + + Storage() noexcept; + }; + + inline Storage * Storage() { return Storage::instance(); } + +} // namespace + +#endif //UCENTRAL_USTORAGESERVICE_H diff --git a/src/SubSystemServer.cpp b/src/SubSystemServer.cpp new file mode 100644 index 0000000..68f783d --- /dev/null +++ b/src/SubSystemServer.cpp @@ -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(new Poco::Net::Context(Poco::Net::Context::TLS_SERVER_USE, P)); + + if(!key_file_password_.empty()) { + auto PassphraseHandler = Poco::SharedPtr( 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 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 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); + } +} +} \ No newline at end of file diff --git a/src/SubSystemServer.h b/src/SubSystemServer.h new file mode 100644 index 0000000..94f6a4e --- /dev/null +++ b/src/SubSystemServer.h @@ -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 + +#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; + +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 ConfigServersList_; + std::string SubSystemConfigPrefix_; +}; +} +#endif //UCENTRAL_SUBSYSTEMSERVER_H diff --git a/src/Utils.cpp b/src/Utils.cpp new file mode 100644 index 0000000..ee611cc --- /dev/null +++ b/src/Utils.cpp @@ -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 +#include +#include +#include +#include +#include + +#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 Split(const std::string &List, char Delimiter ) { + std::vector ReturnList; + + unsigned long P=0; + + while(P12) + 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 & 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 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 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(H1H2) + return false; + if(M1M1) + 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(RangeIP.addr()), + *static_cast(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(RangeIP.addr()), + *static_cast(IP.addr()), MaskLength); + } + } + } + return false; + } + return false; + } + +} diff --git a/src/Utils.h b/src/Utils.h new file mode 100644 index 0000000..afffc72 --- /dev/null +++ b/src/Utils.h @@ -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 +#include + +#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 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 & B); + + using byte = std::uint8_t; + + [[nodiscard]] std::string base64encode(const byte *input, unsigned long size); + std::vector 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 diff --git a/src/storage_mysql.cpp b/src/storage_mysql.cpp new file mode 100644 index 0000000..ca7ecfb --- /dev/null +++ b/src/storage_mysql.cpp @@ -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(); + MySQLConn_->registerConnector(); + Pool_ = std::make_unique(MySQLConn_->name(), ConnectionStr, 4, NumSessions, IdleTime); + + return 0; + } +#endif +} \ No newline at end of file diff --git a/src/storage_pgql.cpp b/src/storage_pgql.cpp new file mode 100644 index 0000000..d7b2f44 --- /dev/null +++ b/src/storage_pgql.cpp @@ -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(); + PostgresConn_->registerConnector(); + Pool_ = std::make_unique(PostgresConn_->name(), ConnectionStr, 4, NumSessions, IdleTime); + + return 0; + } +#endif + +} \ No newline at end of file diff --git a/src/storage_sqlite.cpp b/src/storage_sqlite.cpp new file mode 100644 index 0000000..e101326 --- /dev/null +++ b/src/storage_sqlite.cpp @@ -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(); + SQLiteConn_->registerConnector(); + Pool_ = std::make_unique(SQLiteConn_->name(), DBName, 4, NumSessions, IdleTime); + + return 0; + } +} \ No newline at end of file diff --git a/src/storage_tables.cpp b/src/storage_tables.cpp new file mode 100644 index 0000000..580c616 --- /dev/null +++ b/src/storage_tables.cpp @@ -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; + } + +} \ No newline at end of file diff --git a/src/uCentralProtocol.h b/src/uCentralProtocol.h new file mode 100644 index 0000000..87c649d --- /dev/null +++ b/src/uCentralProtocol.h @@ -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 diff --git a/src/uCentralTypes.h b/src/uCentralTypes.h new file mode 100644 index 0000000..75b392a --- /dev/null +++ b/src/uCentralTypes.h @@ -0,0 +1,40 @@ +// +// Created by stephane bourque on 2021-06-13. +// + +#ifndef UCENTRALGW_UCENTRALTYPES_H +#define UCENTRALGW_UCENTRALTYPES_H + +#include "SubSystemServer.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace uCentral::Types { + typedef std::pair StringPair; + typedef std::vector StringPairVec; + typedef std::queue StringPairQueue; + typedef std::vector StringVec; + typedef std::set StringSet; + typedef std::vector SubSystemVec; + typedef std::map> StringMapStringSet; + typedef std::function TopicNotifyFunction; + typedef std::list> TopicNotifyFunctionList; + typedef std::map NotifyTable; + typedef std::map 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 diff --git a/ucentralprov.properties b/ucentralprov.properties new file mode 100644 index 0000000..adef00a --- /dev/null +++ b/ucentralprov.properties @@ -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 + + +