From 9f0059d4e425fcbf3abcef9fd83705de7f4f32db Mon Sep 17 00:00:00 2001 From: stephb9959 Date: Mon, 10 Jan 2022 10:31:20 -0800 Subject: [PATCH] Initial commit --- .idea/.gitignore | 8 + .idea/markdown.xml | 9 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/wlan-cloud-analytics.iml | 2 + CMakeLists.txt | 95 + build | 1 + owanalytics.properties | 98 + owanalytics.service | 22 + set_env.sh | 4 + src/Daemon.cpp | 56 + src/Daemon.h | 51 + src/Dashboard.cpp | 21 + src/Dashboard.h | 23 + src/RESTAPI/RESTAPI_routers.cpp | 23 + src/RESTObjects/RESTAPI_AnalyticsObjects.cpp | 18 + src/RESTObjects/RESTAPI_AnalyticsObjects.h | 22 + src/RESTObjects/RESTAPI_CertObjects.cpp | 178 + src/RESTObjects/RESTAPI_CertObjects.h | 101 + src/RESTObjects/RESTAPI_FMSObjects.cpp | 248 ++ src/RESTObjects/RESTAPI_FMSObjects.h | 133 + src/RESTObjects/RESTAPI_GWobjects.cpp | 269 ++ src/RESTObjects/RESTAPI_GWobjects.h | 197 + src/RESTObjects/RESTAPI_ProvObjects.cpp | 633 +++ src/RESTObjects/RESTAPI_ProvObjects.h | 380 ++ src/RESTObjects/RESTAPI_SecurityObjects.cpp | 589 +++ src/RESTObjects/RESTAPI_SecurityObjects.h | 296 ++ src/StorageService.cpp | 44 + src/StorageService.h | 33 + src/framework/API_Proxy.h | 93 + src/framework/ConfigurationValidator.cpp | 2479 +++++++++++ src/framework/ConfigurationValidator.h | 44 + src/framework/CountryCodes.h | 271 ++ src/framework/KafkaTopics.h | 40 + src/framework/MicroService.h | 4154 ++++++++++++++++++ src/framework/OpenWifiTypes.h | 40 + src/framework/RESTAPI_errors.h | 63 + src/framework/RESTAPI_protocol.h | 138 + src/framework/StorageClass.h | 166 + src/framework/orm.h | 883 ++++ src/framework/uCentral_Protocol.h | 130 + src/ow_version.h.in | 13 + 43 files changed, 12086 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/markdown.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/wlan-cloud-analytics.iml create mode 100644 CMakeLists.txt create mode 100644 build create mode 100644 owanalytics.properties create mode 100644 owanalytics.service create mode 100755 set_env.sh create mode 100644 src/Daemon.cpp create mode 100644 src/Daemon.h create mode 100644 src/Dashboard.cpp create mode 100644 src/Dashboard.h create mode 100644 src/RESTAPI/RESTAPI_routers.cpp create mode 100644 src/RESTObjects/RESTAPI_AnalyticsObjects.cpp create mode 100644 src/RESTObjects/RESTAPI_AnalyticsObjects.h create mode 100644 src/RESTObjects/RESTAPI_CertObjects.cpp create mode 100644 src/RESTObjects/RESTAPI_CertObjects.h create mode 100644 src/RESTObjects/RESTAPI_FMSObjects.cpp create mode 100644 src/RESTObjects/RESTAPI_FMSObjects.h create mode 100644 src/RESTObjects/RESTAPI_GWobjects.cpp create mode 100644 src/RESTObjects/RESTAPI_GWobjects.h create mode 100644 src/RESTObjects/RESTAPI_ProvObjects.cpp create mode 100644 src/RESTObjects/RESTAPI_ProvObjects.h create mode 100644 src/RESTObjects/RESTAPI_SecurityObjects.cpp create mode 100644 src/RESTObjects/RESTAPI_SecurityObjects.h create mode 100644 src/StorageService.cpp create mode 100644 src/StorageService.h create mode 100644 src/framework/API_Proxy.h create mode 100644 src/framework/ConfigurationValidator.cpp create mode 100644 src/framework/ConfigurationValidator.h create mode 100644 src/framework/CountryCodes.h create mode 100644 src/framework/KafkaTopics.h create mode 100644 src/framework/MicroService.h create mode 100644 src/framework/OpenWifiTypes.h create mode 100644 src/framework/RESTAPI_errors.h create mode 100644 src/framework/RESTAPI_protocol.h create mode 100644 src/framework/StorageClass.h create mode 100644 src/framework/orm.h create mode 100644 src/framework/uCentral_Protocol.h create mode 100644 src/ow_version.h.in diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 0000000..1e34094 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..79b3c94 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f975af6 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/wlan-cloud-analytics.iml b/.idea/wlan-cloud-analytics.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/wlan-cloud-analytics.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3f4502e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.13) +project(owanalytics VERSION 2.5.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 ${CMAKE_CURRENT_SOURCE_DIR}/build BUILD_NUM) + if(BUILD_INCREMENT) + MATH(EXPR BUILD_NUM "${BUILD_NUM}+1") + file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/build ${BUILD_NUM}) + endif() +else() + set(BUILD_NUM 1) + file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/build ${BUILD_NUM}) +endif() + +find_package(Git QUIET) +if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") + execute_process(COMMAND ${GIT_EXECUTABLE} describe --always --tags + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_RESULT + OUTPUT_VARIABLE GIT_HASH) + if(NOT GIT_RESULT EQUAL "0") + message(FATAL_ERROR "git describe --always --tags failed with ${GIT_RESULT}") + endif() + string(REGEX REPLACE "\n$" "" GIT_HASH "${GIT_HASH}") +endif() + +add_definitions(-DAWS_CUSTOM_MEMORY_MANAGEMENT) + +set(BUILD_SHARED_LIBS 1) +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) +find_package(nlohmann_json REQUIRED) +find_package(nlohmann_json_schema_validator REQUIRED) + +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) + +configure_file(src/ow_version.h.in ${PROJECT_SOURCE_DIR}/src/ow_version.h @ONLY) + +add_executable(owanalytics + build + src/ow_version.h.in + src/framework/CountryCodes.h + src/framework/KafkaTopics.h + src/framework/MicroService.h + src/framework/OpenWifiTypes.h + src/framework/orm.h + src/framework/RESTAPI_errors.h + src/framework/RESTAPI_protocol.h + src/framework/StorageClass.h + src/framework/uCentral_Protocol.h + src/framework/ConfigurationValidator.cpp + src/framework/ConfigurationValidator.h + src/RESTObjects/RESTAPI_SecurityObjects.h src/RESTObjects/RESTAPI_SecurityObjects.cpp + src/RESTObjects/RESTAPI_ProvObjects.cpp src/RESTObjects/RESTAPI_ProvObjects.h + src/RESTObjects/RESTAPI_GWobjects.h src/RESTObjects/RESTAPI_GWobjects.cpp + src/RESTObjects/RESTAPI_FMSObjects.h src/RESTObjects/RESTAPI_FMSObjects.cpp + src/RESTAPI/RESTAPI_routers.cpp + src/Daemon.cpp src/Daemon.h + src/Dashboard.h src/Dashboard.cpp + src/StorageService.cpp src/StorageService.h src/RESTObjects/RESTAPI_AnalyticsObjects.cpp src/RESTObjects/RESTAPI_AnalyticsObjects.h) + +target_link_libraries(owanalytics PUBLIC + ${Poco_LIBRARIES} ${MySQL_LIBRARIES} + ${Boost_LIBRARIES} + ${ZLIB_LIBRARIES} ${AWSSDK_LINK_LIBRARIES} + CppKafka::cppkafka nlohmann_json_schema_validator) + diff --git a/build b/build new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/build @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/owanalytics.properties b/owanalytics.properties new file mode 100644 index 0000000..77182f6 --- /dev/null +++ b/owanalytics.properties @@ -0,0 +1,98 @@ +# +# all your devices. You can replace the * for address by the specific +# address of one of your interfaces +# +# +# REST API access +# +openwifi.restapi.host.0.backlog = 100 +openwifi.restapi.host.0.security = relaxed +openwifi.restapi.host.0.rootca = $OWANALYTICS_ROOT/certs/restapi-ca.pem +openwifi.restapi.host.0.address = * +openwifi.restapi.host.0.port = 16009 +openwifi.restapi.host.0.cert = $OWANALYTICS_ROOT/certs/restapi-cert.pem +openwifi.restapi.host.0.key = $OWANALYTICS_ROOT/certs/restapi-key.pem +openwifi.restapi.host.0.key.password = mypassword + +openwifi.internal.restapi.host.0.backlog = 100 +openwifi.internal.restapi.host.0.security = relaxed +openwifi.internal.restapi.host.0.rootca = $OWANALYTICS_ROOT/certs/restapi-ca.pem +openwifi.internal.restapi.host.0.address = * +openwifi.internal.restapi.host.0.port = 17009 +openwifi.internal.restapi.host.0.cert = $OWANALYTICS_ROOT/certs/restapi-cert.pem +openwifi.internal.restapi.host.0.key = $OWANALYTICS_ROOT/certs/restapi-key.pem +openwifi.internal.restapi.host.0.key.password = mypassword + +# +# Generic section that all microservices must have +# +openwifi.service.key = $OWANALYTICS_ROOT/certs/restapi-key.pem +openwifi.service.key.password = mypassword +openwifi.system.data = $OWANALYTICS_ROOT/data +openwifi.system.debug = false +openwifi.system.uri.private = https://localhost:17009 +openwifi.system.uri.public = https://ucentral.dpaas.arilia.com:16009 +openwifi.system.commandchannel = /tmp/app.owanalytics +openwifi.system.uri.ui = owprov-ui.arilia.com + +############################# +# Generic information for all micro services +############################# +# +# NLB Support +# +alb.enable = true +alb.port = 16105 + +# +# Kafka +# +openwifi.kafka.group.id = analytics +openwifi.kafka.client.id = analytics1 +openwifi.kafka.enable = true +openwifi.kafka.brokerlist = main.arilia.com:9093 +openwifi.kafka.auto.commit = false +openwifi.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 = prov.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.type = file +logging.path = $OWANALYTICS_ROOT/logs +logging.level = debug \ No newline at end of file diff --git a/owanalytics.service b/owanalytics.service new file mode 100644 index 0000000..e145464 --- /dev/null +++ b/owanalytics.service @@ -0,0 +1,22 @@ +[Unit] +Description=OpenWiFi Analytics Service +After=network-online.target docker.service +Wants=network-online.target + +[Service] +Type=simple +Environment="OWANALYTICS_ROOT=/home/admin/dev/wlan-cloud-analytics" +ExecStart=/home/admin/dev/wlan-cloud-analytics/cmake-build/owanalytics +WorkingDirectory=/home/admin/dev/wlan-cloud-analytics +# ExecReload=/bin/kill -s HUP $MAINPID +User=admin +# TimeoutSec=0 +RestartSec=2 +Restart=always +StartLimitBurst=3 +# KillMode=process +LimitNOFILE=500000 +LimitNPROC=500000 + +[Install] +WantedBy=multi-user.target diff --git a/set_env.sh b/set_env.sh new file mode 100755 index 0000000..306c21c --- /dev/null +++ b/set_env.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +export OWANALYTICS_CONFIG=`pwd` +export OWANALYTICS_ROOT=`pwd` diff --git a/src/Daemon.cpp b/src/Daemon.cpp new file mode 100644 index 0000000..ca576b0 --- /dev/null +++ b/src/Daemon.cpp @@ -0,0 +1,56 @@ +// +// 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" + +namespace OpenWifi { + 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, + SubSystemVec{ + OpenWifi::StorageService() + }); + } + return instance_; + } + + void Daemon::initialize() { + } + + void MicroServicePostInitialization() { + Daemon()->initialize(); + } +} + +int main(int argc, char **argv) { + try { + auto App = OpenWifi::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..0b145e7 --- /dev/null +++ b/src/Daemon.h @@ -0,0 +1,51 @@ +// +// 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. +// + +#pragma once + +#include +#include +#include +#include +#include + +#include "Dashboard.h" +#include "framework/MicroService.h" +#include "framework/OpenWifiTypes.h" +#include "RESTObjects/RESTAPI_AnalyticsObjects.h" + +namespace OpenWifi { + + static const char * vDAEMON_PROPERTIES_FILENAME = "owanalytics.properties"; + static const char * vDAEMON_ROOT_ENV_VAR = "OWANALYTICS_ROOT"; + static const char * vDAEMON_CONFIG_ENV_VAR = "OWANALYTICS_CONFIG"; + static const char * vDAEMON_APP_NAME = uSERVICE_ANALYTICS.c_str() ; + static const uint64_t vDAEMON_BUS_TIMER = 10000; + + class Daemon : public MicroService { + public: + explicit Daemon(const std::string & PropFile, + const std::string & RootEnv, + const std::string & ConfigEnv, + const std::string & AppName, + uint64_t BusTimer, + const SubSystemVec & SubSystems) : + MicroService( PropFile, RootEnv, ConfigEnv, AppName, BusTimer, SubSystems) {}; + + void initialize(); + static Daemon *instance(); + inline OpenWifi::AnalyticsDashboard & GetDashboard() { return DB_; } + Poco::Logger & Log() { return Poco::Logger::get(AppName()); } + private: + static Daemon *instance_; + OpenWifi::AnalyticsDashboard DB_{}; + }; + + inline Daemon * Daemon() { return Daemon::instance(); } +} + diff --git a/src/Dashboard.cpp b/src/Dashboard.cpp new file mode 100644 index 0000000..4c05a02 --- /dev/null +++ b/src/Dashboard.cpp @@ -0,0 +1,21 @@ +// +// 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 "Dashboard.h" +#include "StorageService.h" + +namespace OpenWifi { + void AnalyticsDashboard::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..598eb12 --- /dev/null +++ b/src/Dashboard.h @@ -0,0 +1,23 @@ +// +// 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. +// +#pragma once + +#include "framework/OpenWifiTypes.h" +#include "RESTObjects/RESTAPI_AnalyticsObjects.h" + +namespace OpenWifi { + class AnalyticsDashboard { + public: + void Create(); + [[nodiscard]] const AnalyticsObjects::Report & Report() const { return DB_;} + inline void Reset() { LastRun_=0; DB_.reset(); } + private: + AnalyticsObjects::Report DB_{}; + uint64_t LastRun_=0; + }; +} diff --git a/src/RESTAPI/RESTAPI_routers.cpp b/src/RESTAPI/RESTAPI_routers.cpp new file mode 100644 index 0000000..355398e --- /dev/null +++ b/src/RESTAPI/RESTAPI_routers.cpp @@ -0,0 +1,23 @@ +// +// Created by stephane bourque on 2021-10-23. +// + +#include "framework/MicroService.h" + +namespace OpenWifi { + + Poco::Net::HTTPRequestHandler * RESTAPI_ExtRouter(const char *Path, RESTAPIHandler::BindingMap &Bindings, + Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t TransactionId) { + return RESTAPI_Router< + RESTAPI_system_command + >(Path,Bindings,L, S, TransactionId); + } + + Poco::Net::HTTPRequestHandler * RESTAPI_IntRouter(const char *Path, RESTAPIHandler::BindingMap &Bindings, + Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t TransactionId) { + return RESTAPI_Router_I< + RESTAPI_system_command + >(Path, Bindings, L, S, TransactionId); + } + +} \ No newline at end of file diff --git a/src/RESTObjects/RESTAPI_AnalyticsObjects.cpp b/src/RESTObjects/RESTAPI_AnalyticsObjects.cpp new file mode 100644 index 0000000..1ca24a2 --- /dev/null +++ b/src/RESTObjects/RESTAPI_AnalyticsObjects.cpp @@ -0,0 +1,18 @@ +// +// Created by stephane bourque on 2022-01-10. +// + +#include "RESTAPI_AnalyticsObjects.h" + + +namespace OpenWifi::AnalyticsObjects { + + void Report::reset() { + + } + + void Report::to_json(Poco::JSON::Object &Obj) const { + + } + +} \ No newline at end of file diff --git a/src/RESTObjects/RESTAPI_AnalyticsObjects.h b/src/RESTObjects/RESTAPI_AnalyticsObjects.h new file mode 100644 index 0000000..c82b3a7 --- /dev/null +++ b/src/RESTObjects/RESTAPI_AnalyticsObjects.h @@ -0,0 +1,22 @@ +// +// Created by stephane bourque on 2022-01-10. +// + +#pragma once +#include "framework/MicroService.h" + +namespace OpenWifi { + + namespace AnalyticsObjects { + + struct Report { + uint64_t snapShot=0; + Types::CountedMap tenants; + + void reset(); + void to_json(Poco::JSON::Object &Obj) const; + }; + + } + +} \ No newline at end of file diff --git a/src/RESTObjects/RESTAPI_CertObjects.cpp b/src/RESTObjects/RESTAPI_CertObjects.cpp new file mode 100644 index 0000000..f3aca35 --- /dev/null +++ b/src/RESTObjects/RESTAPI_CertObjects.cpp @@ -0,0 +1,178 @@ +// +// Created by stephane bourque on 2021-12-07. +// + +#include "RESTAPI_CertObjects.h" + +using OpenWifi::RESTAPI_utils::field_to_json; +using OpenWifi::RESTAPI_utils::field_from_json; + +namespace OpenWifi { + namespace CertObjects { + void CertificateEntry::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"id", id); + field_to_json(Obj,"entity", entity); + field_to_json(Obj,"creator", creator); + field_to_json(Obj,"type", type); + field_to_json(Obj,"status", status); + field_to_json(Obj,"certificate", certificate); + field_to_json(Obj,"key", key); + field_to_json(Obj,"devid", devid); + field_to_json(Obj,"cas", cas); + field_to_json(Obj,"manufacturer", manufacturer); + field_to_json(Obj,"model", model); + field_to_json(Obj,"redirector", redirector); + field_to_json(Obj,"commonName", commonName); + field_to_json(Obj,"certificateId", certificateId); + field_to_json(Obj,"batch", batch); + field_to_json(Obj,"created", created); + field_to_json(Obj,"modified", modified); + field_to_json(Obj,"revoked", revoked); + field_to_json(Obj,"revokeCount", revokeCount); + } + + bool CertificateEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"id", id); + field_from_json(Obj,"entity", entity); + field_from_json(Obj,"creator", creator); + field_from_json(Obj,"type", type); + field_from_json(Obj,"status", status); + field_from_json(Obj,"certificate", certificate); + field_from_json(Obj,"key", key); + field_from_json(Obj,"devid", devid); + field_from_json(Obj,"cas", cas); + field_from_json(Obj,"manufacturer", manufacturer); + field_from_json(Obj,"model", model); + field_from_json(Obj,"redirector", redirector); + field_from_json(Obj,"commonName", commonName); + field_from_json(Obj,"certificateId", certificateId); + field_from_json(Obj,"batch", batch); + field_from_json(Obj,"created", created); + field_from_json(Obj,"modified", modified); + field_from_json(Obj,"revoked", revoked); + field_from_json(Obj,"revokeCount", revokeCount); + return true; + } catch (...) { + } + return false; + } + + void EntityEntry::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"id", id); + field_to_json(Obj,"creator", creator); + field_to_json(Obj,"name", name); + field_to_json(Obj,"description", description); + field_to_json(Obj,"defaultRedirector", defaultRedirector); + field_to_json(Obj,"apiKey", apiKey); + field_to_json(Obj,"serverEnrollmentProfile", serverEnrollmentProfile); + field_to_json(Obj,"clientEnrollmentProfile", clientEnrollmentProfile); + field_to_json(Obj,"organization", organization); + field_to_json(Obj,"created", created); + field_to_json(Obj,"modified", modified); + field_to_json(Obj,"suspended", suspended); + field_to_json(Obj,"deleted", deleted); + field_to_json(Obj,"notes", notes); + } + + bool EntityEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"id", id); + field_from_json(Obj,"creator", creator); + field_from_json(Obj,"name", name); + field_from_json(Obj,"description", description); + field_from_json(Obj,"defaultRedirector", defaultRedirector); + field_from_json(Obj,"apiKey", apiKey); + field_from_json(Obj,"serverEnrollmentProfile", serverEnrollmentProfile); + field_from_json(Obj,"clientEnrollmentProfile", clientEnrollmentProfile); + field_from_json(Obj,"organization", organization); + field_from_json(Obj,"created", created); + field_from_json(Obj,"modified", modified); + field_from_json(Obj,"suspended", suspended); + field_from_json(Obj,"deleted", deleted); + field_from_json(Obj,"notes", notes); + return true; + } catch (...) { + } + return false; + } + + void BatchEntry::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"id", id); + field_to_json(Obj,"entity", entity); + field_to_json(Obj,"creator", creator); + field_to_json(Obj,"name", name); + field_to_json(Obj,"description", description); + field_to_json(Obj,"manufacturer", manufacturer); + field_to_json(Obj,"model", model); + field_to_json(Obj,"redirector", redirector); + field_to_json(Obj,"commonNames", commonNames); + field_to_json(Obj,"jobHistory", jobHistory); + field_to_json(Obj,"notes", notes); + field_to_json(Obj,"submitted", submitted); + field_to_json(Obj,"started", started); + field_to_json(Obj,"completed", completed); + field_to_json(Obj,"modified", modified); + } + + bool BatchEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"id", id); + field_from_json(Obj,"entity", entity); + field_from_json(Obj,"creator", creator); + field_from_json(Obj,"name", name); + field_from_json(Obj,"description", description); + field_from_json(Obj,"manufacturer", manufacturer); + field_from_json(Obj,"model", model); + field_from_json(Obj,"redirector", redirector); + field_from_json(Obj,"commonNames", commonNames); + field_from_json(Obj,"jobHistory", jobHistory); + field_from_json(Obj,"notes", notes); + field_from_json(Obj,"submitted", submitted); + field_from_json(Obj,"started", started); + field_from_json(Obj,"completed", completed); + field_from_json(Obj,"modified", modified); + return true; + } catch (...) { + } + return false; + } + + void JobEntry::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"id", id); + field_to_json(Obj,"entity", entity); + field_to_json(Obj,"creator", creator); + field_to_json(Obj,"batch", batch); + field_to_json(Obj,"commonNames", commonNames); + field_to_json(Obj,"completedNames", completedNames); + field_to_json(Obj,"errorNames", errorNames); + field_to_json(Obj,"status", status); + field_to_json(Obj,"command", command); + field_to_json(Obj,"parameters", parameters); + field_to_json(Obj,"submitted", submitted); + field_to_json(Obj,"started", started); + field_to_json(Obj,"completed", completed); + } + + bool JobEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"id", id); + field_from_json(Obj,"entity", entity); + field_from_json(Obj,"creator", creator); + field_from_json(Obj,"batch", batch); + field_from_json(Obj,"commonNames", commonNames); + field_from_json(Obj,"completedNames", completedNames); + field_from_json(Obj,"errorNames", errorNames); + field_from_json(Obj,"status", status); + field_from_json(Obj,"command", command); + field_from_json(Obj,"parameters", parameters); + field_from_json(Obj,"submitted", submitted); + field_from_json(Obj,"started", started); + field_from_json(Obj,"completed", completed); + return true; + } catch (...) { + } + return false; + } + } +} \ No newline at end of file diff --git a/src/RESTObjects/RESTAPI_CertObjects.h b/src/RESTObjects/RESTAPI_CertObjects.h new file mode 100644 index 0000000..aeed486 --- /dev/null +++ b/src/RESTObjects/RESTAPI_CertObjects.h @@ -0,0 +1,101 @@ +// +// Created by stephane bourque on 2021-12-07. +// + +#pragma once + +#include +#include "framework/MicroService.h" +#include "framework/OpenWifiTypes.h" +#include "RESTObjects/RESTAPI_SecurityObjects.h" + +namespace OpenWifi { + + namespace CertObjects { + + struct CertificateEntry { + OpenWifi::Types::UUID_t id; + OpenWifi::Types::UUID_t entity; + OpenWifi::Types::UUID_t creator; + std::string type; + std::string status; + std::string certificate; + std::string key; + std::string devid; + std::string cas; + std::string manufacturer; + std::string model; + std::string redirector; + std::string commonName; + std::string certificateId; + OpenWifi::Types::UUID_t batch; + uint64_t created = 0; + uint64_t modified = 0; + uint64_t revoked = 0; + uint64_t revokeCount = 0; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct EntityEntry { + OpenWifi::Types::UUID_t id; + OpenWifi::Types::UUID_t creator; + std::string name; + std::string description; + std::string defaultRedirector; + std::string apiKey; + std::string serverEnrollmentProfile; + std::string clientEnrollmentProfile; + std::string organization; + SecurityObjects::NoteInfoVec notes; + bool suspended=false; + bool deleted=false; + uint64_t created = 0 ; + uint64_t modified = 0 ; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct BatchEntry { + OpenWifi::Types::UUID_t id; + OpenWifi::Types::UUID_t entity; + OpenWifi::Types::UUID_t creator; + std::string name; + std::string description; + std::string manufacturer; + std::string model; + std::string redirector; + std::vector commonNames; + std::vector jobHistory; + SecurityObjects::NoteInfoVec notes; + uint64_t submitted = 0 ; + uint64_t started = 0 ; + uint64_t completed = 0 ; + uint64_t modified = 0 ; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct JobEntry { + OpenWifi::Types::UUID_t id; + OpenWifi::Types::UUID_t entity; + OpenWifi::Types::UUID_t creator; + OpenWifi::Types::UUID_t batch; + std::string command; + OpenWifi::Types::StringVec commonNames; + OpenWifi::Types::StringVec completedNames; + OpenWifi::Types::StringVec errorNames; + Types::StringPairVec parameters; + std::string status; + uint64_t submitted=0; + uint64_t started=0; + uint64_t completed=0; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + } +} \ No newline at end of file diff --git a/src/RESTObjects/RESTAPI_FMSObjects.cpp b/src/RESTObjects/RESTAPI_FMSObjects.cpp new file mode 100644 index 0000000..78d0062 --- /dev/null +++ b/src/RESTObjects/RESTAPI_FMSObjects.cpp @@ -0,0 +1,248 @@ +// +// Created by stephane bourque on 2021-07-12. +// + +#include "RESTAPI_FMSObjects.h" +#include "framework/MicroService.h" + +using OpenWifi::RESTAPI_utils::field_to_json; +using OpenWifi::RESTAPI_utils::field_from_json; + +namespace OpenWifi::FMSObjects { + + void Firmware::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "id", id); + field_to_json(Obj, "release", release); + field_to_json(Obj, "deviceType", deviceType); + field_to_json(Obj, "description", description); + field_to_json(Obj, "revision", revision); + field_to_json(Obj, "uri", uri); + field_to_json(Obj, "image", image); + field_to_json(Obj, "imageDate", imageDate); + field_to_json(Obj, "size", size); + field_to_json(Obj, "downloadCount", downloadCount); + field_to_json(Obj, "firmwareHash", firmwareHash); + field_to_json(Obj, "owner", owner); + field_to_json(Obj, "location", location); + field_to_json(Obj, "uploader", uploader); + field_to_json(Obj, "digest", digest); + field_to_json(Obj, "latest", latest); + field_to_json(Obj, "notes", notes); + field_to_json(Obj, "created", created); + }; + + bool Firmware::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "id", id); + field_from_json(Obj, "release", release); + field_from_json(Obj, "deviceType", deviceType); + field_from_json(Obj, "description", description); + field_from_json(Obj, "revision", revision); + field_from_json(Obj, "uri", uri); + field_from_json(Obj, "image", image); + field_from_json(Obj, "imageDate", imageDate); + field_from_json(Obj, "size", size); + field_from_json(Obj, "downloadCount", downloadCount); + field_from_json(Obj, "firmwareHash", firmwareHash); + field_from_json(Obj, "owner", owner); + field_from_json(Obj, "location", location); + field_from_json(Obj, "uploader", uploader); + field_from_json(Obj, "digest", digest); + field_from_json(Obj, "latest", latest); + field_from_json(Obj, "notes", notes); + field_from_json(Obj, "created", created); + return true; + } catch (...) { + + } + return true; + } + + void FirmwareList::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"firmwares",firmwares); + } + + bool FirmwareList::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "firmwares", firmwares); + return true; + } catch (...) { + + } + return false; + } + + void DeviceType::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "id", id); + field_to_json(Obj, "deviceType", deviceType); + field_to_json(Obj, "manufacturer", manufacturer); + field_to_json(Obj, "model", model); + field_to_json(Obj, "policy", policy); + field_to_json(Obj, "notes", notes); + field_to_json(Obj, "lastUpdate", lastUpdate); + field_to_json(Obj, "created", created); + field_to_json(Obj, "id", id); + field_to_json(Obj, "id", id); + field_to_json(Obj, "id", id); + } + + bool DeviceType::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "id", id); + field_from_json(Obj, "deviceType", deviceType); + field_from_json(Obj, "manufacturer", manufacturer); + field_from_json(Obj, "model", model); + field_from_json(Obj, "policy", policy); + field_from_json(Obj, "notes", notes); + field_from_json(Obj, "lastUpdate", lastUpdate); + field_from_json(Obj, "created", created); + field_from_json(Obj, "id", id); + field_from_json(Obj, "id", id); + field_from_json(Obj, "id", id); + return true; + } catch (...) { + + } + return false; + } + + void DeviceTypeList::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"deviceTypes", deviceTypes); + } + + bool DeviceTypeList::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"deviceTypes", deviceTypes); + return true; + } catch(...) { + + } + return false; + } + + void RevisionHistoryEntry::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "id", id); + field_to_json(Obj, "serialNumber", serialNumber); + field_to_json(Obj, "fromRelease", fromRelease); + field_to_json(Obj, "toRelease", toRelease); + field_to_json(Obj, "commandUUID", commandUUID); + field_to_json(Obj, "revisionId", revisionId); + field_to_json(Obj, "upgraded", upgraded); + } + + bool RevisionHistoryEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "id", id); + field_from_json(Obj, "serialNumber", serialNumber); + field_from_json(Obj, "fromRelease", fromRelease); + field_from_json(Obj, "toRelease", toRelease); + field_from_json(Obj, "commandUUID", commandUUID); + field_from_json(Obj, "revisionId", revisionId); + field_from_json(Obj, "upgraded", upgraded); + return true; + } catch(...) { + + } + return false; + } + + void RevisionHistoryEntryList::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"deviceTypes", history); + } + + bool RevisionHistoryEntryList::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"deviceTypes", history); + return true; + } catch(...) { + + } + return false; + } + + void FirmwareAgeDetails::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"latestId", latestId); + field_to_json(Obj,"image", image); + field_to_json(Obj,"imageDate", imageDate); + field_to_json(Obj,"revision", revision); + field_to_json(Obj,"uri", uri); + field_to_json(Obj,"age", age); + field_to_json(Obj,"latest",latest); + } + + bool FirmwareAgeDetails::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"latestId", latestId); + field_from_json(Obj,"image", image); + field_from_json(Obj,"imageDate", imageDate); + field_from_json(Obj,"revision", revision); + field_from_json(Obj,"uri", uri); + field_from_json(Obj,"age", age); + field_from_json(Obj,"latest", latest); + return true; + } catch(...) { + + } + return false; + } + + void DeviceConnectionInformation::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "serialNumber", serialNumber); + field_to_json(Obj, "revision", revision); + field_to_json(Obj, "deviceType", deviceType); + field_to_json(Obj, "endPoint", endPoint); + field_to_json(Obj, "lastUpdate", lastUpdate); + field_to_json(Obj, "status", status); + } + + bool DeviceConnectionInformation::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "serialNumber", serialNumber); + field_from_json(Obj, "revision", revision); + field_from_json(Obj, "deviceType", deviceType); + field_from_json(Obj, "endPoint", endPoint); + field_from_json(Obj, "lastUpdate", lastUpdate); + field_from_json(Obj, "status", status); + return true; + } catch(...) { + + } + return false; + } + + void DeviceReport::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "ouis",OUI_); + field_to_json(Obj, "revisions", Revisions_); + field_to_json(Obj, "deviceTypes", DeviceTypes_); + field_to_json(Obj, "status", Status_); + field_to_json(Obj, "endPoints", EndPoints_); + field_to_json(Obj, "usingLatest", UsingLatest_); + field_to_json(Obj, "unknownFirmwares", UnknownFirmwares_); + field_to_json(Obj,"snapshot",snapshot); + field_to_json(Obj,"numberOfDevices",numberOfDevices); + field_to_json(Obj, "totalSecondsOld", totalSecondsOld_); + } + + void DeviceReport::reset() { + OUI_.clear(); + Revisions_.clear(); + DeviceTypes_.clear(); + Status_.clear(); + EndPoints_.clear(); + UsingLatest_.clear(); + UnknownFirmwares_.clear(); + totalSecondsOld_.clear(); + numberOfDevices = 0 ; + snapshot = std::time(nullptr); + } + + bool DeviceReport::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + + return true; + } catch (...) { + + } + return false; + } +} diff --git a/src/RESTObjects/RESTAPI_FMSObjects.h b/src/RESTObjects/RESTAPI_FMSObjects.h new file mode 100644 index 0000000..366523e --- /dev/null +++ b/src/RESTObjects/RESTAPI_FMSObjects.h @@ -0,0 +1,133 @@ +// +// Created by stephane bourque on 2021-07-12. +// + +#include + +#ifndef UCENTRALFMS_RESTAPI_FMSOBJECTS_H +#define UCENTRALFMS_RESTAPI_FMSOBJECTS_H + + +#include "RESTAPI_SecurityObjects.h" +#include "framework/OpenWifiTypes.h" + +namespace OpenWifi::FMSObjects { + + struct Firmware { + std::string id; + std::string release; + std::string deviceType; + std::string description; + std::string revision; + std::string uri; + std::string image; + uint64_t imageDate=0; + uint64_t size=0; + uint64_t downloadCount=0; + std::string firmwareHash; + std::string owner; + std::string location; + std::string uploader; + std::string digest; + bool latest=0; + SecurityObjects::NoteInfoVec notes; + uint64_t created=0; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector FirmwareVec; + + struct FirmwareList { + FirmwareVec firmwares; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct DeviceType { + std::string id; + std::string deviceType; + std::string manufacturer; + std::string model; + std::string policy; + SecurityObjects::NoteInfoVec notes; + uint64_t lastUpdate=0; + uint64_t created=0; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector DeviceTypeVec; + + struct DeviceTypeList { + DeviceTypeVec deviceTypes; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct RevisionHistoryEntry { + std::string id; + std::string serialNumber; + std::string fromRelease; + std::string toRelease; + std::string commandUUID; + std::string revisionId; + uint64_t upgraded; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector RevisionHistoryEntryVec; + + struct RevisionHistoryEntryList { + RevisionHistoryEntryVec history; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct FirmwareAgeDetails { + std::string latestId; + std::string image; + uint64_t imageDate; + std::string revision; + std::string uri; + uint64_t age=0; + bool latest=true; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct DeviceConnectionInformation { + std::string serialNumber; + std::string revision; + std::string deviceType; + std::string endPoint; + uint64_t lastUpdate; + std::string status; + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct DeviceReport { + uint64_t snapshot=0; + uint64_t numberOfDevices=0; + Types::CountedMap OUI_; + Types::CountedMap Revisions_; + Types::CountedMap DeviceTypes_; + Types::CountedMap Status_; + Types::CountedMap EndPoints_; + Types::CountedMap UsingLatest_; + Types::CountedMap UnknownFirmwares_; + Types::CountedMap totalSecondsOld_; + void to_json(Poco::JSON::Object &Obj) const; + void reset(); + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; +} + + +#endif //UCENTRALFMS_RESTAPI_FMSOBJECTS_H diff --git a/src/RESTObjects/RESTAPI_GWobjects.cpp b/src/RESTObjects/RESTAPI_GWobjects.cpp new file mode 100644 index 0000000..be66a0a --- /dev/null +++ b/src/RESTObjects/RESTAPI_GWobjects.cpp @@ -0,0 +1,269 @@ +// +// 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 "Daemon.h" +#ifdef TIP_GATEWAY_SERVICE +#include "DeviceRegistry.h" +#include "CapabilitiesCache.h" +#endif + +#include "RESTAPI_GWobjects.h" +#include "framework/MicroService.h" + +using OpenWifi::RESTAPI_utils::field_to_json; +using OpenWifi::RESTAPI_utils::field_from_json; +using OpenWifi::RESTAPI_utils::EmbedDocument; + +namespace OpenWifi::GWObjects { + + void Device::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"serialNumber", SerialNumber); +#ifdef TIP_GATEWAY_SERVICE + field_to_json(Obj,"deviceType", CapabilitiesCache::instance()->Get(Compatible)); +#endif + field_to_json(Obj,"macAddress", MACAddress); + field_to_json(Obj,"manufacturer", Manufacturer); + field_to_json(Obj,"UUID", UUID); + EmbedDocument("configuration", Obj, Configuration); + field_to_json(Obj,"notes", Notes); + field_to_json(Obj,"createdTimestamp", CreationTimestamp); + field_to_json(Obj,"lastConfigurationChange", LastConfigurationChange); + field_to_json(Obj,"lastConfigurationDownload", LastConfigurationDownload); + field_to_json(Obj,"lastFWUpdate", LastFWUpdate); + field_to_json(Obj,"owner", Owner); + field_to_json(Obj,"location", Location); + field_to_json(Obj,"venue", Venue); + field_to_json(Obj,"firmware", Firmware); + field_to_json(Obj,"compatible", Compatible); + field_to_json(Obj,"fwUpdatePolicy", FWUpdatePolicy); + field_to_json(Obj,"devicePassword", DevicePassword); + } + + void Device::to_json_with_status(Poco::JSON::Object &Obj) const { + to_json(Obj); + +#ifdef TIP_GATEWAY_SERVICE + ConnectionState ConState; + + if (DeviceRegistry()->GetState(SerialNumber, ConState)) { + ConState.to_json(Obj); + } else { + field_to_json(Obj,"ipAddress", ""); + field_to_json(Obj,"txBytes", (uint64_t) 0); + field_to_json(Obj,"rxBytes", (uint64_t )0); + field_to_json(Obj,"messageCount", (uint64_t )0); + field_to_json(Obj,"connected", false); + field_to_json(Obj,"lastContact", ""); + field_to_json(Obj,"verifiedCertificate", "NO_CERTIFICATE"); + field_to_json(Obj,"associations_2G", (uint64_t) 0); + field_to_json(Obj,"associations_5G", (uint64_t) 0); + } +#endif + } + + bool Device::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"serialNumber",SerialNumber); + field_from_json(Obj,"deviceType",DeviceType); + field_from_json(Obj,"macAddress",MACAddress); + field_from_json(Obj,"configuration",Configuration); + field_from_json(Obj,"notes",Notes); + field_from_json(Obj,"manufacturer",Manufacturer); + field_from_json(Obj,"owner",Owner); + field_from_json(Obj,"location",Location); + field_from_json(Obj,"venue",Venue); + field_from_json(Obj,"compatible",Compatible); + return true; + } catch (const Poco::Exception &E) { + } + return false; + } + + void Device::Print() const { + std::cout << "Device: " << SerialNumber << " DeviceType:" << DeviceType << " MACAddress:" << MACAddress << " Manufacturer:" + << Manufacturer << " " << Configuration << std::endl; + } + + void Statistics::to_json(Poco::JSON::Object &Obj) const { + EmbedDocument("data", Obj, Data); + field_to_json(Obj,"UUID", UUID); + field_to_json(Obj,"recorded", Recorded); + } + + void Capabilities::to_json(Poco::JSON::Object &Obj) const { + EmbedDocument("capabilities", Obj, Capabilities); + field_to_json(Obj,"firstUpdate", FirstUpdate); + field_to_json(Obj,"lastUpdate", LastUpdate); + } + + void DeviceLog::to_json(Poco::JSON::Object &Obj) const { + EmbedDocument("data", Obj, Data); + field_to_json(Obj,"log", Log); + field_to_json(Obj,"severity", Severity); + field_to_json(Obj,"recorded", Recorded); + field_to_json(Obj,"logType", LogType); + field_to_json(Obj,"UUID", UUID); + } + + void HealthCheck::to_json(Poco::JSON::Object &Obj) const { + EmbedDocument("values", Obj, Data); + field_to_json(Obj,"UUID", UUID); + field_to_json(Obj,"sanity", Sanity); + field_to_json(Obj,"recorded", Recorded); + } + + void DefaultConfiguration::to_json(Poco::JSON::Object &Obj) const { + EmbedDocument("configuration", Obj, Configuration); + field_to_json(Obj,"name", Name); + field_to_json(Obj,"modelIds", Models); + field_to_json(Obj,"description", Description); + field_to_json(Obj,"created", Created); + field_to_json(Obj,"lastModified", LastModified); + } + + void CommandDetails::to_json(Poco::JSON::Object &Obj) const { + EmbedDocument("details", Obj, Details); + EmbedDocument("results", Obj, Results); + field_to_json(Obj,"UUID", UUID); + field_to_json(Obj,"serialNumber", SerialNumber); + field_to_json(Obj,"command", Command); + field_to_json(Obj,"errorText", ErrorText); + field_to_json(Obj,"submittedBy", SubmittedBy); + field_to_json(Obj,"status", Status); + field_to_json(Obj,"submitted", Submitted); + field_to_json(Obj,"executed", Executed); + field_to_json(Obj,"completed", Completed); + field_to_json(Obj,"when", RunAt); + field_to_json(Obj,"errorCode", ErrorCode); + field_to_json(Obj,"custom", Custom); + field_to_json(Obj,"waitingForFile", WaitingForFile); + field_to_json(Obj,"attachFile", AttachDate); + field_to_json(Obj,"executionTime", executionTime); + } + + bool DefaultConfiguration::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"name",Name); + field_from_json(Obj,"configuration",Configuration); + field_from_json(Obj,"modelIds",Models); + field_from_json(Obj,"description",Description); + return true; + } catch (const Poco::Exception &E) { + } + return false; + } + + void BlackListedDevice::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"serialNumber", serialNumber); + field_to_json(Obj,"author", author); + field_to_json(Obj,"reason", reason); + field_to_json(Obj,"created", created); + } + + bool BlackListedDevice::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"serialNumber",serialNumber); + field_from_json(Obj,"author",author); + field_from_json(Obj,"reason",reason); + field_from_json(Obj,"created",created); + return true; + } catch (const Poco::Exception &E) { + } + return false; + } + + void ConnectionState::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"serialNumber", SerialNumber); + field_to_json(Obj,"ipAddress", Address); + field_to_json(Obj,"txBytes", TX); + field_to_json(Obj,"rxBytes", RX); + field_to_json(Obj,"messageCount", MessageCount); + field_to_json(Obj,"UUID", UUID); + field_to_json(Obj,"connected", Connected); + field_to_json(Obj,"firmware", Firmware); + field_to_json(Obj,"lastContact", LastContact); + field_to_json(Obj,"associations_2G", Associations_2G); + field_to_json(Obj,"associations_5G", Associations_5G); + field_to_json(Obj,"webSocketClients", webSocketClients); + field_to_json(Obj,"websocketPackets", websocketPackets); + field_to_json(Obj,"kafkaClients", kafkaClients); + field_to_json(Obj,"kafkaPackets", kafkaPackets); + + switch(VerifiedCertificate) { + case NO_CERTIFICATE: + field_to_json(Obj,"verifiedCertificate", "NO_CERTIFICATE"); break; + case VALID_CERTIFICATE: + field_to_json(Obj,"verifiedCertificate", "VALID_CERTIFICATE"); break; + case MISMATCH_SERIAL: + field_to_json(Obj,"verifiedCertificate", "MISMATCH_SERIAL"); break; + case VERIFIED: + field_to_json(Obj,"verifiedCertificate", "VERIFIED"); break; + default: + field_to_json(Obj,"verifiedCertificate", "NO_CERTIFICATE"); break; + } + } + + void RttySessionDetails::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"serialNumber", SerialNumber); + field_to_json(Obj,"server", Server); + field_to_json(Obj,"port", Port); + field_to_json(Obj,"token",Token); + field_to_json(Obj,"timeout", TimeOut); + field_to_json(Obj,"connectionId",ConnectionId); + field_to_json(Obj,"commandUUID",CommandUUID); + field_to_json(Obj,"started", Started); + field_to_json(Obj,"viewport",ViewPort); + field_to_json(Obj,"password",DevicePassword); + } + + void Dashboard::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"commands",commands); + field_to_json(Obj,"upTimes",upTimes); + field_to_json(Obj,"memoryUsed",memoryUsed); + field_to_json(Obj,"load1",load1); + field_to_json(Obj,"load5",load5); + field_to_json(Obj,"load15",load15); + field_to_json(Obj,"vendors",vendors); + field_to_json(Obj,"status",status); + field_to_json(Obj,"deviceType",deviceType); + field_to_json(Obj,"healths",healths); + field_to_json(Obj,"certificates",certificates); + field_to_json(Obj,"lastContact",lastContact); + field_to_json(Obj,"associations",associations); + field_to_json(Obj,"snapshot",snapshot); + field_to_json(Obj,"numberOfDevices",numberOfDevices); + } + + void Dashboard::reset() { + commands.clear(); + upTimes.clear(); + memoryUsed.clear(); + load1.clear(); + load5.clear(); + load15.clear(); + vendors.clear(); + status.clear(); + deviceType.clear(); + healths.clear(); + certificates.clear(); + lastContact.clear(); + associations.clear(); + numberOfDevices = 0 ; + snapshot = std::time(nullptr); + } + + void CapabilitiesModel::to_json(Poco::JSON::Object &Obj) const{ + field_to_json(Obj,"deviceType", deviceType); + field_to_json(Obj,"capabilities", capabilities); + }; + +} + diff --git a/src/RESTObjects/RESTAPI_GWobjects.h b/src/RESTObjects/RESTAPI_GWobjects.h new file mode 100644 index 0000000..dfc0ca2 --- /dev/null +++ b/src/RESTObjects/RESTAPI_GWobjects.h @@ -0,0 +1,197 @@ +// +// 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. +// + +#pragma once + +#include "Poco/JSON/Object.h" +#include "RESTAPI_SecurityObjects.h" + +namespace OpenWifi::GWObjects { + + enum CertificateValidation { + NO_CERTIFICATE, + VALID_CERTIFICATE, + MISMATCH_SERIAL, + VERIFIED + }; + + struct ConnectionState { + uint64_t MessageCount = 0 ; + std::string SerialNumber; + std::string Address; + uint64_t UUID = 0 ; + uint64_t PendingUUID = 0 ; + uint64_t TX = 0, RX = 0; + uint64_t Associations_2G=0; + uint64_t Associations_5G=0; + bool Connected = false; + uint64_t LastContact=0; + std::string Firmware; + CertificateValidation VerifiedCertificate = NO_CERTIFICATE; + std::string Compatible; + uint64_t kafkaClients=0; + uint64_t webSocketClients=0; + uint64_t kafkaPackets=0; + uint64_t websocketPackets=0; + void to_json(Poco::JSON::Object &Obj) const; + }; + + struct Device { + std::string SerialNumber; + std::string DeviceType; + std::string MACAddress; + std::string Manufacturer; + std::string Configuration; + SecurityObjects::NoteInfoVec Notes; + std::string Owner; + std::string Location; + std::string Firmware; + std::string Compatible; + std::string FWUpdatePolicy; + uint64_t UUID = 0 ; + uint64_t CreationTimestamp = 0 ; + uint64_t LastConfigurationChange = 0 ; + uint64_t LastConfigurationDownload = 0 ; + uint64_t LastFWUpdate = 0 ; + std::string Venue; + std::string DevicePassword; + void to_json(Poco::JSON::Object &Obj) const; + void to_json_with_status(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + void Print() const; + }; + + struct Statistics { + std::string SerialNumber; + uint64_t UUID = 0 ; + std::string Data; + uint64_t Recorded = 0; + void to_json(Poco::JSON::Object &Obj) const; + }; + + struct HealthCheck { + std::string SerialNumber; + uint64_t UUID = 0 ; + std::string Data; + uint64_t Recorded = 0 ; + uint64_t Sanity = 0 ; + void to_json(Poco::JSON::Object &Obj) const; + }; + + struct Capabilities { + std::string Capabilities; + uint64_t FirstUpdate = 0 ; + uint64_t LastUpdate = 0 ; + void to_json(Poco::JSON::Object &Obj) const; + }; + + struct DeviceLog { + enum Level { + LOG_EMERG = 0, /* system is unusable */ + LOG_ALERT = 1, /* action must be taken immediately */ + LOG_CRIT = 2, /* critical conditions */ + LOG_ERR = 3, /* error conditions */ + LOG_WARNING = 4, /* warning conditions */ + LOG_NOTICE = 5, /* normal but significant condition */ + LOG_INFO = 6, /* informational */ + LOG_DEBUG = 7 /* debug-level messages */ + }; + std::string SerialNumber; + std::string Log; + std::string Data; + uint64_t Severity = 0 ; + uint64_t Recorded = 0 ; + uint64_t LogType = 0 ; + uint64_t UUID = 0 ; + void to_json(Poco::JSON::Object &Obj) const; + }; + + struct DefaultConfiguration { + std::string Name; + std::string Configuration; + Types::StringVec Models; + std::string Description; + uint64_t Created; + uint64_t LastModified; + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + struct CommandDetails { + std::string UUID; + std::string SerialNumber; + std::string Command; + std::string Status; + std::string SubmittedBy; + std::string Results; + std::string Details; + std::string ErrorText; + uint64_t Submitted = time(nullptr); + uint64_t Executed = 0; + uint64_t Completed = 0 ; + uint64_t RunAt = 0 ; + uint64_t ErrorCode = 0 ; + uint64_t Custom = 0 ; + uint64_t WaitingForFile = 0 ; + uint64_t AttachDate = 0 ; + uint64_t AttachSize = 0 ; + std::string AttachType; + double executionTime = 0.0; + void to_json(Poco::JSON::Object &Obj) const; + }; + + struct BlackListedDevice { + std::string serialNumber; + std::string reason; + std::string author; + uint64_t created; + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + struct RttySessionDetails { + std::string SerialNumber; + std::string Server; + uint64_t Port = 0 ; + std::string Token; + uint64_t TimeOut = 0 ; + std::string ConnectionId; + uint64_t Started = 0 ; + std::string CommandUUID; + uint64_t ViewPort = 0 ; + std::string DevicePassword; + void to_json(Poco::JSON::Object &Obj) const; + }; + + struct Dashboard { + uint64_t snapshot = 0 ; + uint64_t numberOfDevices = 0 ; + Types::CountedMap commands; + Types::CountedMap upTimes; + Types::CountedMap memoryUsed; + Types::CountedMap load1; + Types::CountedMap load5; + Types::CountedMap load15; + Types::CountedMap vendors; + Types::CountedMap status; + Types::CountedMap deviceType; + Types::CountedMap healths; + Types::CountedMap certificates; + Types::CountedMap lastContact; + Types::CountedMap associations; + void to_json(Poco::JSON::Object &Obj) const; + void reset(); + }; + + struct CapabilitiesModel { + std::string deviceType; + std::string capabilities; + + void to_json(Poco::JSON::Object &Obj) const; + }; +} diff --git a/src/RESTObjects/RESTAPI_ProvObjects.cpp b/src/RESTObjects/RESTAPI_ProvObjects.cpp new file mode 100644 index 0000000..3f76c8f --- /dev/null +++ b/src/RESTObjects/RESTAPI_ProvObjects.cpp @@ -0,0 +1,633 @@ +// +// 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_ProvObjects.h" +#include "framework/MicroService.h" + +using OpenWifi::RESTAPI_utils::field_to_json; +using OpenWifi::RESTAPI_utils::field_from_json; + +namespace OpenWifi::ProvObjects { + + void ObjectInfo::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,"created",created); + field_to_json(Obj,"modified",modified); + field_to_json(Obj,"notes",notes); + field_to_json(Obj,"tags",tags); + } + + bool ObjectInfo::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,"created",created); + field_from_json(Obj,"modified",modified); + field_from_json(Obj,"notes",notes); + field_from_json(Obj,"tags",tags); + return true; + } catch(...) { + + } + return false; + } + + void ManagementPolicyEntry::to_json(Poco::JSON::Object &Obj) const { + field_to_json( Obj,"users",users); + field_to_json( Obj,"resources",resources); + field_to_json( Obj,"access",access); + field_to_json( Obj,"policy",policy); + } + + bool ManagementPolicyEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json( Obj,"users",users); + field_from_json( Obj,"resources",resources); + field_from_json( Obj,"access",access); + field_from_json( Obj,"policy",policy); + return true; + } catch(...) { + + } + return false; + } + + void ManagementPolicy::to_json(Poco::JSON::Object &Obj) const { + info.to_json(Obj); + field_to_json(Obj, "entries", entries); + field_to_json(Obj, "inUse", inUse); + field_to_json(Obj, "entity", entity); + } + + bool ManagementPolicy::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + info.from_json(Obj); + field_from_json(Obj, "entries", entries); + field_from_json(Obj, "inUse", inUse); + field_from_json(Obj, "entity", entity); + return true; + } catch(...) { + + } + return false; + } + + void Entity::to_json(Poco::JSON::Object &Obj) const { + info.to_json(Obj); + field_to_json( Obj,"parent",parent); + field_to_json( Obj,"venues",venues); + field_to_json( Obj,"children",children); + field_to_json( Obj,"contacts",contacts); + field_to_json( Obj,"locations",locations); + field_to_json( Obj,"managementPolicy",managementPolicy); + field_to_json( Obj,"deviceConfiguration",deviceConfiguration); + field_to_json( Obj,"devices",devices); + field_to_json( Obj,"rrm",rrm); + field_to_json( Obj,"sourceIP",sourceIP); + } + + bool Entity::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + info.from_json(Obj); + field_from_json( Obj,"parent",parent); + field_from_json( Obj,"venues",venues); + field_from_json( Obj,"children",children); + field_from_json( Obj,"contacts",contacts); + field_from_json( Obj,"locations",locations); + field_from_json( Obj,"managementPolicy",managementPolicy); + field_from_json( Obj,"deviceConfiguration",deviceConfiguration); + field_from_json( Obj,"devices",devices); + field_from_json( Obj,"rrm",rrm); + field_from_json( Obj,"sourceIP",sourceIP); + return true; + } catch(...) { + + } + return false; + } + + void DiGraphEntry::to_json(Poco::JSON::Object &Obj) const { + field_to_json( Obj,"parent",parent); + field_to_json( Obj,"child",child); + } + + bool DiGraphEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json( Obj,"parent",parent); + field_from_json( Obj,"child",child); + return true; + } catch (...) { + + } + return false; + } + + void Venue::to_json(Poco::JSON::Object &Obj) const { + info.to_json(Obj); + field_to_json( Obj,"parent",parent); + field_to_json( Obj,"entity",entity); + field_to_json( Obj,"children",children); + field_to_json( Obj,"devices",devices); + field_to_json( Obj,"topology",topology); + field_to_json( Obj,"parent",parent); + field_to_json( Obj,"design",design); + field_to_json( Obj,"managementPolicy",managementPolicy); + field_to_json( Obj,"deviceConfiguration",deviceConfiguration); + field_to_json( Obj,"contact",contact); + field_to_json( Obj,"location",location); + field_to_json( Obj,"rrm",rrm); + field_to_json( Obj,"sourceIP",sourceIP); + } + + bool Venue::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + info.from_json(Obj); + field_from_json( Obj,"parent",parent); + field_from_json( Obj,"entity",entity); + field_from_json( Obj,"children",children); + field_from_json( Obj,"devices",devices); + field_from_json( Obj,"topology",topology); + field_from_json( Obj,"parent",parent); + field_from_json( Obj,"design",design); + field_from_json( Obj,"managementPolicy",managementPolicy); + field_from_json( Obj,"deviceConfiguration",deviceConfiguration); + field_from_json( Obj,"contact",contact); + field_from_json( Obj,"location",location); + field_from_json( Obj,"rrm",rrm); + field_from_json( Obj,"sourceIP",sourceIP); + return true; + } catch (...) { + + } + return false; + } + + void UserInfoDigest::to_json(Poco::JSON::Object &Obj) const { + field_to_json( Obj,"id",id); + field_to_json( Obj,"entity",loginId); + field_to_json( Obj,"children",userType); + } + + bool UserInfoDigest::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json( Obj,"id",id); + field_from_json( Obj,"entity",loginId); + field_from_json( Obj,"children",userType); + return true; + } catch(...) { + } + return false; + } + + void ManagementRole::to_json(Poco::JSON::Object &Obj) const { + info.to_json(Obj); + field_to_json( Obj,"managementPolicy",managementPolicy); + field_to_json( Obj,"users",users); + field_to_json( Obj,"entity",entity); + } + + bool ManagementRole::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + info.from_json(Obj); + field_from_json( Obj,"managementPolicy",managementPolicy); + field_from_json( Obj,"users",users); + field_from_json( Obj,"entity",entity); + return true; + } catch(...) { + } + return false; + } + + void Location::to_json(Poco::JSON::Object &Obj) const { + info.to_json(Obj); + field_to_json( Obj,"type",OpenWifi::ProvObjects::to_string(type)); + field_to_json( Obj,"buildingName",buildingName); + field_to_json( Obj,"addressLines",addressLines); + field_to_json( Obj,"city",city); + field_to_json( Obj,"state",state); + field_to_json( Obj,"postal",postal); + field_to_json( Obj,"country",country); + field_to_json( Obj,"phones",phones); + field_to_json( Obj,"mobiles",mobiles); + field_to_json( Obj,"geoCode",geoCode); + field_to_json( Obj,"inUse",inUse); + field_to_json( Obj,"entity",entity); + field_to_json( Obj,"managementPolicy",managementPolicy); + } + + bool Location::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + info.from_json(Obj); + std::string tmp_type; + field_from_json( Obj,"type", tmp_type); + type = location_from_string(tmp_type); + field_from_json( Obj,"buildingName",buildingName); + field_from_json( Obj,"addressLines",addressLines); + field_from_json( Obj,"city",city); + field_from_json( Obj,"state",state); + field_from_json( Obj,"postal",postal); + field_from_json( Obj,"country",country); + field_from_json( Obj,"phones",phones); + field_from_json( Obj,"mobiles",mobiles); + field_from_json( Obj,"geoCode",geoCode); + field_from_json( Obj,"inUse",inUse); + field_from_json( Obj,"entity",entity); + field_from_json( Obj,"managementPolicy",managementPolicy); + return true; + } catch (...) { + + } + return false; + } + + void Contact::to_json(Poco::JSON::Object &Obj) const { + info.to_json(Obj); + field_to_json( Obj,"type", to_string(type)); + field_to_json( Obj,"title",title); + field_to_json( Obj,"salutation",salutation); + field_to_json( Obj,"firstname",firstname); + field_to_json( Obj,"lastname",lastname); + field_to_json( Obj,"initials",initials); + field_to_json( Obj,"visual",visual); + field_to_json( Obj,"mobiles",mobiles); + field_to_json( Obj,"phones",phones); + field_to_json( Obj,"primaryEmail",primaryEmail); + field_to_json( Obj,"secondaryEmail",secondaryEmail); + field_to_json( Obj,"accessPIN",accessPIN); + field_to_json( Obj,"inUse",inUse); + field_to_json( Obj,"entity",entity); + field_to_json( Obj,"managementPolicy",managementPolicy); + } + + bool Contact::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + info.from_json(Obj); + std::string tmp_type; + field_from_json( Obj,"type", tmp_type); + type = contact_from_string(tmp_type); + field_from_json( Obj,"title",title); + field_from_json( Obj,"salutation",salutation); + field_from_json( Obj,"firstname",firstname); + field_from_json( Obj,"lastname",lastname); + field_from_json( Obj,"initials",initials); + field_from_json( Obj,"visual",visual); + field_from_json( Obj,"mobiles",mobiles); + field_from_json( Obj,"phones",phones); + field_from_json( Obj,"primaryEmail",primaryEmail); + field_from_json( Obj,"secondaryEmail",secondaryEmail); + field_from_json( Obj,"accessPIN",accessPIN); + field_from_json( Obj,"inUse",inUse); + field_from_json( Obj,"entity",entity); + field_from_json( Obj,"managementPolicy",managementPolicy); + return true; + } catch (...) { + + } + return false; + } + + void InventoryTag::to_json(Poco::JSON::Object &Obj) const { + info.to_json(Obj); + field_to_json(Obj, "serialNumber", serialNumber); + field_to_json(Obj, "venue", venue); + field_to_json(Obj, "entity", entity); + field_to_json(Obj, "subscriber", subscriber); + field_to_json(Obj, "deviceType", deviceType); + field_to_json(Obj, "qrCode", qrCode); + field_to_json(Obj, "geoCode", geoCode); + field_to_json(Obj, "location", location); + field_to_json(Obj, "contact", contact); + field_to_json( Obj,"deviceConfiguration",deviceConfiguration); + field_to_json( Obj,"rrm",rrm); + field_to_json( Obj,"managementPolicy",managementPolicy); + } + + bool InventoryTag::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + info.from_json(Obj); + field_from_json( Obj,"serialNumber",serialNumber); + field_from_json( Obj,"venue",venue); + field_from_json( Obj,"entity",entity); + field_from_json( Obj,"subscriber",subscriber); + field_from_json( Obj,"deviceType",deviceType); + field_from_json(Obj, "qrCode", qrCode); + field_from_json( Obj,"geoCode",geoCode); + field_from_json( Obj,"location",location); + field_from_json( Obj,"contact",contact); + field_from_json( Obj,"deviceConfiguration",deviceConfiguration); + field_from_json( Obj,"rrm",rrm); + field_from_json( Obj,"managementPolicy",managementPolicy); + return true; + } catch(...) { + + } + return false; + } + + void DeviceConfigurationElement::to_json(Poco::JSON::Object &Obj) const { + field_to_json( Obj,"name", name); + field_to_json( Obj,"description", description); + field_to_json( Obj,"weight", weight); + field_to_json( Obj,"configuration", configuration); + } + + bool DeviceConfigurationElement::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json( Obj,"name",name); + field_from_json( Obj,"description",description); + field_from_json( Obj,"weight",weight); + field_from_json( Obj,"configuration",configuration); + return true; + } catch(...) { + + } + return false; + } + + void DeviceConfiguration::to_json(Poco::JSON::Object &Obj) const { + info.to_json(Obj); + field_to_json( Obj,"managementPolicy",managementPolicy); + field_to_json( Obj,"deviceTypes",deviceTypes); + field_to_json( Obj,"configuration",configuration); + field_to_json( Obj,"inUse",inUse); + field_to_json( Obj,"variables",variables); + field_to_json( Obj,"rrm",rrm); + field_to_json( Obj,"firmwareUpgrade",firmwareUpgrade); + field_to_json( Obj,"firmwareRCOnly",firmwareRCOnly); + } + + bool DeviceConfiguration::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + info.from_json(Obj); + field_from_json( Obj,"managementPolicy",managementPolicy); + field_from_json( Obj,"deviceTypes",deviceTypes); + field_from_json( Obj,"configuration",configuration); + field_from_json( Obj,"inUse",inUse); + field_from_json( Obj,"variables",variables); + field_from_json( Obj,"rrm",rrm); + field_from_json( Obj,"firmwareUpgrade",firmwareUpgrade); + field_from_json( Obj,"firmwareRCOnly",firmwareRCOnly); + return true; + } catch(...) { + + } + return false; + } + + void Report::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "snapshot", snapShot); + field_to_json(Obj, "devices", tenants); + }; + + void Report::reset() { + tenants.clear(); + } + + void ExpandedUseEntry::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "uuid", uuid); + field_to_json(Obj, "name", name); + field_to_json(Obj, "description", description); + } + + bool ExpandedUseEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json( Obj,"uuid",uuid); + field_from_json( Obj,"name",name); + field_from_json( Obj,"description",description); + return true; + } catch(...) { + + } + return false; + } + + void ExpandedUseEntryList::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "type", type); + field_to_json(Obj, "entries", entries); + } + + bool ExpandedUseEntryList::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json( Obj,"type",type); + field_from_json( Obj,"entries",entries); + return true; + } catch(...) { + + } + return false; + } + + void ExpandedUseEntryMapList::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "entries", entries); + } + + bool ExpandedUseEntryMapList::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json( Obj,"entries",entries); + return true; + } catch(...) { + + } + return false; + } + + void UuidList::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "list", list); + } + + bool UuidList::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "list", list); + return true; + } catch(...) { + + } + return false; + } + + void field_to_json(Poco::JSON::Object &Obj, const char * FieldName, ACLACCESS A) { + switch(A) { + case READ: Obj.set(FieldName,"read"); break; + case MODIFY: Obj.set(FieldName,"modify"); break; + case CREATE: Obj.set(FieldName,"create"); break; + case DELETE: Obj.set(FieldName,"delete"); break; + case NONE: + default: + Obj.set(FieldName,"none"); + } + } + + void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char * FieldName, ACLACCESS &A) { + if(Obj->has(FieldName)) { + auto V = Obj->getValue(FieldName); + if(V=="read") + A = READ; + else if(V=="modify") + A = MODIFY; + else if(V=="create") + A = CREATE; + else if(V=="delete") + A = DELETE; + else if(V=="none") + A = NONE; + else + throw Poco::Exception("invalid JSON"); + } + } + + void ObjectACL::to_json(Poco::JSON::Object &Obj) const { + RESTAPI_utils::field_to_json(Obj, "users", users); + RESTAPI_utils::field_to_json(Obj, "roles", roles); + field_to_json(Obj, "access", access); + } + + bool ObjectACL::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + RESTAPI_utils::field_from_json(Obj, "users", users); + RESTAPI_utils::field_from_json(Obj, "roles", roles); + field_from_json(Obj, "access", access); + return true; + } catch(...) { + + } + return false; + } + + void ObjectACLList::to_json(Poco::JSON::Object &Obj) const { + RESTAPI_utils::field_to_json(Obj, "list", list); + } + + bool ObjectACLList::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + RESTAPI_utils::field_from_json(Obj, "list", list); + return true; + } catch(...) { + + } + return false; + } + + std::string to_string(VISIBILITY A) { + switch(A) { + case PUBLIC: return "public"; + case SELECT: return "select"; + case PRIVATE: + default: + return "private"; + } + } + + void field_to_json(Poco::JSON::Object &Obj, const char * FieldName, VISIBILITY A) { + Obj.set(FieldName,to_string(A)); + } + + VISIBILITY visibility_from_string(const std::string &V) { + if(V=="public") + return PUBLIC; + else if(V=="select") + return SELECT; + else if(V=="private") + return PRIVATE; + throw Poco::Exception("invalid json"); + } + + void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char * FieldName, VISIBILITY &A) { + if(Obj->has(FieldName)) { + auto V = Obj->getValue(FieldName); + A = visibility_from_string(V); + } + } + + void Map::to_json(Poco::JSON::Object &Obj) const { + info.to_json(Obj); + RESTAPI_utils::field_to_json( Obj,"data",data); + RESTAPI_utils::field_to_json( Obj,"entity",entity); + RESTAPI_utils::field_to_json( Obj,"creator",creator); + field_to_json( Obj,"visibility",visibility); + RESTAPI_utils::field_to_json( Obj,"access",access); + } + + bool Map::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + info.from_json(Obj); + RESTAPI_utils::field_from_json( Obj,"data",data); + RESTAPI_utils::field_from_json( Obj,"entity",entity); + RESTAPI_utils::field_from_json( Obj,"creator",creator); + field_from_json( Obj,"visibility",visibility); + RESTAPI_utils::field_from_json( Obj,"access",access); + return true; + } catch(...) { + + } + return false; + } + + void MapList::to_json(Poco::JSON::Object &Obj) const { + RESTAPI_utils::field_to_json( Obj,"list",list); + } + + bool MapList::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + RESTAPI_utils::field_from_json( Obj,"list",list); + return true; + } catch(...) { + + } + return false; + } + + bool UpdateObjectInfo(const Poco::JSON::Object::Ptr &O, const SecurityObjects::UserInfo &U, ObjectInfo &I) { + uint64_t Now = std::time(nullptr); + if(O->has("name")) + I.name = O->get("name").toString(); + + if(I.name.empty()) + return false; + + if(O->has("description")) + I.description = O->get("description").toString(); + SecurityObjects::MergeNotes(O,U,I.notes); + SecurityObjects::NoteInfoVec N; + for(auto &i:I.notes) { + if(i.note.empty()) + continue; + N.push_back(SecurityObjects::NoteInfo{.created=Now,.createdBy=U.email,.note=i.note}); + } + I.modified = Now; + return true; + } + + bool CreateObjectInfo(const Poco::JSON::Object::Ptr &O, const SecurityObjects::UserInfo &U, ObjectInfo &I) { + uint64_t Now = std::time(nullptr); + if(O->has("name")) + I.name = O->get("name").toString(); + + if(I.name.empty()) + return false; + + if(O->has("description")) + I.description = O->get("description").toString(); + + SecurityObjects::NoteInfoVec N; + for(auto &i:I.notes) { + if(i.note.empty()) + continue; + N.push_back(SecurityObjects::NoteInfo{.created=Now,.createdBy=U.email,.note=i.note}); + } + I.notes = N; + I.modified = I.created = Now; + I.id = MicroService::CreateUUID(); + + return true; + } +} + diff --git a/src/RESTObjects/RESTAPI_ProvObjects.h b/src/RESTObjects/RESTAPI_ProvObjects.h new file mode 100644 index 0000000..6ac9b4f --- /dev/null +++ b/src/RESTObjects/RESTAPI_ProvObjects.h @@ -0,0 +1,380 @@ +// +// 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. +// + +#pragma once + +#include +#include "RESTAPI_SecurityObjects.h" + +namespace OpenWifi::ProvObjects { + + enum FIRMWARE_UPGRADE_RULES { + dont_upgrade, + upgrade_inherit, + upgrade_release_only, + upgrade_latest + }; + + struct ObjectInfo { + Types::UUID_t id; + std::string name; + std::string description; + SecurityObjects::NoteInfoVec notes; + uint64_t created=0; + uint64_t modified=0; + Types::TagList tags; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct ManagementPolicyEntry { + Types::UUIDvec_t users; + Types::UUIDvec_t resources; + Types::StringVec access; + std::string policy; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct ManagementPolicy { + ObjectInfo info; + std::vector entries; + Types::StringVec inUse; + Types::UUID_t entity; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector ManagementPolicyVec; + + struct Entity { + ObjectInfo info; + Types::UUID_t parent; + Types::UUIDvec_t children; + Types::UUIDvec_t venues; + Types::UUIDvec_t contacts; // all contacts associated in this entity + Types::UUIDvec_t locations; // all locations associated in this entity + Types::UUID_t managementPolicy; + Types::UUIDvec_t deviceConfiguration; + Types::UUIDvec_t devices; + std::string rrm; + Types::StringVec sourceIP; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector EntityVec; + + struct DiGraphEntry { + Types::UUID_t parent; + Types::UUID_t child; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + typedef std::vector DiGraph; + + struct Venue { + ObjectInfo info; + Types::UUID_t entity; + Types::UUID_t parent; + Types::UUIDvec_t children; + Types::UUID_t managementPolicy; + Types::UUIDvec_t devices; + DiGraph topology; + std::string design; + Types::UUIDvec_t deviceConfiguration; + std::string contact; + std::string location; + std::string rrm; + Types::StringVec sourceIP; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector VenueVec; + + struct UserInfoDigest { + std::string id; + std::string loginId; + std::string userType; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct ManagementRole { + ObjectInfo info; + Types::UUID_t managementPolicy; + Types::UUIDvec_t users; + Types::StringVec inUse; + Types::UUID_t entity; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector ManagementRoleVec; + + enum LocationType { + LT_SERVICE, LT_EQUIPMENT, LT_AUTO, LT_MANUAL, + LT_SPECIAL, LT_UNKNOWN, LT_CORPORATE + }; + + inline std::string to_string(LocationType L) { + switch(L) { + case LT_SERVICE: return "SERVICE"; + case LT_EQUIPMENT: return "EQUIPMENT"; + case LT_AUTO: return "AUTO"; + case LT_MANUAL: return "MANUAL"; + case LT_SPECIAL: return "SPECIAL"; + case LT_UNKNOWN: return "UNKNOWN"; + case LT_CORPORATE: return "CORPORATE"; + default: return "UNKNOWN"; + } + } + + inline LocationType location_from_string(const std::string &S) { + if(!Poco::icompare(S,"SERVICE")) + return LT_SERVICE; + else if(!Poco::icompare(S,"EQUIPMENT")) + return LT_EQUIPMENT; + else if(!Poco::icompare(S,"AUTO")) + return LT_AUTO; + else if(!Poco::icompare(S,"MANUAL")) + return LT_MANUAL; + else if(!Poco::icompare(S,"SPECIAL")) + return LT_SPECIAL; + else if(!Poco::icompare(S,"UNKNOWN")) + return LT_UNKNOWN; + else if(!Poco::icompare(S,"CORPORATE")) + return LT_CORPORATE; + return LT_UNKNOWN; + } + + struct Location { + ObjectInfo info; + LocationType type; + std::string buildingName; + Types::StringVec addressLines; + std::string city; + std::string state; + std::string postal; + std::string country; + Types::StringVec phones; + Types::StringVec mobiles; + std::string geoCode; + Types::StringVec inUse; + Types::UUID_t entity; + Types::UUID_t managementPolicy; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector LocationVec; + + enum ContactType { + CT_SUBSCRIBER, CT_USER, CT_INSTALLER, CT_CSR, CT_MANAGER, + CT_BUSINESSOWNER, CT_TECHNICIAN, CT_CORPORATE, CT_UNKNOWN + }; + + inline std::string to_string(ContactType L) { + switch(L) { + case CT_SUBSCRIBER: return "SUBSCRIBER"; + case CT_USER: return "USER"; + case CT_INSTALLER: return "INSTALLER"; + case CT_CSR: return "CSR"; + case CT_MANAGER: return "MANAGER"; + case CT_BUSINESSOWNER: return "BUSINESSOWNER"; + case CT_TECHNICIAN: return "TECHNICIAN"; + case CT_CORPORATE: return "CORPORATE"; + case CT_UNKNOWN: return "UNKNOWN"; + default: return "UNKNOWN"; + } + } + + inline ContactType contact_from_string(const std::string &S) { + if(!Poco::icompare(S,"SUBSCRIBER")) + return CT_SUBSCRIBER; + else if(!Poco::icompare(S,"USER")) + return CT_USER; + else if(!Poco::icompare(S,"INSTALLER")) + return CT_INSTALLER; + else if(!Poco::icompare(S,"CSR")) + return CT_CSR; + else if(!Poco::icompare(S,"BUSINESSOWNER")) + return CT_BUSINESSOWNER; + else if(!Poco::icompare(S,"TECHNICIAN")) + return CT_TECHNICIAN; + else if(!Poco::icompare(S,"CORPORATE")) + return CT_CORPORATE; + else if(!Poco::icompare(S,"UNKNOWN")) + return CT_UNKNOWN; + return CT_UNKNOWN; + } + + struct Contact { + ObjectInfo info; + ContactType type=CT_USER; + std::string title; + std::string salutation; + std::string firstname; + std::string lastname; + std::string initials; + std::string visual; + Types::StringVec mobiles; + Types::StringVec phones; + std::string primaryEmail; + std::string secondaryEmail; + std::string accessPIN; + Types::StringVec inUse; + Types::UUID_t entity; + Types::UUID_t managementPolicy; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector ContactVec; + + struct DeviceConfigurationElement { + std::string name; + std::string description; + uint64_t weight; + std::string configuration; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector DeviceConfigurationElementVec; + + struct DeviceConfiguration { + ObjectInfo info; + Types::UUID_t managementPolicy; + Types::StringVec deviceTypes; + DeviceConfigurationElementVec configuration; + Types::StringVec inUse; + Types::StringPairVec variables; + std::string rrm; + std::string firmwareUpgrade; + bool firmwareRCOnly=false; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector DeviceConfigurationVec; + + struct InventoryTag { + ObjectInfo info; + std::string serialNumber; + std::string venue; + std::string entity; + std::string subscriber; + std::string deviceType; + std::string qrCode; + std::string geoCode; + std::string location; + std::string contact; + std::string deviceConfiguration; + std::string rrm; + Types::UUID_t managementPolicy; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + typedef std::vector InventoryTagVec; + + struct Report { + uint64_t snapShot=0; + Types::CountedMap tenants; + + void reset(); + void to_json(Poco::JSON::Object &Obj) const; + }; + + struct ExpandedUseEntry { + std::string uuid; + std::string name; + std::string description; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct ExpandedUseEntryList { + std::string type; + std::vector entries; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct ExpandedUseEntryMapList { + std::vector entries; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct UuidList { + std::vector list; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + enum ACLACCESS { + NONE, READ, MODIFY, CREATE, DELETE + }; + + struct ObjectACL { + UuidList users; + UuidList roles; + ACLACCESS access = NONE; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct ObjectACLList { + std::vector list; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + enum VISIBILITY { + PUBLIC, PRIVATE, SELECT + }; + + std::string to_string(VISIBILITY A); + VISIBILITY visibility_from_string(const std::string &V); + + struct Map { + ObjectInfo info; + std::string data; + std::string entity; + std::string creator; + VISIBILITY visibility = PRIVATE; + ObjectACLList access; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + struct MapList { + std::vector list; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + + bool UpdateObjectInfo(const Poco::JSON::Object::Ptr &O, const SecurityObjects::UserInfo &U, ObjectInfo &I); + bool CreateObjectInfo(const Poco::JSON::Object::Ptr &O, const SecurityObjects::UserInfo &U, ObjectInfo &I); +}; diff --git a/src/RESTObjects/RESTAPI_SecurityObjects.cpp b/src/RESTObjects/RESTAPI_SecurityObjects.cpp new file mode 100644 index 0000000..c368ee2 --- /dev/null +++ b/src/RESTObjects/RESTAPI_SecurityObjects.cpp @@ -0,0 +1,589 @@ +// +// 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 "framework/MicroService.h" +#include "RESTAPI_SecurityObjects.h" + +using OpenWifi::RESTAPI_utils::field_to_json; +using OpenWifi::RESTAPI_utils::field_from_json; + +namespace OpenWifi::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,"partner")) + return PARTNER; + else if (!Poco::icompare(U,"csr")) + return CSR; + else if (!Poco::icompare(U, "system")) + return SYSTEM; + else if (!Poco::icompare(U, "installer")) + return INSTALLER; + else if (!Poco::icompare(U, "noc")) + return NOC; + else if (!Poco::icompare(U, "accounting")) + return ACCOUNTING; + return UNKNOWN; + } + + std::string UserTypeToString(USER_ROLE U) { + switch(U) { + case ROOT: return "root"; + case ADMIN: return "admin"; + case SUBSCRIBER: return "subscriber"; + case PARTNER: return "partner"; + case CSR: return "csr"; + case SYSTEM: return "system"; + case INSTALLER: return "installer"; + case NOC: return "noc"; + case ACCOUNTING: return "accounting"; + case UNKNOWN: + 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 MobilePhoneNumber::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"number", number); + field_to_json(Obj,"verified", verified); + field_to_json(Obj,"primary", primary); + } + + bool MobilePhoneNumber::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"number",number); + field_from_json(Obj,"verified",verified); + field_from_json(Obj,"primary",primary); + return true; + } catch (...) { + + } + return false; + }; + + void MfaAuthInfo::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"enabled", enabled); + field_to_json(Obj,"method", method); + } + + bool MfaAuthInfo::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"enabled",enabled); + field_from_json(Obj,"method",method); + return true; + } catch (...) { + + } + return false; + } + + void UserLoginLoginExtensions::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "mobiles", mobiles); + field_to_json(Obj, "mfa", mfa); + } + + bool UserLoginLoginExtensions::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"mobiles",mobiles); + field_from_json(Obj,"mfa",mfa); + return true; + } catch (...) { + + } + return false; + } + + void MFAChallengeRequest::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "uuid", uuid); + field_to_json(Obj, "question", question); + field_to_json(Obj, "created", created); + field_to_json(Obj, "method", method); + } + + bool MFAChallengeRequest::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"uuid",uuid); + field_from_json(Obj,"question",question); + field_from_json(Obj,"created",created); + field_from_json(Obj,"method",method); + return true; + } catch (...) { + + } + return false; + }; + + void MFAChallengeResponse::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "uuid", uuid); + field_to_json(Obj, "answer", answer); + + } + + bool MFAChallengeResponse::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"uuid",uuid); + field_from_json(Obj,"answer",answer); + 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,"location", location); + field_from_json(Obj,"owner", owner); + 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); + return true; + } catch(...) { + + } + return false; + } + + bool MergeNotes(Poco::JSON::Object::Ptr Obj, const UserInfo &UInfo, NoteInfoVec & Notes) { + try { + if(Obj->has("notes") && Obj->isArray("notes")) { + SecurityObjects::NoteInfoVec NIV; + NIV = RESTAPI_utils::to_object_array(Obj->get("notes").toString()); + for(auto const &i:NIV) { + SecurityObjects::NoteInfo ii{.created=(uint64_t)std::time(nullptr), .createdBy=UInfo.email, .note=i.note}; + Notes.push_back(ii); + } + } + return true; + } catch(...) { + + } + return false; + } + + bool MergeNotes(const NoteInfoVec & NewNotes, const UserInfo &UInfo, NoteInfoVec & ExistingNotes) { + for(auto const &i:NewNotes) { + SecurityObjects::NoteInfo ii{.created=(uint64_t)std::time(nullptr), .createdBy=UInfo.email, .note=i.note}; + ExistingNotes.push_back(ii); + } + return true; + } + + 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 ); + return true; + } 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); + return true; + } 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); + return true; + } catch(...) { + + } + return false; + } + + void ActionLink::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"id",id); + field_to_json(Obj,"action",action); + field_to_json(Obj,"userId",userId); + field_to_json(Obj,"actionTemplate",actionTemplate); + field_to_json(Obj,"variables",variables); + field_to_json(Obj,"locale",locale); + field_to_json(Obj,"message",message); + field_to_json(Obj,"sent",sent); + field_to_json(Obj,"created",created); + field_to_json(Obj,"expires",expires); + field_to_json(Obj,"completed",completed); + field_to_json(Obj,"canceled",canceled); + } + + bool ActionLink::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"id",id); + field_from_json(Obj,"action",action); + field_from_json(Obj,"userId",userId); + field_from_json(Obj,"actionTemplate",actionTemplate); + field_from_json(Obj,"variables",variables); + field_from_json(Obj,"locale",locale); + field_from_json(Obj,"message",message); + field_from_json(Obj,"sent",sent); + field_from_json(Obj,"created",created); + field_from_json(Obj,"expires",expires); + field_from_json(Obj,"completed",completed); + field_from_json(Obj,"canceled",canceled); + return true; + } catch(...) { + + } + return false; + } + + void Preferences::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"id",id); + field_to_json(Obj,"modified",modified); + field_to_json(Obj,"data",data); + } + + bool Preferences::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"id",id); + field_from_json(Obj,"modified",modified); + field_from_json(Obj,"data",data); + return true; + } catch(...) { + + } + return false; + } + + void SubMfaConfig::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"id",id); + field_to_json(Obj,"type",type); + field_to_json(Obj,"sms",sms); + field_to_json(Obj,"email",email); + } + + bool SubMfaConfig::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"id",id); + field_from_json(Obj,"type",type); + field_from_json(Obj,"sms",sms); + field_from_json(Obj,"email",email); + return true; + } catch(...) { + + } + return false; + } + + void Token::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj,"token",token); + field_to_json(Obj,"refreshToken",refreshToken); + field_to_json(Obj,"tokenType",tokenType); + field_to_json(Obj,"userName",userName); + field_to_json(Obj,"created",created); + field_to_json(Obj,"expires",expires); + field_to_json(Obj,"idleTimeout",idleTimeout); + field_to_json(Obj,"revocationDate",revocationDate); + } + + bool Token::from_json(Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj,"token",token); + field_from_json(Obj,"refreshToken",refreshToken); + field_from_json(Obj,"tokenType",tokenType); + field_from_json(Obj,"userName",userName); + field_from_json(Obj,"created",created); + field_from_json(Obj,"expires",expires); + field_from_json(Obj,"idleTimeout",idleTimeout); + field_from_json(Obj,"revocationDate",revocationDate); + return true; + } catch(...) { + + } + return false; + } + +} + diff --git a/src/RESTObjects/RESTAPI_SecurityObjects.h b/src/RESTObjects/RESTAPI_SecurityObjects.h new file mode 100644 index 0000000..15a9b4b --- /dev/null +++ b/src/RESTObjects/RESTAPI_SecurityObjects.h @@ -0,0 +1,296 @@ +// +// 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. +// + +#pragma once + +#include +#include "framework/OpenWifiTypes.h" +#include "Poco/JSON/Object.h" +#include "Poco/Data/LOB.h" +#include "Poco/Data/LOBStream.h" + +namespace OpenWifi { + namespace SecurityObjects { + + typedef std::string USER_ID_TYPE; + + 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, INSTALLER, NOC, ACCOUNTING, PARTNER + }; + + 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 MobilePhoneNumber { + std::string number; + bool verified = false; + bool primary = false; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + struct MfaAuthInfo { + bool enabled = false; + std::string method; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + struct UserLoginLoginExtensions { + std::vector mobiles; + struct MfaAuthInfo mfa; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + struct MFAChallengeRequest { + std::string uuid; + std::string question; + std::string method; + uint64_t created = std::time(nullptr); + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + struct MFAChallengeResponse { + std::string uuid; + std::string answer; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + 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; + UserLoginLoginExtensions userTypeProprietaryInfo; + std::string securityPolicy; + uint64_t securityPolicyChange = 0 ; + std::string currentPassword; + OpenWifi::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; + + // bool append_from_json(Poco::JSON::Object::Ptr Obj, const UserInfo &UInfo, NoteInfoVec & Notes); + bool MergeNotes(Poco::JSON::Object::Ptr Obj, const UserInfo &UInfo, NoteInfoVec & Notes); + bool MergeNotes(const NoteInfoVec & NewNotes, const UserInfo &UInfo, NoteInfoVec & ExistingNotes); + + 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{"OpenWiFi"}; + std::string uri; + std::string authenticationType{"internal_v1"}; + 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=0; + 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); + }; + + enum LinkActions { + FORGOT_PASSWORD=1, + VERIFY_EMAIL, + SUB_FORGOT_PASSWORD, + SUB_VERIFY_EMAIL + }; + + struct ActionLink { + std::string id; + uint64_t action; + std::string userId; + std::string actionTemplate; + Types::StringPairVec variables; + std::string locale; + std::string message; + uint64_t sent=0; + uint64_t created=std::time(nullptr); + uint64_t expires=0; + uint64_t completed=0; + uint64_t canceled=0; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + struct Preferences { + std::string id; + uint64_t modified; + Types::StringPairVec data; + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + struct SubMfaConfig { + std::string id; + std::string type; + std::string sms; + std::string email; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + struct Token { + std::string token; + std::string refreshToken; + std::string tokenType; + std::string userName; + uint64_t created=0; + uint64_t expires=0; + uint64_t idleTimeout=0; + uint64_t revocationDate=0; + + void to_json(Poco::JSON::Object &Obj) const; + bool from_json(Poco::JSON::Object::Ptr &Obj); + }; + + struct Avatar { + std::string id; + std::string type; + uint64_t created=0; + std::string name; + Poco::Data::LOB avatar; + }; + + } +} diff --git a/src/StorageService.cpp b/src/StorageService.cpp new file mode 100644 index 0000000..3b7b0f7 --- /dev/null +++ b/src/StorageService.cpp @@ -0,0 +1,44 @@ +// +// 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 "RESTObjects/RESTAPI_ProvObjects.h" + +namespace OpenWifi { + + int Storage::Start() { + std::lock_guard Guard(Mutex_); + + StorageClass::Start(); + Updater_.start(*this); + return 0; + } + + void Storage::run() { + Running_ = true ; + bool FirstRun=true; + long Retry = 2000; + while(Running_) { + if(!FirstRun) + Poco::Thread::trySleep(Retry); + if(!Running_) + break; + FirstRun = false; + Retry = 2000; + } + } + + void Storage::Stop() { + Running_=false; + Updater_.wakeUp(); + Updater_.join(); + 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..a20fe9d --- /dev/null +++ b/src/StorageService.h @@ -0,0 +1,33 @@ +// +// 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. +// + +#pragma once + +#include "framework/MicroService.h" +#include "framework/StorageClass.h" + +namespace OpenWifi { + class Storage : public StorageClass, Poco::Runnable { + public: + static auto instance() { + static auto instance_ = new Storage; + return instance_; + } + + int Start() override; + void Stop() override; + + void run() final; + + private: + Poco::Thread Updater_; + std::atomic_bool Running_=false; + }; + inline auto StorageService() { return Storage::instance(); } +} // namespace + diff --git a/src/framework/API_Proxy.h b/src/framework/API_Proxy.h new file mode 100644 index 0000000..82fd2e6 --- /dev/null +++ b/src/framework/API_Proxy.h @@ -0,0 +1,93 @@ +// +// Created by stephane bourque on 2021-11-30. +// + +#pragma once + +#include "framework/MicroService.h" +#include "Poco/JSON/Parser.h" + +namespace OpenWifi { + inline void API_Proxy( Poco::Logger &Logger, + Poco::Net::HTTPServerRequest *Request, + Poco::Net::HTTPServerResponse *Response, + const char * ServiceType, + const char * PathRewrite, + uint64_t msTimeout_ = 10000 ) { + try { + auto Services = MicroService::instance().GetServices(ServiceType); + for(auto const &Svc:Services) { + Poco::URI SourceURI(Request->getURI()); + Poco::URI DestinationURI(Svc.PrivateEndPoint); + DestinationURI.setPath(PathRewrite); + DestinationURI.setQuery(SourceURI.getQuery()); + + // std::cout << " Source: " << SourceURI.toString() << std::endl; + // std::cout << "Destination: " << DestinationURI.toString() << std::endl; + + Poco::Net::HTTPSClientSession Session(DestinationURI.getHost(), DestinationURI.getPort()); + Session.setKeepAlive(true); + Session.setTimeout(Poco::Timespan(msTimeout_/1000, msTimeout_ % 1000)); + Poco::Net::HTTPRequest ProxyRequest(Request->getMethod(), + DestinationURI.getPathAndQuery(), + Poco::Net::HTTPMessage::HTTP_1_1); + if(Request->has("Authorization")) { + ProxyRequest.add("Authorization", Request->get("Authorization")); + } else { + ProxyRequest.add("X-API-KEY", Svc.AccessKey); + ProxyRequest.add("X-INTERNAL-NAME", MicroService::instance().PublicEndPoint()); + } + + if(Request->getMethod() == Poco::Net::HTTPRequest::HTTP_DELETE) { + Session.sendRequest(ProxyRequest); + Poco::Net::HTTPResponse ProxyResponse; + Session.receiveResponse(ProxyResponse); + Response->setStatus(ProxyResponse.getStatus()); + Response->send(); + return; + } else { + Poco::JSON::Parser P; + std::stringstream SS; + try { + auto Body = P.parse(Request->stream()).extract(); + Poco::JSON::Stringifier::condense(Body,SS); + SS << "\r\n\r\n"; + } catch(const Poco::Exception &E) { + Logger.log(E); + } + + if(SS.str().empty()) { + Session.sendRequest(ProxyRequest); + } else { + ProxyRequest.setContentType("application/json"); + ProxyRequest.setContentLength(SS.str().size()); + std::ostream & os = Session.sendRequest(ProxyRequest); + os << SS.str() ; + } + + Poco::Net::HTTPResponse ProxyResponse; + std::stringstream SSR; + try { + std::istream &ProxyResponseStream = Session.receiveResponse(ProxyResponse); + Poco::JSON::Parser P2; + auto ProxyResponseBody = P2.parse(ProxyResponseStream).extract(); + Poco::JSON::Stringifier::condense(ProxyResponseBody,SSR); + Response->setContentType("application/json"); + Response->setContentLength(SSR.str().size()); + Response->setStatus(ProxyResponse.getStatus()); + Response->sendBuffer(SSR.str().c_str(),SSR.str().size()); + return; + } catch( const Poco::Exception & E) { + + } + Response->setStatus(ProxyResponse.getStatus()); + Response->send(); + return; + } + } + + } catch (const Poco::Exception &E) { + Logger.log(E); + } + } +} \ No newline at end of file diff --git a/src/framework/ConfigurationValidator.cpp b/src/framework/ConfigurationValidator.cpp new file mode 100644 index 0000000..a7f1d7e --- /dev/null +++ b/src/framework/ConfigurationValidator.cpp @@ -0,0 +1,2479 @@ +// +// Created by stephane bourque on 2021-09-14. +// + +#include +#include +#include + +#include "framework/MicroService.h" +#include "ConfigurationValidator.h" +#include "framework/CountryCodes.h" +#include "Poco/StringTokenizer.h" + +namespace OpenWifi { + + static const std::string GitUCentralJSONSchemaFile{"https://raw.githubusercontent.com/blogic/ucentral-schema/main/ucentral.schema.json"}; + + static json DefaultUCentralSchema = R"( +{ + "$id": "https://openwrt.org/ucentral.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "uuid": { + "type": "integer" + }, + "unit": { + "$ref": "#/$defs/unit" + }, + "globals": { + "$ref": "#/$defs/globals" + }, + "definitions": { + "$ref": "#/$defs/definitions" + }, + "ethernet": { + "type": "array", + "items": { + "$ref": "#/$defs/ethernet" + } + }, + "switch": { + "$ref": "#/$defs/switch" + }, + "radios": { + "type": "array", + "items": { + "$ref": "#/$defs/radio" + } + }, + "interfaces": { + "type": "array", + "items": { + "$ref": "#/$defs/interface" + } + }, + "services": { + "$ref": "#/$defs/service" + }, + "metrics": { + "$ref": "#/$defs/metrics" + }, + "config-raw": { + "$ref": "#/$defs/config-raw" + }, + "third-party": { + "type": "object", + "additionalProperties": true + } + }, + "$defs": { + "unit": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "timezone": { + "type": "string", + "examples": [ + "UTC", + "EST5", + "CET-1CEST,M3.5.0,M10.5.0/3" + ] + }, + "leds-active": { + "type": "boolean", + "default": true + }, + "random-password": { + "type": "boolean", + "default": false + } + } + }, + "globals.wireless-multimedia.class-selector": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "CS1", + "CS2", + "CS3", + "CS4", + "CS5", + "CS6", + "AF11", + "AF12", + "AF13", + "AF21", + "AF22", + "AF23", + "AF31", + "AF32", + "AF33", + "AF41", + "AF42", + "AF43", + "DF", + "EF" + ] + } + }, + "globals.wireless-multimedia": { + "type": "object", + "properties": { + "UP0": { + "$ref": "#/$defs/globals.wireless-multimedia.class-selector" + }, + "UP1": { + "$ref": "#/$defs/globals.wireless-multimedia.class-selector" + }, + "UP2": { + "$ref": "#/$defs/globals.wireless-multimedia.class-selector" + }, + "UP3": { + "$ref": "#/$defs/globals.wireless-multimedia.class-selector" + }, + "UP4": { + "$ref": "#/$defs/globals.wireless-multimedia.class-selector" + }, + "UP5": { + "$ref": "#/$defs/globals.wireless-multimedia.class-selector" + }, + "UP6": { + "$ref": "#/$defs/globals.wireless-multimedia.class-selector" + }, + "UP7": { + "$ref": "#/$defs/globals.wireless-multimedia.class-selector" + } + } + }, + "globals.wireless-multimedia-profile": { + "type": "string", + "enum": [ + "enterprise" + ] + }, + "globals": { + "type": "object", + "properties": { + "ipv4-network": { + "type": "string", + "format": "uc-cidr4", + "examples": [ + "192.168.0.0/16" + ] + }, + "ipv6-network": { + "type": "string", + "format": "uc-cidr6", + "examples": [ + "fdca:1234:4567::/48" + ] + }, + "wireless-multimedia": { + "oneOf": [ + { + "$ref": "#/$defs/globals.wireless-multimedia" + }, + { + "$ref": "#/$defs/globals.wireless-multimedia-profile" + } + ] + } + } + }, + "definitions": { + "type": "object", + "properties": { + "wireless-encryption": { + "type": "object", + "patternProperties": { + ".+": { + "$ref": "#/$defs/interface.ssid.encryption", + "additionalProperties": false + } + } + } + } + }, + "ethernet": { + "type": "object", + "properties": { + "select-ports": { + "type": "array", + "items": { + "type": "string", + "examples": [ + "LAN1", + "LAN2", + "LAN3", + "LAN4", + "LAN*", + "WAN*", + "*" + ] + } + }, + "speed": { + "type": "integer", + "enum": [ + 10, + 100, + 1000, + 2500, + 5000, + 10000 + ] + }, + "duplex": { + "type": "string", + "enum": [ + "half", + "full" + ] + }, + "services": { + "type": "array", + "items": { + "type": "string", + "examples": [ + "quality-of-service" + ] + } + } + } + }, + "switch": { + "type": "object", + "properties": { + "port-mirror": { + "type": "object", + "properties": { + "monitor-ports": { + "type": "array", + "items": { + "type": "string" + } + }, + "analysis-port": { + "type": "string" + } + } + }, + "loop-detection": { + "type": "object", + "properties": { + "protocol": { + "type": "string", + "enum": [ + "rstp" + ], + "default": "rstp" + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "upstream", + "downstream" + ] + } + } + } + } + } + }, + "radio.rates": { + "type": "object", + "properties": { + "beacon": { + "type": "integer", + "default": 6000, + "enum": [ + 0, + 1000, + 2000, + 5500, + 6000, + 9000, + 11000, + 12000, + 18000, + 24000, + 36000, + 48000, + 54000 + ] + }, + "multicast": { + "type": "integer", + "default": 24000, + "enum": [ + 0, + 1000, + 2000, + 5500, + 6000, + 9000, + 11000, + 12000, + 18000, + 24000, + 36000, + 48000, + 54000 + ] + } + } + }, + "radio.he": { + "type": "object", + "properties": { + "multiple-bssid": { + "type": "boolean", + "default": false + }, + "ema": { + "type": "boolean", + "default": false + }, + "bss-color": { + "type": "integer", + "default": 64 + } + } + }, + "radio": { + "type": "object", + "properties": { + "band": { + "type": "string", + "enum": [ + "2G", + "5G", + "5G-lower", + "5G-upper", + "6G" + ] + }, + "bandwidth": { + "type": "integer", + "enum": [ + 5, + 10, + 20 + ] + }, + "channel": { + "oneOf": [ + { + "type": "integer", + "maximum": 171, + "minimum": 1 + }, + { + "type": "string", + "const": "auto" + } + ] + }, + "country": { + "type": "string", + "maxLength": 2, + "minLength": 2, + "examples": [ + "US" + ] + }, + "channel-mode": { + "type": "string", + "enum": [ + "HT", + "VHT", + "HE" + ], + "default": "HE" + }, + "channel-width": { + "type": "integer", + "enum": [ + 20, + 40, + 80, + 160, + 8080 + ], + "default": 80 + }, + "require-mode": { + "type": "string", + "enum": [ + "HT", + "VHT", + "HE" + ] + }, + "mimo": { + "type": "string", + "enum": [ + "1x1", + "2x2", + "3x3", + "4x4", + "5x5", + "6x6", + "7x7", + "8x8" + ] + }, + "tx-power": { + "type": "integer", + "maximum": 30, + "minimum": 0 + }, + "legacy-rates": { + "type": "boolean", + "default": false + }, + "beacon-interval": { + "type": "integer", + "default": 100, + "maximum": 65535, + "minimum": 15 + }, + "dtim-period": { + "type": "integer", + "default": 2, + "maximum": 255, + "minimum": 1 + }, + "maximum-clients": { + "type": "integer", + "example": 64 + }, + "rates": { + "$ref": "#/$defs/radio.rates" + }, + "he-settings": { + "$ref": "#/$defs/radio.he" + }, + "hostapd-iface-raw": { + "type": "array", + "items": { + "type": "string", + "examples": [ + "ap_table_expiration_time=3600", + "device_type=6-0050F204-1", + "ieee80211h=1", + "rssi_ignore_probe_request=-75", + "time_zone=EST5", + "uuid=12345678-9abc-def0-1234-56789abcdef0", + "venue_url=1:http://www.example.com/info-eng", + "wpa_deny_ptk0_rekey=0" + ] + } + } + } + }, + "interface.vlan": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "maximum": 4050 + }, + "proto": { + "decription": "The L2 vlan tag that shall be added (1q,1ad) ", + "type": "string", + "enum": [ + "802.1ad", + "802.1q" + ], + "default": "802.1q" +} +} +}, +"interface.bridge": { + "type": "object", + "properties": { + "mtu": { + "type": "integer", + "maximum": 65535, + "minimum": 256, + "examples": [ + 1500 + ] + }, + "tx-queue-len": { + "type": "integer", + "examples": [ + 5000 + ] + }, + "isolate-ports": { + "type": "boolean", + "default": false + } + } + }, + "interface.ethernet": { + "type": "object", + "properties": { + "select-ports": { + "type": "array", + "items": { + "type": "string", + "examples": [ + "LAN1", + "LAN2", + "LAN3", + "LAN4", + "LAN*", + "WAN*", + "*" + ] + } + }, + "multicast": { + "type": "boolean", + "default": true + }, + "learning": { + "type": "boolean", + "default": true + }, + "isolate": { + "type": "boolean", + "default": false + }, + "macaddr": { + "type": "string", + "format": "uc-mac" + }, + "reverse-path-filter": { + "type": "boolean", + "default": false + }, + "vlan-tag": { + "type": "string", + "enum": [ + "tagged", + "un-tagged", + "auto" + ], + "default": "auto" + } + } + }, + "interface.ipv4.dhcp": { + "type": "object", + "properties": { + "lease-first": { + "type": "integer", + "examples": [ + 10 + ] + }, + "lease-count": { + "type": "integer", + "examples": [ + 100 + ] + }, + "lease-time": { + "type": "string", + "format": "uc-timeout", + "default": "6h" + }, + "relay-server": { + "type": "string", + "format": "ipv4", + "example": "192.168.2.1" + }, + "circuit-id-format": { + "type": "string", + "example": [ + "\\{Interface\\}:\\{VLAN-Id\\}:\\{SSID\\}:\\{Model\\}:\\{Name\\}:\\{AP-MAC\\}:\\{Location\\}", + "\\{AP-MAC\\};\\{SSID\\};\\{Crypto\\}", + "\\{Name\\} \\{ESSID\\}" + ] + }, + "remote-id-format": { + "type": "string", + "example": [ + "\\{Client-MAC-hex\\} \\{SSID\\}", + "\\{AP-MAC-hex\\} \\{SSID\\}" + ] + } + } + }, + "interface.ipv4.dhcp-lease": { + "type": "object", + "properties": { + "macaddr": { + "type": "string", + "format": "uc-mac", + "examples": [ + "00:11:22:33:44:55" + ] + }, + "static-lease-offset": { + "type": "integer", + "examples": [ + 10 + ] + }, + "lease-time": { + "type": "string", + "format": "uc-timeout", + "default": "6h" + }, + "publish-hostname": { + "type": "boolean", + "default": true + } + } + }, + "interface.ipv4": { + "type": "object", + "properties": { + "addressing": { + "type": "string", + "enum": [ + "dynamic", + "static" + ], + "examples": [ + "static" + ] + }, + "subnet": { + "type": "string", + "format": "uc-cidr4", + "examples": [ + "auto/24" + ] + }, + "gateway": { + "type": "string", + "format": "ipv4", + "examples": [ + "192.168.1.1" + ] + }, + "send-hostname": { + "type": "boolean", + "default": true, + "examples": [ + true + ] + }, + "use-dns": { + "type": "array", + "items": { + "type": "string", + "format": "ipv4", + "examples": [ + "8.8.8.8", + "4.4.4.4" + ] + } + }, + "dhcp": { + "$ref": "#/$defs/interface.ipv4.dhcp" + }, + "dhcp-leases": { + "type": "array", + "items": { + "$ref": "#/$defs/interface.ipv4.dhcp-lease" + } + } + } + }, + "interface.ipv6.dhcpv6": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "hybrid", + "stateless", + "stateful", + "relay" + ] + }, + "announce-dns": { + "type": "array", + "items": { + "type": "string", + "format": "ipv6" + } + }, + "filter-prefix": { + "type": "string", + "format": "uc-cidr6", + "default": "::/0" + } + } + }, + "interface.ipv6": { + "type": "object", + "properties": { + "addressing": { + "type": "string", + "enum": [ + "dynamic", + "static" + ] + }, + "subnet": { + "type": "string", + "format": "uc-cidr6", + "examples": [ + "auto/64" + ] + }, + "gateway": { + "type": "string", + "format": "ipv6", + "examples": [ + "2001:db8:123:456::1" + ] + }, + "prefix-size": { + "type": "integer", + "maximum": 64, + "minimum": 0 + }, + "dhcpv6": { + "$ref": "#/$defs/interface.ipv6.dhcpv6" + } + } + }, + "interface.broad-band.wwan": { + "type": "object", + "properties": { + "protocol": { + "type": "string", + "const": "wwan" + }, + "modem-type": { + "type": "string", + "enum": [ + "qmi", + "mbim", + "wwan" + ] + }, + "access-point-name": { + "type": "string" + }, + "authentication-type": { + "type": "string", + "enum": [ + "none", + "pap", + "chap", + "pap-chap" + ], + "default": "none" + }, + "pin-code": { + "type": "string" + }, + "user-name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "packet-data-protocol": { + "type": "string", + "enum": [ + "ipv4", + "ipv6", + "dual-stack" + ], + "default": "dual-stack" + } + } + }, + "interface.broad-band.pppoe": { + "type": "object", + "properties": { + "protocol": { + "type": "string", + "const": "pppoe" + }, + "user-name": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "interface.broad-band": { + "oneOf": [ + { + "$ref": "#/$defs/interface.broad-band.wwan" + }, + { + "$ref": "#/$defs/interface.broad-band.pppoe" + } + ] + }, + "interface.captive": { + "type": "object", + "properties": { + "gateway-name": { + "type": "string", + "default": "uCentral - Captive Portal" + }, + "gateway-fqdn": { + "type": "string", + "format": "fqdn", + "default": "ucentral.splash" + }, + "max-clients": { + "type": "integer", + "default": 32 + }, + "upload-rate": { + "type": "integer", + "default": 0 + }, + "download-rate": { + "type": "integer", + "default": 0 + }, + "upload-quota": { + "type": "integer", + "default": 0 + }, + "download-quota": { + "type": "integer", + "default": 0 + } + } + }, + "interface.ssid.encryption": { + "type": "object", + "properties": { + "proto": { + "type": "string", + "enum": [ + "none", + "psk", + "psk2", + "psk-mixed", + "wpa", + "wpa2", + "wpa-mixed", + "sae", + "sae-mixed", + "wpa3", + "wpa3-192", + "wpa3-mixed" + ], + "examples": [ + "psk2" + ] + }, + "key": { + "type": "string", + "maxLength": 63, + "minLength": 8 + }, + "ieee80211w": { + "type": "string", + "enum": [ + "disabled", + "optional", + "required" + ], + "default": "disabled" + } + } + }, + "interface.ssid.multi-psk": { + "type": "object", + "properties": { + "mac": { + "type": "string", + "format": "uc-mac" + }, + "key": { + "type": "string", + "maxLength": 63, + "minLength": 8 + }, + "vlan-id": { + "type": "integer", + "maximum": 4096, + "examples": [ + 3, + 100, + 200, + 4094 + ] + } + } + }, + "interface.ssid.rrm": { + "type": "object", + "properties": { + "neighbor-reporting": { + "type": "boolean", + "default": false + }, + "lci": { + "type": "string" + }, + "civic-location": { + "type": "string" + }, + "ftm-responder": { + "type": "boolean", + "default": false + }, + "stationary-ap": { + "type": "boolean", + "default": false + } + } + }, + "interface.ssid.rate-limit": { + "type": "object", + "properties": { + "ingress-rate": { + "type": "integer", + "default": 0 + }, + "egress-rate": { + "type": "integer", + "default": 0 + } + } + }, + "interface.ssid.roaming": { + "type": "object", + "properties": { + "message-exchange": { + "type": "string", + "enum": [ + "air", + "ds" + ], + "default": "ds" + }, + "generate-psk": { + "type": "boolean", + "default": false + }, + "domain-identifier": { + "type": "string", + "maxLength": 4, + "minLength": 4, + "examples": [ + "abcd" + ] + }, + "pmk-r0-key-holder": { + "type": "string", + "example": "14:DD:20:47:14:E4,14DD204714E4,00112233445566778899aabbccddeeff" + }, + "pmk-r1-key-holder": { + "type": "string", + "example": "14:DD:20:47:14:E4,14DD204714E4,00112233445566778899aabbccddeeff" + } + } + }, + "interface.ssid.radius.local-user": { + "type": "object", + "properties": { + "mac": { + "type": "string", + "format": "uc-mac" + }, + "user-name": { + "type": "string", + "minLength": 1 + }, + "password": { + "type": "string", + "maxLength": 63, + "minLength": 8 + }, + "vlan-id": { + "type": "integer", + "maximum": 4096, + "examples": [ + 3, + 100, + 200, + 4094 + ] + } + } + }, + "interface.ssid.radius.local": { + "type": "object", + "properties": { + "server-identity": { + "type": "string", + "default": "uCentral" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/interface.ssid.radius.local-user" + } + } + } + }, + "interface.ssid.radius.server": { + "type": "object", + "properties": { + "host": { + "type": "string", + "format": "uc-host", + "examples": [ + "192.168.1.10" + ] + }, + "port": { + "type": "integer", + "maximum": 65535, + "minimum": 1024, + "examples": [ + 1812 + ] + }, + "secret": { + "type": "string", + "examples": [ + "secret" + ] + }, + "request-attribute": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "maximum": 255, + "minimum": 1 + }, + "value": { + "anyOf": [ + { + "type": "integer", + "maximum": 4294967295, + "minimum": 0 + }, + { + "type": "string" + } + ] + } + }, + "examples": [ + { + "id": 27, + "value": 900 + }, + { + "id": 32, + "value": "My NAS ID" + }, + { + "id": 56, + "value": 1004 + }, + { + "id": 126, + "value": "Example Operator" + } + ] + }, + "examples": [ + "126:s:Operator" + ] + } + } + }, + "interface.ssid.radius": { + "type": "object", + "properties": { + "nas-identifier": { + "type": "string" + }, + "chargeable-user-id": { + "type": "boolean", + "default": false + }, + "local": { + "$ref": "#/$defs/interface.ssid.radius.local" + }, + "authentication": { + "$ref": "#/$defs/interface.ssid.radius.server" + }, + "accounting": { + "allOf": [ + { + "$ref": "#/$defs/interface.ssid.radius.server" + }, + { + "type": "object", + "properties": { + "interval": { + "type": "integer", + "maximum": 600, + "minimum": 60, + "default": 60 + } + } + } + ] + } + } + }, + "interface.ssid.certificates": { + "type": "object", + "properties": { + "use-local-certificates": { + "type": "boolean", + "default": false + }, + "ca-certificate": { + "type": "string" + }, + "certificate": { + "type": "string" + }, + "private-key": { + "type": "string" + }, + "private-key-password": { + "type": "string" + } + } + }, + "interface.ssid.pass-point": { + "type": "object", + "properties": { + "venue-name": { + "type": "array", + "items": { + "type": "string" + } + }, + "venue-group": { + "type": "integer", + "maximum": 32 + }, + "venue-type": { + "type": "integer", + "maximum": 32 + }, + "venue-url": { + "type": "array", + "items": { + "type": "string", + "format": "uri" + } + }, + "auth-type": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "terms-and-conditions", + "online-enrollment", + "http-redirection", + "dns-redirection" + ] + }, + "uri": { + "type": "string", + "format": "uri", + "examples": [ + "https://operator.example.org/wireless-access/terms-and-conditions.html", + "http://www.example.com/redirect/me/here/" + ] + } + }, + "minLength": 2, + "maxLength": 2 + }, + "domain-name": { + "type": "array", + "items": { + "type": "string", + "format": "hostname" + } + }, + "nai-realm": { + "type": "array", + "items": { + "type": "string" + } + }, + "osen": { + "type": "boolean" + }, + "anqp-domain": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + }, + "anqp-3gpp-cell-net": { + "type": "array", + "items": { + "type": "string" + } + }, + "friendly-name": { + "type": "array", + "items": { + "type": "string" + } + }, + "access-network-type": { + "type": "integer", + "maximum": 15, + "default": 0 + }, + "internet": { + "type": "boolean", + "default": true + }, + "asra": { + "type": "boolean", + "default": false + }, + "esr": { + "type": "boolean", + "default": false + }, + "uesa": { + "type": "boolean", + "default": false + }, + "hessid": { + "type": "string", + "example": "00:11:22:33:44:55" + }, + "roaming-consortium": { + "type": "array", + "items": { + "type": "string" + } + }, + "disable-dgaf": { + "type": "boolean", + "default": false + }, + "ipaddr-type-available": { + "type": "integer", + "maximum": 255 + }, + "connection-capability": { + "type": "array", + "items": { + "type": "string" + } + }, + "icons": { + "type": "array", + "items": { + "type": "object", + "properties": { + "width": { + "type": "integer", + "examples": [ + 64 + ] + }, + "height": { + "type": "integer", + "examples": [ + 64 + ] + }, + "type": { + "type": "string", + "examples": [ + "image/png" + ] + }, + "icon": { + "type": "string", + "format": "uc-base64" + }, + "language": { + "type": "string", + "pattern": "^[a-z][a-z][a-z]$", + "examples": [ + "eng", + "fre", + "ger", + "ita" + ] + } + }, + "examples": [ + { + "width": 32, + "height": 32, + "type": "image/png", + "language": "eng", + "icon": "R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" + } + ] + } + }, + "wan-metrics": { + "type": "object", + "properties": { + "info": { + "type": "string", + "enum": [ + "up", + "down", + "testing" + ] + }, + "downlink": { + "type": "integer" + }, + "uplink": { + "type": "integer" + } + } + } + } + }, + "interface.ssid.quality-thresholds": { + "type": "object", + "properties": { + "probe-request-rssi": { + "type": "integer" + }, + "association-request-rssi": { + "type": "integer" + } + } + }, + "interface.ssid": { + "type": "object", + "properties": { + "purpose": { + "type": "string", + "enum": [ + "user-defined", + "onboarding-ap", + "onboarding-sta" + ], + "default": "user-defined" + }, + "name": { + "type": "string", + "maxLength": 32, + "minLength": 1 + }, + "wifi-bands": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "2G", + "5G", + "5G-lower", + "5G-upper", + "6G" + ] + } + }, + "bss-mode": { + "type": "string", + "enum": [ + "ap", + "sta", + "mesh", + "wds-ap", + "wds-sta", + "wds-repeater" + ], + "default": "ap" + }, + "bssid": { + "type": "string", + "format": "uc-mac" + }, + "hidden-ssid": { + "type": "boolean" + }, + "isolate-clients": { + "type": "boolean" + }, + "power-save": { + "type": "boolean" + }, + "rts-threshold": { + "type": "integer", + "maximum": 65535, + "minimum": 1 + }, + "broadcast-time": { + "type": "boolean" + }, + "unicast-conversion": { + "type": "boolean" + }, + "services": { + "type": "array", + "items": { + "type": "string", + "examples": [ + "wifi-steering" + ] + } + }, + "maximum-clients": { + "type": "integer", + "example": 64 + }, + "proxy-arp": { + "type": "boolean", + "default": true + }, + "disassoc-low-ack": { + "decription": "Disassociate stations based on excessive transmission failures or other indications of connection loss.", + "type": "boolean", + "default": false + }, + "vendor-elements": { + "decription": "This option allows embedding custom vendor specific IEs inside the beacons of a BSS in AP mode.", + "type": "string" + }, + "encryption": { + "$ref": "#/$defs/interface.ssid.encryption" + }, + "multi-psk": { + "type": "array", + "items": { + "$ref": "#/$defs/interface.ssid.multi-psk" + } + }, + "rrm": { + "$ref": "#/$defs/interface.ssid.rrm" + }, + "rate-limit": { + "$ref": "#/$defs/interface.ssid.rate-limit" + }, + "roaming": { + "$ref": "#/$defs/interface.ssid.roaming" + }, + "radius": { + "$ref": "#/$defs/interface.ssid.radius" + }, + "certificates": { + "$ref": "#/$defs/interface.ssid.certificates" + }, + "pass-point": { + "$ref": "#/$defs/interface.ssid.pass-point" + }, + "quality-thresholds": { + "$ref": "#/$defs/interface.ssid.quality-thresholds" + }, + "hostapd-bss-raw": { + "type": "array", + "items": { + "type": "string", + "examples": [ + "ap_table_expiration_time=3600", + "device_type=6-0050F204-1", + "ieee80211h=1", + "rssi_ignore_probe_request=-75", + "time_zone=EST5", + "uuid=12345678-9abc-def0-1234-56789abcdef0", + "venue_url=1:http://www.example.com/info-eng", + "wpa_deny_ptk0_rekey=0" + ] + } + } + } + }, + "interface.tunnel.mesh": { + "type": "object", + "properties": { + "proto": { + "type": "string", + "const": "mesh" + } + } + }, + "interface.tunnel.vxlan": { + "type": "object", + "properties": { + "proto": { + "type": "string", + "const": "vxlan" + }, + "peer-address": { + "type": "string", + "format": "ipv4", + "example": "192.168.100.1" + }, + "peer-port": { + "type": "integer", + "maximum": 65535, + "minimum": 1, + "examples": [ + 4789 + ] + } + } + }, + "interface.tunnel.l2tp": { + "type": "object", + "properties": { + "proto": { + "type": "string", + "const": "l2tp" + }, + "server": { + "type": "string", + "format": "ipv4", + "example": "192.168.100.1" + }, + "user-name": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "interface.tunnel.gre": { + "type": "object", + "properties": { + "proto": { + "type": "string", + "const": "gre" + }, + "peer-address": { + "type": "string", + "format": "ipv4", + "example": "192.168.100.1" + } + } + }, + "interface.tunnel": { + "oneOf": [ + { + "$ref": "#/$defs/interface.tunnel.mesh" + }, + { + "$ref": "#/$defs/interface.tunnel.vxlan" + }, + { + "$ref": "#/$defs/interface.tunnel.l2tp" + }, + { + "$ref": "#/$defs/interface.tunnel.gre" + } + ] + }, + "interface": { + "type": "object", + "properties": { + "name": { + "type": "string", + "examples": [ + "LAN" + ] + }, + "role": { + "type": "string", + "enum": [ + "upstream", + "downstream" + ] + }, + "isolate-hosts": { + "type": "boolean" + }, + "metric": { + "type": "integer", + "maximum": 4294967295, + "minimum": 0 + }, + "services": { + "type": "array", + "items": { + "type": "string", + "examples": [ + "ssh", + "lldp" + ] + } + }, + "vlan": { + "$ref": "#/$defs/interface.vlan" + }, + "bridge": { + "$ref": "#/$defs/interface.bridge" + }, + "ethernet": { + "type": "array", + "items": { + "$ref": "#/$defs/interface.ethernet" + } + }, + "ipv4": { + "$ref": "#/$defs/interface.ipv4" + }, + "ipv6": { + "$ref": "#/$defs/interface.ipv6" + }, + "broad-band": { + "$ref": "#/$defs/interface.broad-band" + }, + "captive": { + "$ref": "#/$defs/interface.captive" + }, + "ssids": { + "type": "array", + "items": { + "$ref": "#/$defs/interface.ssid" + } + }, + "tunnel": { + "$ref": "#/$defs/interface.tunnel" + } + } + }, + "service.lldp": { + "type": "object", + "properties": { + "describe": { + "type": "string", + "default": "uCentral Access Point" + }, + "location": { + "type": "string", + "default": "uCentral Network" + } + } + }, + "service.ssh": { + "type": "object", + "properties": { + "port": { + "type": "integer", + "maximum": 65535, + "default": 22 + }, + "authorized-keys": { + "type": "array", + "items": { + "type": "string", + "examples": [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC0ghdSd2D2y08TFowZLMZn3x1/Djw3BkNsIeHt/Z+RaXwvfV1NQAnNdaOngMT/3uf5jZtYxhpl+dbZtRhoUPRvKflKBeFHYBqjZVzD3r4ns2Ofm2UpHlbdOpMuy9oeTSCeF0IKZZ6szpkvSirQogeP2fe9KRkzQpiza6YxxaJlWw== user@example", + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ4FDjyCsg+1Mh2C5G7ibR3z0Kw1dU57kfXebLRwS6CL bob@work", + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBP/JpJ/KHtKKImzISBDwLO0/EwytIr4pGZQXcP6GCSHchLMyfjf147KNlF9gC+3FibzqKH02EiQspVhRgfuK6y0= alice@home" + ] + } + }, + "password-authentication": { + "type": "boolean", + "default": true + } + } + }, + "service.ntp": { + "type": "object", + "properties": { + "servers": { + "type": "array", + "items": { + "type": "string", + "format": "uc-host" + }, + "examples": [ + "0.openwrt.pool.ntp.org" + ] + }, + "local-server": { + "type": "boolean", + "examples": [ + true + ] + } + } + }, + "service.mdns": { + "type": "object", + "properties": { + "enable": { + "type": "boolean", + "default": false + } + } + }, + "service.rtty": { + "type": "object", + "properties": { + "host": { + "type": "string", + "format": "uc-host", + "examples": [ + "192.168.1.10" + ] + }, + "port": { + "type": "integer", + "maximum": 65535, + "default": 5912 + }, + "token": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "examples": [ + "01234567890123456789012345678901" + ] + } + } + }, + "service.log": { + "type": "object", + "properties": { + "host": { + "type": "string", + "format": "uc-host", + "examples": [ + "192.168.1.10" + ] + }, + "port": { + "type": "integer", + "maximum": 65535, + "minimum": 100, + "examples": [ + 2000 + ] + }, + "proto": { + "type": "string", + "enum": [ + "tcp", + "udp" + ], + "default": "udp" + }, + "size": { + "type": "integer", + "minimum": 32, + "default": 1000 + } + } + }, + "service.http": { + "type": "object", + "properties": { + "http-port": { + "type": "integer", + "maximum": 65535, + "minimum": 1, + "default": 80 + } + } + }, + "service.igmp": { + "type": "object", + "properties": { + "enable": { + "type": "boolean", + "default": false + } + } + }, + "service.ieee8021x": { + "type": "object", + "properties": { + "ca-certificate": { + "type": "string" + }, + "use-local-certificates": { + "type": "boolean", + "default": false + }, + "server-certificate": { + "type": "string" + }, + "private-key": { + "type": "string" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/$defs/interface.ssid.radius.local-user" + } + } + } + }, + "service.radius-proxy": { + "type": "object", + "properties": { + "realms": { + "type": "array", + "items": { + "type": "object", + "properties": { + "realm": { + "type": "string", + "default": "*" + }, + "auto-discover": { + "type": "boolean", + "default": false + }, + "host": { + "type": "string", + "format": "uc-host", + "examples": [ + "192.168.1.10" + ] + }, + "port": { + "type": "integer", + "maximum": 65535, + "default": 2083 + }, + "secret": { + "type": "string" + }, + "use-local-certificates": { + "type": "boolean", + "default": false + }, + "ca-certificate": { + "type": "string" + }, + "certificate": { + "type": "string" + }, + "private-key": { + "type": "string" + }, + "private-key-password": { + "type": "string" + } + } + } + } + } + }, + "service.online-check": { + "type": "object", + "properties": { + "ping-hosts": { + "type": "array", + "items": { + "type": "string", + "format": "uc-host", + "examples": [ + "192.168.1.10" + ] + } + }, + "download-hosts": { + "type": "array", + "items": { + "type": "string", + "examples": [ + "www.example.org" + ] + } + }, + "check-interval": { + "type": "number", + "default": 60 + }, + "check-threshold": { + "type": "number", + "default": 1 + }, + "action": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "wifi", + "leds" + ] + } + } + } + }, + "service.open-flow": { + "type": "object", + "properties": { + "controller": { + "type": "string", + "format": "ip", + "example": "192.168.10.1" + }, + "datapath-description": { + "type": "string", + "example": "Building 2, Floor 6, AP 2" + }, + "mode": { + "type": "string", + "enum": [ + "pssl", + "ptcp", + "ssl", + "tcp" + ], + "default": "ssl" + }, + "ca-certificate": { + "type": "string" + }, + "ssl-certificate": { + "type": "string" + }, + "private-key": { + "type": "string" + } + } + }, + "service.data-plane": { + "type": "object", + "properties": { + "ingress-filters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "program": { + "type": "string", + "format": "uc-base64" + } + } + } + } + } + }, + "service.wifi-steering": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "local", + "cloud" + ], + "examples": [ + "local" + ] + }, + "assoc-steering": { + "type": "boolean", + "default": false + }, + "required-snr": { + "type": "integer", + "default": 0 + }, + "required-probe-snr": { + "type": "integer", + "default": 0 + }, + "required-roam-snr": { + "type": "integer", + "default": 0 + }, + "load-kick-threshold": { + "type": "integer", + "default": 0 + }, + "auto-channel": { + "type": "boolean", + "default": false + } + } + }, + "service.quality-of-service": { + "type": "object", + "properties": { + "select-ports": { + "type": "array", + "items": { + "type": "string", + "default": "WAN" + } + }, + "bandwidth-up": { + "type": "integer", + "default": 0 + }, + "bandwidth-down": { + "type": "integer", + "default": 0 + }, + "classifier": { + "type": "array", + "items": { + "type": "object", + "properties": { + "dscp": { + "type": "string", + "enum": [ + "CS0", + "CS1", + "CS2", + "CS3", + "CS4", + "CS5", + "CS6", + "CS7" + ], + "default": "CS1" + }, + "ports": { + "type": "array", + "items": { + "type": "object", + "properties": { + "protocol": { + "type": "string", + "enum": [ + "any", + "tcp", + "udp" + ], + "default": "any" + }, + "port": { + "type": "integer" + }, + "range-end": { + "type": "integer" + }, + "reclassify": { + "type": "boolean", + "default": true + } + } + }, + "dns": { + "type": "array", + "items": { + "type": "string", + "format": "fqdn" + } + } + } + } + } + } + } + }, + "service.facebook-wifi": { + "type": "object", + "properties": { + "vendor-id": { + "type": "string" + }, + "gateway-id": { + "type": "string" + }, + "secret": { + "type": "string" + } + } + }, + "service.airtime-policies": { + "type": "object", + "properties": { + "dns-match": { + "type": "array", + "items": { + "type": "string", + "examples": [ + "*.voice.example.com" + ] + } + }, + "dns-weight": { + "type": "integer", + "default": 256 + } + } + }, + "service": { + "type": "object", + "properties": { + "lldp": { + "$ref": "#/$defs/service.lldp" + }, + "ssh": { + "$ref": "#/$defs/service.ssh" + }, + "ntp": { + "$ref": "#/$defs/service.ntp" + }, + "mdns": { + "$ref": "#/$defs/service.mdns" + }, + "rtty": { + "$ref": "#/$defs/service.rtty" + }, + "log": { + "$ref": "#/$defs/service.log" + }, + "http": { + "$ref": "#/$defs/service.http" + }, + "igmp": { + "$ref": "#/$defs/service.igmp" + }, + "ieee8021x": { + "$ref": "#/$defs/service.ieee8021x" + }, + "radius-proxy": { + "$ref": "#/$defs/service.radius-proxy" + }, + "online-check": { + "$ref": "#/$defs/service.online-check" + }, + "open-flow": { + "$ref": "#/$defs/service.open-flow" + }, + "data-plane": { + "$ref": "#/$defs/service.data-plane" + }, + "wifi-steering": { + "$ref": "#/$defs/service.wifi-steering" + }, + "quality-of-service": { + "$ref": "#/$defs/service.quality-of-service" + }, + "facebook-wifi": { + "$ref": "#/$defs/service.facebook-wifi" + }, + "airtime-policies": { + "$ref": "#/$defs/service.airtime-policies" + } + } + }, + "metrics.statistics": { + "type": "object", + "properties": { + "interval": { + "type": "integer" + }, + "types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ssids", + "lldp", + "clients" + ] + } + } + } + }, + "metrics.health": { + "type": "object", + "properties": { + "interval": { + "type": "integer", + "minimum": 60 + } + } + }, + "metrics.wifi-frames": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "probe", + "auth", + "assoc", + "disassoc", + "deauth", + "local-deauth", + "inactive-deauth", + "key-mismatch", + "beacon-report", + "radar-detected" + ] + } + } + } + }, + "metrics.dhcp-snooping": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ack", + "discover", + "offer", + "request", + "solicit", + "reply", + "renew" + ] + } + } + } + }, + "metrics": { + "type": "object", + "properties": { + "statistics": { + "$ref": "#/$defs/metrics.statistics" + }, + "health": { + "$ref": "#/$defs/metrics.health" + }, + "wifi-frames": { + "$ref": "#/$defs/metrics.wifi-frames" + }, + "dhcp-snooping": { + "$ref": "#/$defs/metrics.dhcp-snooping" + } + } + }, + "config-raw": { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "string" + }, + "examples": [ + [ + "set", + "system.@system[0].timezone", + "GMT0" + ], + [ + "delete", + "firewall.@zone[0]" + ], + [ + "delete", + "dhcp.wan" + ], + [ + "add", + "dhcp", + "dhcp" + ], + [ + "add-list", + "system.ntp.server", + "0.pool.example.org" + ], + [ + "del-list", + "system.ntp.server", + "1.openwrt.pool.ntp.org" + ] + ] + } +} +} +} + )"_json; + + class ConfigurationValidator *ConfigurationValidator::instance_ = nullptr; + + void ConfigurationValidator::Init() { + if(Initialized_) + return; + std::string GitSchema; + try { + if(Utils::wgets(GitUCentralJSONSchemaFile, GitSchema)) { + auto schema = json::parse(GitSchema); + Validator_->set_root_schema(schema); + Logger().information("Using uCentral validation schema from GIT."); + } else { + std::string FileName{ MicroService::instance().DataDir() + "/ucentral.schema.json" }; + std::ifstream input(FileName); + std::stringstream schema_file; + schema_file << input.rdbuf(); + input.close(); + auto schema = json::parse(schema_file.str()); + Validator_->set_root_schema(schema); + Logger().information("Using uCentral validation schema from local file."); + } + } catch (const Poco::Exception &E) { + Validator_->set_root_schema(DefaultUCentralSchema); + Logger().information("Using uCentral validation from built-in default."); + } + Initialized_ = Working_ = true; + } + + int ConfigurationValidator::Start() { + Init(); + return 0; + } + + void ConfigurationValidator::Stop() { + + } + + static inline bool IsIPv4(const std::string &value) { + Poco::Net::IPAddress A; + return ((Poco::Net::IPAddress::tryParse(value,A) && A.family()==Poco::Net::IPAddress::IPv4)); + } + + static inline bool IsIPv6(const std::string &value) { + Poco::Net::IPAddress A; + return ((Poco::Net::IPAddress::tryParse(value,A) && A.family()==Poco::Net::IPAddress::IPv6)); + } + + static inline bool IsIP(const std::string &value) { + return IsIPv4(value) || IsIPv6(value); + } + + static inline bool IsCIDRv6(const std::string &value) { + auto Tokens = Poco::StringTokenizer(value,"/"); + if(Tokens.count()==2 && IsIPv6(Tokens[0])) { + auto Mask = std::atoi(Tokens[1].c_str()); + if(Mask>=48 && Mask<=128) + return true; + } + return false; + } + + static inline bool IsCIDRv4(const std::string &value) { + auto Tokens = Poco::StringTokenizer(value,"/"); + if(Tokens.count()==2 && IsIPv4(Tokens[0])) { + auto Mask = std::atoi(Tokens[1].c_str()); + if(Mask>=0 && Mask<=32) + return true; + } + return false; + } + + static inline bool IsCIDR(const std::string &value) { + return IsCIDRv4(value) || IsCIDRv6(value); + } + + void ConfigurationValidator::my_format_checker(const std::string &format, const std::string &value) + { + static const std::regex host_regex{"^(?=.{1,254}$)((?=[a-z0-9-]{1,63}\\.)(xn--+)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,63}$"}; + static const std::regex mac_regex{"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"}; + static const std::regex uc_timeout_regex{"^[0-9]+[dmshw]$"}; + static const std::regex b64_regex("^[a-zA-Z0-9\\+/]*={0,3}$"); + + if(format == "uc-cidr4") { + if(IsCIDRv4(value)) + return; + throw std::invalid_argument(value + " is not a valid CIDR IPv4: should be something like 192.168.0.0/16."); + } else if(format == "uc-cidr6") { + if(IsCIDRv6(value)) + return; + throw std::invalid_argument(value + " is not a valid CIDR IPv6: should be something like 2e60:3500::/64."); + } else if(format=="uc-cidr") { + if(IsCIDR(value)) + return; + throw std::invalid_argument(value + " is not a valid CIDR IPv6/IPv4: should be something like 2e60:3500::/64."); + } else if(format == "uc-mac") { + if(std::regex_match(value,mac_regex)) + return; + throw std::invalid_argument(value + " is not a valid MAC: should be something like 2e60:3500::/64."); + } else if(format == "uc-timeout") { + if(std::regex_match(value,uc_timeout_regex)) + return; + throw std::invalid_argument(value + " is not a proper timeout value: 6d, 300m, 24h, 84000s, infinite"); + } else if(format == "uc-host") { + if(IsIP(value)) + return; + if(std::regex_match(value,host_regex)) + return; + throw std::invalid_argument(value + " is not a proper FQDN."); + } else if(format == "fqdn") { + if(std::regex_match(value,host_regex)) + return; + throw std::invalid_argument(value + " is not a proper FQDN."); + } else if(format == "uc-base64") { + std::string s{value}; + Poco::trimInPlace(s); + if( (s.size() %4 ==0) && std::regex_match(s,b64_regex)) + return; + throw std::invalid_argument(value + " is not a base64 encoded value."); + } else if(format == "uri") { + try { + Poco::URI uri(value); + return; + } catch (...) { + } + throw std::invalid_argument(value + " is not a valid URI: should be something like https://hello.world.com."); + } else if(format == "ip") { + if (IsIP(value)) + return; + throw std::invalid_argument(value + " is not a valid IP address."); + } else { + try { + nlohmann::json_schema::default_string_format_check(format,value); + } catch (const std::logic_error &E) { + std::string Error{"JSON Schema validation: "}; + } + } + } + + bool ConfigurationValidator::Validate(const std::string &C, std::string &Error) { + if(Working_) { + try { + auto Doc = json::parse(C); + Validator_->validate(Doc); + return true; + } catch(const std::exception &E) { + Error = E.what(); + std::cout << "Validation failed, here is why: " << E.what() << "\n"; + return false; + } + } + return true; + } + + void ConfigurationValidator::reinitialize(Poco::Util::Application &self) { + Logger().information("Reinitializing."); + Working_ = Initialized_ = false; + Init(); + } + +} diff --git a/src/framework/ConfigurationValidator.h b/src/framework/ConfigurationValidator.h new file mode 100644 index 0000000..522697c --- /dev/null +++ b/src/framework/ConfigurationValidator.h @@ -0,0 +1,44 @@ +// +// Created by stephane bourque on 2021-09-14. +// + +#pragma once + +#include +#include "framework/MicroService.h" + +using nlohmann::json; +using nlohmann::json_schema::json_validator; + +namespace OpenWifi { + class ConfigurationValidator : public SubSystemServer { + public: + + static ConfigurationValidator *instance() { + if(instance_== nullptr) + instance_ = new ConfigurationValidator; + return instance_; + } + + bool Validate(const std::string &C, std::string &Error); + static void my_format_checker(const std::string &format, const std::string &value); + int Start() override; + void Stop() override; + void reinitialize(Poco::Util::Application &self) override; + + private: + static ConfigurationValidator * instance_; + bool Initialized_=false; + bool Working_=false; + void Init(); + std::unique_ptr Validator_=std::make_unique(nullptr, my_format_checker); + + ConfigurationValidator(): + SubSystemServer("configvalidator", "CFG-VALIDATOR", "config.validator") { + } + }; + + inline ConfigurationValidator * ConfigurationValidator() { return ConfigurationValidator::instance(); } + inline bool ValidateUCentralConfiguration(const std::string &C, std::string &Error) { return ConfigurationValidator::instance()->Validate(C, Error); } +} + diff --git a/src/framework/CountryCodes.h b/src/framework/CountryCodes.h new file mode 100644 index 0000000..92a002a --- /dev/null +++ b/src/framework/CountryCodes.h @@ -0,0 +1,271 @@ +// +// Created by stephane bourque on 2021-10-08. +// + +#pragma once + +#include +#include +#include + +namespace OpenWifi { + + struct CountryInfo { + std::string code; + std::string name; + }; + + inline static const std::vector CountryCodes { + { .code= "US", .name= "United States" }, + { .code= "GB", .name= "United Kingdom" }, + { .code= "CA", .name= "Canada" }, + { .code= "AF", .name= "Afghanistan" }, + { .code= "AX", .name= "Aland Islands" }, + { .code= "AL", .name= "Albania" }, + { .code= "DZ", .name= "Algeria" }, + { .code= "AS", .name= "American Samoa" }, + { .code= "AD", .name= "Andorra" }, + { .code= "AO", .name= "Angola" }, + { .code= "AI", .name= "Anguilla" }, + { .code= "AQ", .name= "Antarctica" }, + { .code= "AG", .name= "Antigua And Barbuda" }, + { .code= "AR", .name= "Argentina" }, + { .code= "AM", .name= "Armenia" }, + { .code= "AN", .name= "Netherlands Antilles" }, + { .code= "AW", .name= "Aruba" }, + { .code= "AU", .name= "Australia" }, + { .code= "AT", .name= "Austria" }, + { .code= "AZ", .name= "Azerbaijan" }, + { .code= "BS", .name= "Bahamas" }, + { .code= "BH", .name= "Bahrain" }, + { .code= "BD", .name= "Bangladesh" }, + { .code= "BB", .name= "Barbados" }, + { .code= "BY", .name= "Belarus" }, + { .code= "BE", .name= "Belgium" }, + { .code= "BZ", .name= "Belize" }, + { .code= "BJ", .name= "Benin" }, + { .code= "BM", .name= "Bermuda" }, + { .code= "BT", .name= "Bhutan" }, + { .code= "BO", .name= "Bolivia" }, + { .code= "BA", .name= "Bosnia And Herzegovina" }, + { .code= "BW", .name= "Botswana" }, + { .code= "BV", .name= "Bouvet Island" }, + { .code= "BR", .name= "Brazil" }, + { .code= "IO", .name= "British Indian Ocean Territory" }, + { .code= "BN", .name= "Brunei Darussalam" }, + { .code= "BG", .name= "Bulgaria" }, + { .code= "BF", .name= "Burkina Faso" }, + { .code= "BI", .name= "Burundi" }, + { .code= "KH", .name= "Cambodia" }, + { .code= "CM", .name= "Cameroon" }, + { .code= "CA", .name= "Canada" }, + { .code= "CV", .name= "Cape Verde" }, + { .code= "KY", .name= "Cayman Islands" }, + { .code= "CF", .name= "Central African Republic" }, + { .code= "TD", .name= "Chad" }, + { .code= "CL", .name= "Chile" }, + { .code= "CN", .name= "China" }, + { .code= "CX", .name= "Christmas Island" }, + { .code= "CC", .name= "Cocos (Keeling) Islands" }, + { .code= "CO", .name= "Colombia" }, + { .code= "KM", .name= "Comoros" }, + { .code= "CG", .name= "Congo" }, + { .code= "CD", .name= "Congo, Democratic Republic" }, + { .code= "CK", .name= "Cook Islands" }, + { .code= "CR", .name= "Costa Rica" }, + { .code= "CI", .name= "Cote D\"Ivoire" }, + { .code= "HR", .name= "Croatia" }, + { .code= "CU", .name= "Cuba" }, + { .code= "CY", .name= "Cyprus" }, + { .code= "CZ", .name= "Czech Republic" }, + { .code= "DK", .name= "Denmark" }, + { .code= "DJ", .name= "Djibouti" }, + { .code= "DM", .name= "Dominica" }, + { .code= "DO", .name= "Dominican Republic" }, + { .code= "EC", .name= "Ecuador" }, + { .code= "EG", .name= "Egypt" }, + { .code= "SV", .name= "El Salvador" }, + { .code= "GQ", .name= "Equatorial Guinea" }, + { .code= "ER", .name= "Eritrea" }, + { .code= "EE", .name= "Estonia" }, + { .code= "ET", .name= "Ethiopia" }, + { .code= "FK", .name= "Falkland Islands (Malvinas)" }, + { .code= "FO", .name= "Faroe Islands" }, + { .code= "FJ", .name= "Fiji" }, + { .code= "FI", .name= "Finland" }, + { .code= "FR", .name= "France" }, + { .code= "GF", .name= "French Guiana" }, + { .code= "PF", .name= "French Polynesia" }, + { .code= "TF", .name= "French Southern Territories" }, + { .code= "GA", .name= "Gabon" }, + { .code= "GM", .name= "Gambia" }, + { .code= "GE", .name= "Georgia" }, + { .code= "DE", .name= "Germany" }, + { .code= "GH", .name= "Ghana" }, + { .code= "GI", .name= "Gibraltar" }, + { .code= "GR", .name= "Greece" }, + { .code= "GL", .name= "Greenland" }, + { .code= "GD", .name= "Grenada" }, + { .code= "GP", .name= "Guadeloupe" }, + { .code= "GU", .name= "Guam" }, + { .code= "GT", .name= "Guatemala" }, + { .code= "GG", .name= "Guernsey" }, + { .code= "GN", .name= "Guinea" }, + { .code= "GW", .name= "Guinea-Bissau" }, + { .code= "GY", .name= "Guyana" }, + { .code= "HT", .name= "Haiti" }, + { .code= "HM", .name= "Heard Island & Mcdonald Islands" }, + { .code= "VA", .name= "Holy See (Vatican City State)" }, + { .code= "HN", .name= "Honduras" }, + { .code= "HK", .name= "Hong Kong" }, + { .code= "HU", .name= "Hungary" }, + { .code= "IS", .name= "Iceland" }, + { .code= "IN", .name= "India" }, + { .code= "ID", .name= "Indonesia" }, + { .code= "IR", .name= "Iran, Islamic Republic Of" }, + { .code= "IQ", .name= "Iraq" }, + { .code= "IE", .name= "Ireland" }, + { .code= "IM", .name= "Isle Of Man" }, + { .code= "IL", .name= "Israel" }, + { .code= "IT", .name= "Italy" }, + { .code= "JM", .name= "Jamaica" }, + { .code= "JP", .name= "Japan" }, + { .code= "JE", .name= "Jersey" }, + { .code= "JO", .name= "Jordan" }, + { .code= "KZ", .name= "Kazakhstan" }, + { .code= "KE", .name= "Kenya" }, + { .code= "KI", .name= "Kiribati" }, + { .code= "KR", .name= "Korea" }, + { .code= "KW", .name= "Kuwait" }, + { .code= "KG", .name= "Kyrgyzstan" }, + { .code= "LA", .name= "Lao People\"s Democratic Republic" }, + { .code= "LV", .name= "Latvia" }, + { .code= "LB", .name= "Lebanon" }, + { .code= "LS", .name= "Lesotho" }, + { .code= "LR", .name= "Liberia" }, + { .code= "LY", .name= "Libyan Arab Jamahiriya" }, + { .code= "LI", .name= "Liechtenstein" }, + { .code= "LT", .name= "Lithuania" }, + { .code= "LU", .name= "Luxembourg" }, + { .code= "MO", .name= "Macao" }, + { .code= "MK", .name= "Macedonia" }, + { .code= "MG", .name= "Madagascar" }, + { .code= "MW", .name= "Malawi" }, + { .code= "MY", .name= "Malaysia" }, + { .code= "MV", .name= "Maldives" }, + { .code= "ML", .name= "Mali" }, + { .code= "MT", .name= "Malta" }, + { .code= "MH", .name= "Marshall Islands" }, + { .code= "MQ", .name= "Martinique" }, + { .code= "MR", .name= "Mauritania" }, + { .code= "MU", .name= "Mauritius" }, + { .code= "YT", .name= "Mayotte" }, + { .code= "MX", .name= "Mexico" }, + { .code= "FM", .name= "Micronesia, Federated States Of" }, + { .code= "MD", .name= "Moldova" }, + { .code= "MC", .name= "Monaco" }, + { .code= "MN", .name= "Mongolia" }, + { .code= "ME", .name= "Montenegro" }, + { .code= "MS", .name= "Montserrat" }, + { .code= "MA", .name= "Morocco" }, + { .code= "MZ", .name= "Mozambique" }, + { .code= "MM", .name= "Myanmar" }, + { .code= "NA", .name= "Namibia" }, + { .code= "NR", .name= "Nauru" }, + { .code= "NP", .name= "Nepal" }, + { .code= "NL", .name= "Netherlands" }, + { .code= "AN", .name= "Netherlands Antilles" }, + { .code= "NC", .name= "New Caledonia" }, + { .code= "NZ", .name= "New Zealand" }, + { .code= "NI", .name= "Nicaragua" }, + { .code= "NE", .name= "Niger" }, + { .code= "NG", .name= "Nigeria" }, + { .code= "NU", .name= "Niue" }, + { .code= "NF", .name= "Norfolk Island" }, + { .code= "MP", .name= "Northern Mariana Islands" }, + { .code= "NO", .name= "Norway" }, + { .code= "OM", .name= "Oman" }, + { .code= "PK", .name= "Pakistan" }, + { .code= "PW", .name= "Palau" }, + { .code= "PS", .name= "Palestinian Territory, Occupied" }, + { .code= "PA", .name= "Panama" }, + { .code= "PG", .name= "Papua New Guinea" }, + { .code= "PY", .name= "Paraguay" }, + { .code= "PE", .name= "Peru" }, + { .code= "PH", .name= "Philippines" }, + { .code= "PN", .name= "Pitcairn" }, + { .code= "PL", .name= "Poland" }, + { .code= "PT", .name= "Portugal" }, + { .code= "PR", .name= "Puerto Rico" }, + { .code= "QA", .name= "Qatar" }, + { .code= "RE", .name= "Reunion" }, + { .code= "RO", .name= "Romania" }, + { .code= "RU", .name= "Russian Federation" }, + { .code= "RW", .name= "Rwanda" }, + { .code= "BL", .name= "Saint Barthelemy" }, + { .code= "SH", .name= "Saint Helena" }, + { .code= "KN", .name= "Saint Kitts And Nevis" }, + { .code= "LC", .name= "Saint Lucia" }, + { .code= "MF", .name= "Saint Martin" }, + { .code= "PM", .name= "Saint Pierre And Miquelon" }, + { .code= "VC", .name= "Saint Vincent And Grenadines" }, + { .code= "WS", .name= "Samoa" }, + { .code= "SM", .name= "San Marino" }, + { .code= "ST", .name= "Sao Tome And Principe" }, + { .code= "SA", .name= "Saudi Arabia" }, + { .code= "SN", .name= "Senegal" }, + { .code= "RS", .name= "Serbia" }, + { .code= "SC", .name= "Seychelles" }, + { .code= "SL", .name= "Sierra Leone" }, + { .code= "SG", .name= "Singapore" }, + { .code= "SK", .name= "Slovakia" }, + { .code= "SI", .name= "Slovenia" }, + { .code= "SB", .name= "Solomon Islands" }, + { .code= "SO", .name= "Somalia" }, + { .code= "ZA", .name= "South Africa" }, + { .code= "GS", .name= "South Georgia And Sandwich Isl." }, + { .code= "ES", .name= "Spain" }, + { .code= "LK", .name= "Sri Lanka" }, + { .code= "SD", .name= "Sudan" }, + { .code= "SR", .name= "Suriname" }, + { .code= "SJ", .name= "Svalbard And Jan Mayen" }, + { .code= "SZ", .name= "Swaziland" }, + { .code= "SE", .name= "Sweden" }, + { .code= "CH", .name= "Switzerland" }, + { .code= "SY", .name= "Syrian Arab Republic" }, + { .code= "TW", .name= "Taiwan" }, + { .code= "TJ", .name= "Tajikistan" }, + { .code= "TZ", .name= "Tanzania" }, + { .code= "TH", .name= "Thailand" }, + { .code= "TL", .name= "Timor-Leste" }, + { .code= "TG", .name= "Togo" }, + { .code= "TK", .name= "Tokelau" }, + { .code= "TO", .name= "Tonga" }, + { .code= "TT", .name= "Trinidad And Tobago" }, + { .code= "TN", .name= "Tunisia" }, + { .code= "TR", .name= "Turkey" }, + { .code= "TM", .name= "Turkmenistan" }, + { .code= "TC", .name= "Turks And Caicos Islands" }, + { .code= "TV", .name= "Tuvalu" }, + { .code= "UG", .name= "Uganda" }, + { .code= "UA", .name= "Ukraine" }, + { .code= "AE", .name= "United Arab Emirates" }, + { .code= "GB", .name= "United Kingdom" }, + { .code= "US", .name= "United States" }, + { .code= "UM", .name= "United States Outlying Islands" }, + { .code= "UY", .name= "Uruguay" }, + { .code= "UZ", .name= "Uzbekistan" }, + { .code= "VU", .name= "Vanuatu" }, + { .code= "VE", .name= "Venezuela" }, + { .code= "VN", .name= "Viet Nam" }, + { .code= "VG", .name= "Virgin Islands, British" }, + { .code= "VI", .name= "Virgin Islands, U.S." }, + { .code= "WF", .name= "Wallis And Futuna" }, + { .code= "EH", .name= "Western Sahara" }, + { .code= "YE", .name= "Yemen" }, + { .code= "ZM", .name= "Zambia" }, + { .code= "ZW", .name= "Zimbabwe" } + }; + +} + diff --git a/src/framework/KafkaTopics.h b/src/framework/KafkaTopics.h new file mode 100644 index 0000000..6be2ba5 --- /dev/null +++ b/src/framework/KafkaTopics.h @@ -0,0 +1,40 @@ +// +// 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. +// + +#pragma once + +namespace OpenWifi::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"}; + static const std::string DEVICE_EVENT_QUEUE{"device_event_queue"}; + static const std::string DEVICE_TELEMETRY{"device_telemetry"}; + + 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"}; + } + } +} + diff --git a/src/framework/MicroService.h b/src/framework/MicroService.h new file mode 100644 index 0000000..e98f241 --- /dev/null +++ b/src/framework/MicroService.h @@ -0,0 +1,4154 @@ +// +// 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. +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +#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 "Poco/Net/Context.h" +#include "Poco/Net/SecureServerSocket.h" +#include "Poco/Net/Socket.h" +#include "Poco/Net/SSLManager.h" +#include "Poco/Net/PrivateKeyPassphraseHandler.h" +#include "Poco/Net/HTTPServerResponse.h" +#include "Poco/Net/HTTPServer.h" +#include "Poco/Net/FTPStreamFactory.h" +#include "Poco/Net/FTPSStreamFactory.h" +#include "Poco/Net/HTTPSStreamFactory.h" +#include "Poco/Net/HTTPStreamFactory.h" +#include "Poco/File.h" +#include "Poco/Net/HTTPRequestHandler.h" +#include "Poco/Net/OAuth20Credentials.h" +#include "Poco/Util/HelpFormatter.h" +#include "Poco/Net/PartHandler.h" +#include "Poco/TemporaryFile.h" +#include "Poco/NullStream.h" +#include "Poco/CountingStream.h" +#include "Poco/URI.h" +#include "Poco/Net/HTTPSClientSession.h" +#include "Poco/Net/NetworkInterface.h" +#include "Poco/ExpireLRUCache.h" +#include "Poco/JSON/Object.h" +#include "Poco/JSON/Parser.h" +#include "Poco/StringTokenizer.h" +#include "Poco/AsyncChannel.h" +#include "Poco/ConsoleChannel.h" +#include "Poco/AutoPtr.h" +#include "Poco/FormattingChannel.h" +#include "Poco/PatternFormatter.h" +#include "Poco/FileChannel.h" +#include "Poco/SimpleFileChannel.h" +#include "Poco/Util/PropertyFileConfiguration.h" + +#include "cppkafka/cppkafka.h" + +#include "framework/OpenWifiTypes.h" +#include "framework/KafkaTopics.h" +#include "framework/RESTAPI_protocol.h" +#include "framework/RESTAPI_errors.h" +#include "framework/uCentral_Protocol.h" +#include "RESTObjects/RESTAPI_SecurityObjects.h" +#include "nlohmann/json.hpp" + +#include "ow_version.h" + +#define _OWDEBUG_ std::cout<< __FILE__ <<":" << __LINE__ << std::endl; + +namespace OpenWifi { + + enum UNAUTHORIZED_REASON { + SUCCESS=0, + PASSWORD_CHANGE_REQUIRED, + INVALID_CREDENTIALS, + PASSWORD_ALREADY_USED, + USERNAME_PENDING_VERIFICATION, + PASSWORD_INVALID, + INTERNAL_ERROR, + ACCESS_DENIED, + INVALID_TOKEN, + EXPIRED_TOKEN, + RATE_LIMIT_EXCEEDED, + BAD_MFA_TRANSACTION, + MFA_FAILURE + }; + + class AppServiceRegistry { + public: + inline AppServiceRegistry(); + + static AppServiceRegistry & instance() { + static auto instance_= new AppServiceRegistry; + return *instance_; + } + + inline ~AppServiceRegistry() { + Save(); + } + + inline void Save() { + std::istringstream IS( to_string(Registry_)); + std::ofstream OF; + OF.open(FileName,std::ios::binary | std::ios::trunc); + Poco::StreamCopier::copyStream(IS, OF); + } + + inline void Set(const char *Key, uint64_t Value ) { + Registry_[Key] = Value; + Save(); + } + + inline void Set(const char *Key, const std::string &Value ) { + Registry_[Key] = Value; + Save(); + } + + inline void Set(const char *Key, bool Value ) { + Registry_[Key] = Value; + Save(); + } + + inline bool Get(const char *Key, bool & Value ) { + if(Registry_[Key].is_boolean()) { + Value = Registry_[Key].get(); + return true; + } + return false; + } + + inline bool Get(const char *Key, uint64_t & Value ) { + if(Registry_[Key].is_number_unsigned()) { + Value = Registry_[Key].get(); + return true; + } + return false; + } + + inline bool Get(const char *Key, std::string & Value ) { + if(Registry_[Key].is_string()) { + Value = Registry_[Key].get(); + return true; + } + return false; + } + + private: + std::string FileName; + nlohmann::json Registry_; + }; +} + +namespace OpenWifi::RESTAPI_utils { + + inline 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); + } + + 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, double V) { + Obj.set(Field,V); + } + + inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, float 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 Types::StringPairVec & S) { + Poco::JSON::Array Array; + for(const auto &i:S) { + Poco::JSON::Object O; + O.set("tag",i.first); + O.set("value", i.second); + Array.add(O); + } + Obj.set(Field,Array); + } + + 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::TagList &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::StringPairVec &Vec) { + if(Obj->isArray(Field)) { + auto O = Obj->getArray(Field); + for(const auto &i:*O) { + std::string S1,S2; + auto Inner = i.extract(); + if(Inner->has("tag")) + S1 = Inner->get("tag").toString(); + if(Inner->has("value")) + S2 = Inner->get("value").toString(); + auto P = std::make_pair(S1,S2); + Vec.push_back(P); + } + } + } + + 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()); + } + } + } + + inline void field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, Types::TagList &V) { + if(Obj->isArray(Field)) { + V.clear(); + Poco::JSON::Array::Ptr A = Obj->getArray(Field); + for(const auto &i:*A) { + V.push_back(i); + } + } + } + + 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); + } + + inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, int Value) { + Obj.set(Field, Value); + } + + template void field_to_json(Poco::JSON::Object &Obj, const char *Field, const T &Value) { + Poco::JSON::Object Answer; + Value.to_json(Answer); + Obj.set(Field, Answer); + } + + 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); + } + } + } + + inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, int &Value) { + if(Obj->isObject(Field)) { + Value = Obj->get(Field); + } + } + + 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::TagList & ObjectArray) { + Poco::JSON::Array OutputArr; + if(ObjectArray.empty()) + return "[]"; + for(auto const &i:ObjectArray) { + OutputArr.add(i); + } + std::ostringstream OS; + Poco::JSON::Stringifier::stringify(OutputArr,OS, 0,0, Poco::JSON_PRESERVE_KEY_ORDER ); + return OS.str(); + } + + 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(); + } + + inline std::string to_string(const Types::StringPairVec & ObjectArray) { + Poco::JSON::Array OutputArr; + if(ObjectArray.empty()) + return "[]"; + for(auto const &i:ObjectArray) { + Poco::JSON::Array InnerArray; + InnerArray.add(i.first); + InnerArray.add(i.second); + OutputArr.add(InnerArray); + } + 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 std::vector> & ObjectArray) { + Poco::JSON::Array OutputArr; + if(ObjectArray.empty()) + return "[]"; + for(auto const &i:ObjectArray) { + Poco::JSON::Array InnerArr; + for(auto const &j:i) { + if constexpr(std::is_integral::value) { + InnerArr.add(j); + } if constexpr(std::is_same_v) { + InnerArr.add(j); + } else { + InnerArr.add(j); + Poco::JSON::Object O; + j.to_json(O); + InnerArr.add(O); + } + } + OutputArr.add(InnerArr); + } + 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; + } + + inline OpenWifi::Types::TagList to_taglist(const std::string & ObjectString) { + Types::TagList 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); + } + } catch (...) { + + } + return Result; + } + + inline Types::StringPairVec to_stringpair_array(const std::string &S) { + Types::StringPairVec R; + if(S.empty()) + return R; + try { + Poco::JSON::Parser P; + auto Object = P.parse(S).template extract(); + for (const auto &i : *Object) { + auto InnerObject = i.template extract(); + if(InnerObject->size()==2) { + auto S1 = InnerObject->getElement(0); + auto S2 = InnerObject->getElement(1); + R.push_back(std::make_pair(S1,S2)); + } + } + } catch (...) { + + } + + return R; + } + + 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 std::vector> to_array_of_array_of_object(const std::string & ObjectString) { + std::vector> Result; + if(ObjectString.empty()) + return Result; + try { + Poco::JSON::Parser P1; + auto OutterArray = P1.parse(ObjectString).template extract(); + for (auto const &i : *OutterArray) { + Poco::JSON::Parser P2; + auto InnerArray = P2.parse(i).template extract(); + std::vector InnerVector; + for(auto const &j: *InnerArray) { + auto Object = j.template extract(); + T Obj; + Obj.from_json(Object); + InnerVector.push_back(Obj); + } + Result.push_back(InnerVector); + } + } 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; + } +} + +namespace OpenWifi::Utils { + + enum MediaTypeEncodings { + PLAIN, + BINARY, + BASE64 + }; + struct MediaTypeEncoding { + MediaTypeEncodings Encoding=PLAIN; + std::string ContentType; + }; + + [[nodiscard]] inline 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]] inline 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]] inline 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 = '='; + + using byte = std::uint8_t; + + [[nodiscard]] inline 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; + } + + [[nodiscard]] inline std::vector base64decode(const std::string& input) + { + if(input.length() % 4) + throw std::runtime_error("Invalid base64 length!"); + + std::size_t padding=0; + + 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=0; + 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; + } + + inline 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; + } + + + inline 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; + } + + inline 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; + } + + [[nodiscard]] inline 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"; + } + } + + [[nodiscard]] inline 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; + } + + [[nodiscard]] inline std::string IntToSerialNumber(uint64_t S) { + char b[16]; + for(int i=0;i<12;++i) { + int B = (S & 0x0f); + if(B<10) + b[11-i] = B+'0'; + else + b[11-i] = B - 10 + 'a'; + S >>= 4 ; + } + b[12]=0; + return b; + } + + + [[nodiscard]] inline bool SerialNumberMatch(const std::string &S1, const std::string &S2, int Bits=2) { + auto S1_i = SerialNumberToInt(S1); + auto S2_i = SerialNumberToInt(S2); + return ((S1_i>>Bits)==(S2_i>>Bits)); + } + + [[nodiscard]] inline 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; + } + + [[nodiscard]] inline 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; + } + + [[nodiscard]] inline 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) ; + OpenWifi::AppServiceRegistry().Set("systemid",S); + return S; + } + + [[nodiscard]] inline uint64_t GetSystemId(); + + [[nodiscard]] inline bool ValidEMailAddress(const std::string &email) { + // define a regular expression + static const std::regex pattern + (("(\\w+)(\\.|_\\+)?(\\w*)@(\\w+)(\\.(\\w+))+")); + + // try to match the string with the regular expression + return std::regex_match(email, pattern); + } + + + [[nodiscard]] inline 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; + } + + inline void ReplaceVariables( std::string & Content , const Types::StringPairVec & P) { + for(const auto &[Variable,Value]:P) { + Poco::replaceInPlace(Content,"${" + Variable + "}", Value); + } + } + + [[nodiscard]] inline 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" }; + } + + [[nodiscard]] inline 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; + } + + [[nodiscard]] inline 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; + } + + [[nodiscard]] inline bool wgets(const std::string &URL, std::string &Response) { + try { + Poco::URI uri(URL); + Poco::Net::HTTPSClientSession session(uri.getHost(), uri.getPort()); + + // prepare path + std::string path(uri.getPathAndQuery()); + if (path.empty()) { + path = "/"; + } + + // send request + Poco::Net::HTTPRequest req(Poco::Net::HTTPRequest::HTTP_GET, path, Poco::Net::HTTPMessage::HTTP_1_1); + session.sendRequest(req); + + Poco::Net::HTTPResponse res; + std::istream &is = session.receiveResponse(res); + std::ostringstream os; + + Poco::StreamCopier::copyStream(is,os); + Response = os.str(); + + return true; + } catch (...) { + + } + return false; + } + + template< typename T > + std::string int_to_hex( T i ) + { + std::stringstream stream; + stream << std::setfill ('0') << std::setw(12) + << std::hex << i; + return stream.str(); + } + +} + +namespace OpenWifi { + + static const std::string uSERVICE_SECURITY{"owsec"}; + static const std::string uSERVICE_GATEWAY{"owgw"}; + static const std::string uSERVICE_FIRMWARE{ "owfms"}; + static const std::string uSERVICE_TOPOLOGY{ "owtopo"}; + static const std::string uSERVICE_PROVISIONING{ "owprov"}; + static const std::string uSERVICE_OWLS{ "owls"}; + static const std::string uSERVICE_SUBCRIBER{ "owsub"}; + static const std::string uSERVICE_INSTALLER{ "owinst"}; + static const std::string uSERVICE_ANALYTICS{ "owanalytics"}; + + class ConfigurationEntry { + public: + template explicit ConfigurationEntry(T def) : + Default_(def), + Current_(def){ + } + + template explicit ConfigurationEntry(T def, T cur, const std::string &Hint="") : + Default_(def), + Current_(cur), + Hint_(Hint){ + } + + inline ConfigurationEntry()=default; + inline ~ConfigurationEntry()=default; + + template explicit operator T () const { return std::get(Current_); } + inline ConfigurationEntry & operator=(const char *v) { Current_ = std::string(v); return *this;} + template ConfigurationEntry & operator=(T v) { Current_ = (T) v; return *this;} + + void reset() { + Current_ = Default_; + } + + private: + std::variant Default_, Current_; + std::string Hint_; + }; + inline std::string to_string(const ConfigurationEntry &v) { return (std::string) v; } + + typedef std::map ConfigurationMap_t; + + template class RecordCache { + public: + explicit RecordCache( KeyType Record::* Q) : + MemberOffset(Q){ + }; + inline auto update(const Record &R) { + return Cache_.update(R.*MemberOffset, R); + } + inline auto get(const KeyType &K) { + return Cache_.get(K); + } + inline auto remove(const KeyType &K) { + return Cache_.remove(K); + } + inline auto remove(const Record &R) { + return Cache_.remove(R.*MemberOffset); + } + private: + KeyType Record::* MemberOffset; + Poco::ExpireLRUCache Cache_{Size,Expiry}; + }; + + class MyErrorHandler : public Poco::ErrorHandler { + public: + explicit MyErrorHandler(Poco::Util::Application &App) : App_(App) {} + inline void 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())); + } + + inline void exception(const std::exception & E) { + Poco::Thread * CurrentThread = Poco::Thread::current(); + App_.logger().warning(Poco::format("std::exception on %s",CurrentThread->getName())); + } + + inline void exception() { + Poco::Thread * CurrentThread = Poco::Thread::current(); + App_.logger().warning(Poco::format("exception on %s",CurrentThread->getName())); + } + private: + Poco::Util::Application &App_; + }; + + class BusEventManager : public Poco::Runnable { + public: + inline void run() final; + inline void Start(); + inline void Stop(); + private: + std::atomic_bool Running_ = false; + Poco::Thread Thread_; + }; + + 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_; + }; + + 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]] inline const std::string &Address() const { return address_; }; + [[nodiscard]] inline uint32_t Port() const { return port_; }; + [[nodiscard]] inline const std::string &KeyFile() const { return key_file_; }; + [[nodiscard]] inline const std::string &CertFile() const { return cert_file_; }; + [[nodiscard]] inline const std::string &RootCA() const { return root_ca_; }; + [[nodiscard]] inline const std::string &KeyFilePassword() const { return key_file_password_; }; + [[nodiscard]] inline const std::string &IssuerCertFile() const { return issuer_cert_file_; }; + [[nodiscard]] inline const std::string &Name() const { return name_; }; + [[nodiscard]] inline int Backlog() const { return backlog_; } + + [[nodiscard]] inline Poco::Net::SecureServerSocket 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); + } + } + + inline void LogCertInfo(Poco::Logger &L, const Poco::Crypto::X509Certificate &C) const { + 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("============================================================================================="); + } + + inline void 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); + } + } + + inline void 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); + } + } + + 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 &LoggingPrefix, + std::string SubSystemConfigPrefix); + + inline void initialize(Poco::Util::Application &self) override; + inline void uninitialize() override { + } + inline void reinitialize(Poco::Util::Application &self) override { + Logger().information("Reloading of this subsystem is not supported."); + } + inline void defineOptions(Poco::Util::OptionSet &options) override { + } + inline const std::string & Name() const { return Name_; }; + inline const char * name() const override { return Name_.c_str(); } + + inline const PropertiesFileServerEntry & Host(uint64_t index) { return ConfigServersList_[index]; }; + inline uint64_t HostSize() const { return ConfigServersList_.size(); } + inline Poco::Logger &Logger() { if(Log_) + return Log_->L; + return Poco::Logger::get("tmp"); + }; + inline void SetLoggingLevel(Poco::Message::Priority NewPriority) { Logger().setLevel(NewPriority); } + inline int GetLoggingLevel() { return Logger().getLevel(); } + + virtual int Start() = 0; + virtual void Stop() = 0; + + struct LoggerWrapper { + Poco::Logger &L; + inline LoggerWrapper(Poco::Logger &Logger) : L(Logger) {} + }; + + protected: + std::recursive_mutex Mutex_; + std::vector ConfigServersList_; + private: + std::unique_ptr Log_; + // Poco::Logger &Logger_; + std::string Name_; + std::string LoggerPrefix_; + std::string SubSystemConfigPrefix_; + }; + + class RESTAPI_GenericServer { + public: + + enum { + LOG_GET=0, + LOG_DELETE, + LOG_PUT, + LOG_POST + }; + + void inline SetFlags(bool External, const std::string &Methods) { + Poco::StringTokenizer Tokens(Methods,","); + auto Offset = (External ? 0 : 4); + for(const auto &i:Tokens) { + if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_DELETE)==0) + LogFlags_[Offset+LOG_DELETE]=true; + else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_PUT)==0) + LogFlags_[Offset+LOG_PUT]=true; + else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_POST)==0) + LogFlags_[Offset+LOG_POST]=true; + else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_GET)==0) + LogFlags_[Offset+LOG_GET]=true; + } + } + + inline void InitLogging(); + + [[nodiscard]] inline bool LogIt(const std::string &Method, bool External) const { + auto Offset = (External ? 0 : 4); + if(Method == Poco::Net::HTTPRequest::HTTP_GET) + return LogFlags_[Offset+LOG_GET]; + if(Method == Poco::Net::HTTPRequest::HTTP_POST) + return LogFlags_[Offset+LOG_POST]; + if(Method == Poco::Net::HTTPRequest::HTTP_PUT) + return LogFlags_[Offset+LOG_PUT]; + if(Method == Poco::Net::HTTPRequest::HTTP_DELETE) + return LogFlags_[Offset+LOG_DELETE]; + return false; + }; + + [[nodiscard]] inline bool LogBadTokens(bool External) const { + return LogBadTokens_[ (External ? 0 : 1) ]; + }; + + private: + std::array LogFlags_{false}; + std::array LogBadTokens_{false}; + }; + + class RESTAPI_PartHandler: public Poco::Net::PartHandler + { + public: + RESTAPI_PartHandler(): + _length(0) + { + } + + inline 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]] inline int length() const + { + return _length; + } + + [[nodiscard]] inline const std::string& name() const + { + return _name; + } + + [[nodiscard]] inline const std::string& fileName() const + { + return _fileName; + } + + [[nodiscard]] inline const std::string& contentType() const + { + return _type; + } + + private: + int _length; + std::string _type; + std::string _name; + std::string _fileName; + }; + + class RESTAPI_RateLimiter : public SubSystemServer { + public: + + struct ClientCacheEntry { + int64_t Start=0; + int Count=0; + }; + + static auto instance() { + static auto instance_ = new RESTAPI_RateLimiter; + return instance_; + } + + inline int Start() final { return 0;}; + inline void Stop() final { }; + + inline bool IsRateLimited(const Poco::Net::HTTPServerRequest &R, int64_t Period, int64_t MaxCalls) { + Poco::URI uri(R.getURI()); + auto H = str_hash(uri.getPath() + R.clientAddress().host().toString()); + auto E = Cache_.get(H); + auto Now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + if(E.isNull()) { + Cache_.add(H,ClientCacheEntry{.Start=Now, .Count=1}); + return false; + } + if((Now-E->Start)Count++; + Cache_.update(H,E); + if(E->Count > MaxCalls) { + Logger().warning(Poco::format("RATE-LIMIT-EXCEEDED: from '%s'", R.clientAddress().toString())); + return true; + } + return false; + } + E->Start = Now; + E->Count = 1; + Cache_.update(H,E); + return false; + } + + inline void Clear() { + Cache_.clear(); + } + + private: + Poco::ExpireLRUCache Cache_{2048}; + std::hash str_hash; + + RESTAPI_RateLimiter() noexcept: + SubSystemServer("RateLimiter", "RATE-LIMITER", "rate.limiter") + { + } + + }; + + inline auto RESTAPI_RateLimiter() { return RESTAPI_RateLimiter::instance(); } + + 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; + std::vector Select; + bool Lifetime=false, LastOnly=false, Newest=false, CountOnly=false, AdditionalInfo=false; + }; + typedef std::map BindingMap; + + struct RateLimit { + int64_t Interval=1000; + int64_t MaxCalls=10; + }; + + RESTAPIHandler( BindingMap map, + Poco::Logger &l, + std::vector Methods, + RESTAPI_GenericServer & Server, + uint64_t TransactionId, + bool Internal=false, + bool AlwaysAuthorize=true, + bool RateLimited=false, + const RateLimit & Profile = RateLimit{.Interval=1000,.MaxCalls=100}, + bool SubscriberOnly=false) + : Bindings_(std::move(map)), + Logger_(l), + Methods_(std::move(Methods)), + Server_(Server), + TransactionId_(TransactionId), + Internal_(Internal), + AlwaysAuthorize_(AlwaysAuthorize), + RateLimited_(RateLimited), + MyRates_(Profile), + SubOnlyService_(SubscriberOnly) + { + } + + inline bool RoleIsAuthorized(const std::string & Path, const std::string & Method, std::string & Reason) { + return true; + } + + inline void handleRequest(Poco::Net::HTTPServerRequest &RequestIn, + Poco::Net::HTTPServerResponse &ResponseIn) final { + try { + Request = &RequestIn; + Response = &ResponseIn; + + Poco::Thread::current()->setName("WebServerThread_" + std::to_string(TransactionId_)); + + if(RateLimited_ && RESTAPI_RateLimiter()->IsRateLimited(RequestIn,MyRates_.Interval, MyRates_.MaxCalls)) { + return UnAuthorized("Rate limit exceeded.",RATE_LIMIT_EXCEEDED); + } + + if (!ContinueProcessing()) + return; + + bool Expired=false; + if (AlwaysAuthorize_ && !IsAuthorized(Expired, SubOnlyService_)) { + if(Expired) + return UnAuthorized(RESTAPI::Errors::ExpiredToken, EXPIRED_TOKEN); + return UnAuthorized(RESTAPI::Errors::InvalidCredentials, INVALID_TOKEN); + } + + std::string Reason; + if(!RoleIsAuthorized(RequestIn.getURI(), Request->getMethod(), Reason)) { + return UnAuthorized(Reason, ACCESS_DENIED); + } + + ParseParameters(); + if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_GET) + return DoGet(); + else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_POST) + return DoPost(); + else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_DELETE) + return DoDelete(); + else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_PUT) + return DoPut(); + return BadRequest(RESTAPI::Errors::UnsupportedHTTPMethod); + } catch (const Poco::Exception &E) { + Logger_.log(E); + return BadRequest(RESTAPI::Errors::InternalError); + } + } + + [[nodiscard]] inline bool NeedAdditionalInfo() const { return QB_.AdditionalInfo; } + [[nodiscard]] inline const std::vector & SelectedRecords() const { return QB_.Select; } + + [[nodiscard]] inline const Poco::JSON::Object::Ptr & ParseStream() { + return IncomingParser_.parse(Request->stream()).extract(); + } + + + inline static bool ParseBindings(const std::string & Request, const std::list & EndPoints, BindingMap &bindings) { + bindings.clear(); + std::vector PathItems = Utils::Split(Request, '/'); + + for(const auto &EndPoint:EndPoints) { + std::vector ParamItems = Utils::Split(EndPoint, '/'); + if (PathItems.size() != ParamItems.size()) + continue; + + bool Matched = true; + for (auto i = 0; i != PathItems.size() && Matched; i++) { + 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; + } + + inline void PrintBindings() { + for (const auto &[key, value] : Bindings_) + std::cout << "Key = " << key << " Value= " << value << std::endl; + } + + inline void ParseParameters() { + Poco::URI uri(Request->getURI()); + Parameters_ = uri.getQueryParameters(); + InitQueryBlock(); + } + + inline static bool is_number(const std::string &s) { + return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit); + } + + inline static bool is_bool(const std::string &s) { + if (s == "true" || s == "false") + return true; + return false; + } + + [[nodiscard]] inline uint64_t GetParameter(const std::string &Name, const uint64_t Default) { + auto Hint = std::find_if(Parameters_.begin(),Parameters_.end(),[Name](const std::pair &S){ return S.first==Name; }); + if(Hint==Parameters_.end() || !is_number(Hint->second)) + return Default; + return std::stoull(Hint->second); + } + + [[nodiscard]] inline bool GetBoolParameter(const std::string &Name, bool Default) { + auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair &S){ return S.first==Name; }); + if(Hint==end(Parameters_) || !is_bool(Hint->second)) + return Default; + return Hint->second=="true"; + } + + [[nodiscard]] inline std::string GetParameter(const std::string &Name, const std::string &Default) { + auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair &S){ return S.first==Name; }); + if(Hint==end(Parameters_)) + return Default; + return Hint->second; + } + + [[nodiscard]] inline bool HasParameter(const std::string &Name, std::string &Value) { + auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair &S){ return S.first==Name; }); + if(Hint==end(Parameters_)) + return false; + Value = Hint->second; + return true; + } + + [[nodiscard]] inline bool HasParameter(const std::string &Name, uint64_t & Value) { + auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair &S){ return S.first==Name; }); + if(Hint==end(Parameters_)) + return false; + Value = std::stoull(Hint->second); + return true; + } + + [[nodiscard]] inline const std::string & 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; + } + + [[nodiscard]] inline 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; + } + + static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, std::string &Value) { + if(O->has(Field)) { + Value = O->get(Field).toString(); + return true; + } + return false; + } + + static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, uint64_t &Value) { + if(O->has(Field)) { + Value = O->get(Field); + return true; + } + return false; + } + + static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, bool &Value) { + if(O->has(Field)) { + Value = O->get(Field).toString()=="true"; + return true; + } + return false; + } + + inline void AddCORS() { + 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"); + } + + inline void SetCommonHeaders(bool CloseConnection=false) { + 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"); + } + } + + inline void ProcessOptions() { + AddCORS(); + SetCommonHeaders(); + 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"); + Response->send(); + } + + inline void PrepareResponse(Poco::Net::HTTPResponse::HTTPStatus Status = Poco::Net::HTTPResponse::HTTP_OK, + bool CloseConnection = false) { + Response->setStatus(Status); + AddCORS(); + SetCommonHeaders(CloseConnection); + } + + inline void BadRequest(const std::string & Reason) { + PrepareResponse(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST); + Poco::JSON::Object ErrorObject; + ErrorObject.set("ErrorCode",400); + 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); + } + + inline void InternalError(const std::string & Reason = "") { + PrepareResponse(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR); + Poco::JSON::Object ErrorObject; + ErrorObject.set("ErrorCode",500); + ErrorObject.set("ErrorDetails",Request->getMethod()); + ErrorObject.set("ErrorDescription",Reason.empty() ? "Please try later or review the data submitted." : Reason) ; + std::ostream &Answer = Response->send(); + Poco::JSON::Stringifier::stringify(ErrorObject, Answer); + } + + inline void UnAuthorized(const std::string & Reason = "", int Code = INVALID_CREDENTIALS ) { + PrepareResponse(Poco::Net::HTTPResponse::HTTP_FORBIDDEN); + Poco::JSON::Object ErrorObject; + ErrorObject.set("ErrorCode",Code); + 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); + } + + inline void NotFound() { + PrepareResponse(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); + Logger_.debug(Poco::format("RES-NOTFOUND: User='%s@%s' Method='%s' Path='%s", + UserInfo_.userinfo.email, + Utils::FormatIPv6(Request->clientAddress().toString()), + Request->getMethod(), + Request->getURI())); + } + + inline void OK() { + PrepareResponse(); + 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); + } + } + + inline void SendCompressedTarFile(const std::string & FileName, const std::string & Content) { + Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); + AddCORS(); + Response->set("Content-Type","application/gzip"); + Response->set("Content-Disposition", "attachment; filename=" + FileName ); + 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->setStatus(Poco::Net::HTTPResponse::HTTP_OK); + Response->setContentLength(Content.size()); + Response->setChunkedTransferEncoding(true); + std::ostream& OutputStream = Response->send(); + OutputStream << Content; + } + + inline void SendFile(Poco::File & File, const std::string & UUID) { + Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); + AddCORS(); + 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->setContentLength(File.getSize()); + Response->sendFile(File.path(),"application/octet-stream"); + } + + inline void SendFile(Poco::File & File) { + Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); + AddCORS(); + 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"); + Response->sendFile(File.path(),MT.ContentType); + } + + inline void SendFile(Poco::TemporaryFile &TempAvatar, const std::string &Type, const std::string & Name) { + Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); + AddCORS(); + 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"); + Response->setContentLength(TempAvatar.getSize()); + Response->sendFile(TempAvatar.path(),MT.ContentType); + } + + inline void SendFileContent(const std::string &Content, const std::string &Type, const std::string & Name) { + Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); + AddCORS(); + 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"); + Response->setContentLength(Content.size()); + Response->setContentType(Type ); + auto & OutputStream = Response->send(); + OutputStream << Content ; + } + + inline void SendHTMLFileBack(Poco::File & File, + const Types::StringPairVec & FormVars) { + Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); + AddCORS(); + Response->set("Pragma", "private"); + Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT"); + std::string FormContent = Utils::LoadFile(File.path()); + Utils::ReplaceVariables(FormContent, FormVars); + Response->setContentLength(FormContent.size()); + Response->setChunkedTransferEncoding(true); + Response->setContentType("text/html"); + std::ostream& ostr = Response->send(); + ostr << FormContent; + } + + inline void ReturnStatus(Poco::Net::HTTPResponse::HTTPStatus Status, bool CloseConnection=false) { + PrepareResponse(Status, CloseConnection); + if(Status == Poco::Net::HTTPResponse::HTTP_NO_CONTENT) { + Response->setContentLength(0); + Response->erase("Content-Type"); + Response->setChunkedTransferEncoding(false); + } + Response->send(); + } + + inline bool ContinueProcessing() { + if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_OPTIONS) { + ProcessOptions(); + return false; + } else if (std::find(Methods_.begin(), Methods_.end(), Request->getMethod()) == Methods_.end()) { + BadRequest(RESTAPI::Errors::UnsupportedHTTPMethod); + return false; + } + + return true; + } + + inline bool IsAuthorized(bool & Expired, bool SubOnly = false ); + + inline void ReturnObject(Poco::JSON::Object &Object) { + PrepareResponse(); + std::ostream &Answer = Response->send(); + Poco::JSON::Stringifier::stringify(Object, Answer); + } + + inline void ReturnCountOnly(uint64_t Count) { + Poco::JSON::Object Answer; + Answer.set("count", Count); + ReturnObject(Answer); + } + + inline bool InitQueryBlock() { + if(QueryBlockInitialized_) + return true; + QueryBlockInitialized_=true; + QB_.SerialNumber = GetParameter(RESTAPI::Protocol::SERIALNUMBER, ""); + QB_.StartDate = GetParameter(RESTAPI::Protocol::STARTDATE, 0); + QB_.EndDate = GetParameter(RESTAPI::Protocol::ENDDATE, 0); + QB_.Offset = GetParameter(RESTAPI::Protocol::OFFSET, 0); + QB_.Limit = GetParameter(RESTAPI::Protocol::LIMIT, 100); + QB_.Filter = GetParameter(RESTAPI::Protocol::FILTER, ""); + QB_.Lifetime = GetBoolParameter(RESTAPI::Protocol::LIFETIME,false); + QB_.LogType = GetParameter(RESTAPI::Protocol::LOGTYPE,0); + QB_.LastOnly = GetBoolParameter(RESTAPI::Protocol::LASTONLY,false); + QB_.Newest = GetBoolParameter(RESTAPI::Protocol::NEWEST,false); + QB_.CountOnly = GetBoolParameter(RESTAPI::Protocol::COUNTONLY,false); + QB_.AdditionalInfo = GetBoolParameter(RESTAPI::Protocol::WITHEXTENDEDINFO,false); + + auto RawSelect = GetParameter(RESTAPI::Protocol::SELECT, ""); + + auto Entries = Poco::StringTokenizer(RawSelect,","); + for(const auto &i:Entries) + QB_.Select.emplace_back(i); + if(QB_.Offset<1) + QB_.Offset=0; + return true; + } + + [[nodiscard]] inline uint64_t Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default=0){ + if(Obj->has(Parameter)) + return Obj->get(Parameter); + return Default; + } + + [[nodiscard]] inline std::string 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]] inline bool GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default=false){ + if(Obj->has(Parameter)) + return Obj->get(Parameter).toString()=="true"; + return Default; + } + + [[nodiscard]] inline uint64_t GetWhen(const Poco::JSON::Object::Ptr &Obj) { + return RESTAPIHandler::Get(RESTAPI::Protocol::WHEN, Obj); + } + + template void ReturnObject(const char *Name, const std::vector & Objects) { + Poco::JSON::Object Answer; + RESTAPI_utils::field_to_json(Answer,Name,Objects); + ReturnObject(Answer); + } + + Poco::Logger & Logger() { return Logger_; } + + virtual void DoGet() = 0 ; + virtual void DoDelete() = 0 ; + virtual void DoPost() = 0 ; + virtual void DoPut() = 0 ; + + protected: + BindingMap Bindings_; + Poco::URI::QueryParameters Parameters_; + Poco::Logger &Logger_; + std::string SessionToken_; + SecurityObjects::UserInfoAndPolicy UserInfo_; + std::vector Methods_; + QueryBlock QB_; + bool Internal_=false; + bool RateLimited_=false; + bool QueryBlockInitialized_=false; + bool SubOnlyService_=false; + Poco::Net::HTTPServerRequest *Request= nullptr; + Poco::Net::HTTPServerResponse *Response= nullptr; + bool AlwaysAuthorize_=true; + Poco::JSON::Parser IncomingParser_; + RESTAPI_GenericServer & Server_; + RateLimit MyRates_; + uint64_t TransactionId_; + }; + + class RESTAPI_UnknownRequestHandler : public RESTAPIHandler { + public: + RESTAPI_UnknownRequestHandler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer & Server, uint64_t TransactionId) + : RESTAPIHandler(bindings, L, std::vector{}, Server, TransactionId) {} + inline void DoGet() override {}; + inline void DoPost() override {}; + inline void DoPut() override {}; + inline void DoDelete() override {}; + }; + + 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, RESTAPI_GenericServer & Server, uint64_t TransactionId) { + 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, Server, false, TransactionId); + } + + if constexpr (sizeof...(Args) == 0) { + return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server, TransactionId); + } else { + return RESTAPI_Router(RequestedPath, Bindings, Logger, Server, TransactionId); + } + } + + template + RESTAPIHandler * RESTAPI_Router_I(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings, + Poco::Logger & Logger, RESTAPI_GenericServer & Server, uint64_t TransactionId) { + 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, Server, true, TransactionId); + } + + if constexpr (sizeof...(Args) == 0) { + return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server, TransactionId); + } else { + return RESTAPI_Router_I(RequestedPath, Bindings, Logger, Server, TransactionId); + } + } + + class OpenAPIRequestGet { + public: + explicit OpenAPIRequestGet( const std::string & Type, + const std::string & EndPoint, + const Types::StringPairVec & QueryData, + uint64_t msTimeout): + Type_(Type), + EndPoint_(EndPoint), + QueryData_(QueryData), + msTimeout_(msTimeout) {}; + inline Poco::Net::HTTPServerResponse::HTTPStatus Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken = ""); + private: + std::string Type_; + std::string EndPoint_; + Types::StringPairVec QueryData_; + uint64_t msTimeout_; + }; + + class OpenAPIRequestPut { + public: + explicit OpenAPIRequestPut( const std::string & Type, + const std::string & EndPoint, + const Types::StringPairVec & QueryData, + const Poco::JSON::Object & Body, + uint64_t msTimeout): + Type_(Type), + EndPoint_(EndPoint), + QueryData_(QueryData), + msTimeout_(msTimeout), + Body_(Body){}; + + inline Poco::Net::HTTPServerResponse::HTTPStatus Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken = ""); + + private: + std::string Type_; + std::string EndPoint_; + Types::StringPairVec QueryData_; + uint64_t msTimeout_; + Poco::JSON::Object Body_; + }; + + class OpenAPIRequestPost { + public: + explicit OpenAPIRequestPost( const std::string & Type, + const std::string & EndPoint, + const Types::StringPairVec & QueryData, + const Poco::JSON::Object & Body, + uint64_t msTimeout): + Type_(Type), + EndPoint_(EndPoint), + QueryData_(QueryData), + msTimeout_(msTimeout), + Body_(Body){}; + inline Poco::Net::HTTPServerResponse::HTTPStatus Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken = ""); + private: + std::string Type_; + std::string EndPoint_; + Types::StringPairVec QueryData_; + uint64_t msTimeout_; + Poco::JSON::Object Body_; + }; + + class KafkaProducer : public Poco::Runnable { + public: + inline void run(); + void Start() { + if(!Running_) { + Running_=true; + Worker_.start(*this); + } + } + void Stop() { + if(Running_) { + Running_=false; + Worker_.wakeUp(); + Worker_.join(); + } + } + private: + std::mutex Mutex_; + Poco::Thread Worker_; + std::atomic_bool Running_=false; + }; + + class KafkaConsumer : public Poco::Runnable { + public: + inline void run(); + void Start() { + if(!Running_) { + Running_=true; + Worker_.start(*this); + } + } + void Stop() { + if(Running_) { + Running_=false; + Worker_.wakeUp(); + Worker_.join(); + } + } + private: + std::mutex Mutex_; + Poco::Thread Worker_; + std::atomic_bool Running_=false; + }; + + class KafkaManager : public SubSystemServer { + public: + struct KMessage { + std::string Topic, + Key, + PayLoad; + }; + + friend class KafkaConsumer; + friend class KafkaProducer; + + inline void initialize(Poco::Util::Application & self) override; + + static auto instance() { + static auto instance_ = new KafkaManager; + return instance_; + } + + inline int Start() override { + if(!KafkaEnabled_) + return 0; + ConsumerThr_.Start(); + ProducerThr_.Start(); + return 0; + } + + inline void Stop() override { + if(KafkaEnabled_) { + ProducerThr_.Stop(); + ConsumerThr_.Stop(); + return; + } + } + + inline void PostMessage(const std::string &topic, const std::string & key, const std::string &PayLoad, bool WrapMessage = true ) { + if(KafkaEnabled_) { + std::lock_guard G(Mutex_); + KMessage M{ + .Topic = topic, + .Key = key, + .PayLoad = WrapMessage ? WrapSystemId(PayLoad) : PayLoad }; + Queue_.push(M); + } + } + + [[nodiscard]] inline std::string WrapSystemId(const std::string & PayLoad) { + return std::move( SystemInfoWrapper_ + PayLoad + "}"); + } + + [[nodiscard]] inline bool Enabled() const { return KafkaEnabled_; } + + inline int RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F) { + if(KafkaEnabled_) { + std::lock_guard 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; + } + } + + inline void UnregisterTopicWatcher(const std::string &Topic, int Id) { + if(KafkaEnabled_) { + std::lock_guard 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; + } + } + } + } + + // void WakeUp(); + + private: + bool KafkaEnabled_ = false; + std::queue Queue_; + std::string SystemInfoWrapper_; + int FunctionId_=1; + Types::NotifyTable Notifiers_; + KafkaProducer ProducerThr_; + KafkaConsumer ConsumerThr_; + + inline void PartitionAssignment(const cppkafka::TopicPartitionList& partitions) { + Logger().information(Poco::format("Partition assigned: %Lu...",(uint64_t )partitions.front().get_partition())); + } + inline void PartitionRevocation(const cppkafka::TopicPartitionList& partitions) { + Logger().information(Poco::format("Partition revocation: %Lu...",(uint64_t )partitions.front().get_partition())); + } + + KafkaManager() noexcept: + SubSystemServer("KafkaManager", "KAFKA-SVR", "openwifi.kafka") + { + } + }; + + inline auto KafkaManager() { return KafkaManager::instance(); } + + class AuthClient : public SubSystemServer { + public: + explicit AuthClient() noexcept: + SubSystemServer("Authentication", "AUTH-CLNT", "authentication") + { + } + + static auto instance() { + static auto instance_ = new AuthClient; + return instance_; + } + + inline int Start() override { + return 0; + } + + inline void Stop() override { + std::lock_guard G(Mutex_); + Cache_.clear(); + } + + inline void RemovedCachedToken(const std::string &Token) { + std::lock_guard G(Mutex_); + Cache_.remove(Token); + } + + inline static bool IsTokenExpired(const SecurityObjects::WebToken &T) { + return ((T.expires_in_+T.created_)has("tokenInfo") && Response->has("userInfo")) { + UInfo.from_json(Response); + if(IsTokenExpired(UInfo.webtoken)) { + Expired = true; + return false; + } + Expired = false; + std::lock_guard G(Mutex_); + Cache_.update(SessionToken, UInfo); + return true; + } + } + } catch (...) { + + } + Expired = false; + return false; + } + + inline bool IsAuthorized(const std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, + bool & Expired, bool Sub = false) { + std::lock_guard G(Mutex_); + auto User = Cache_.get(SessionToken); + if(!User.isNull()) { + if(IsTokenExpired(User->webtoken)) { + Expired = true; + return false; + } + Expired = false; + UInfo = *User; + return true; + } + return RetrieveTokenInformation(SessionToken, UInfo, Expired, Sub); + } + + private: + Poco::ExpireLRUCache Cache_{512,1200000 }; + }; + + inline auto AuthClient() { return AuthClient::instance(); } + + class ALBRequestHandler: public Poco::Net::HTTPRequestHandler + /// Return a HTML document with the current date and time. + { + public: + explicit ALBRequestHandler(Poco::Logger & L) + : Logger_(L) + { + } + + void handleRequest(Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response) override + { + 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 auto instance() { + static auto instance = new ALBHealthCheckServer; + return instance; + } + + inline int Start() override; + + inline void Stop() override { + if(Running_) + Server_->stop(); + } + + private: + std::unique_ptr Server_; + std::unique_ptr Socket_; + int Port_ = 0; + std::atomic_bool Running_=false; + }; + + inline auto ALBHealthCheckServer() { return ALBHealthCheckServer::instance(); } + + Poco::Net::HTTPRequestHandler * RESTAPI_ExtRouter(const char *Path, RESTAPIHandler::BindingMap &Bindings, + Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t Id); + + Poco::Net::HTTPRequestHandler * RESTAPI_IntRouter(const char *Path, RESTAPIHandler::BindingMap &Bindings, + Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t Id); + + + class RESTAPI_ExtServer : public SubSystemServer { + public: + static auto instance() { + static auto instance_ = new RESTAPI_ExtServer; + return instance_; + } + int Start() override; + inline void Stop() override { + Logger().information("Stopping "); + for( const auto & svr : RESTServers_ ) + svr->stop(); + Pool_.stopAll(); + Pool_.joinAll(); + RESTServers_.clear(); + } + + inline void reinitialize(Poco::Util::Application &self) override; + + inline Poco::Net::HTTPRequestHandler *CallServer(const char *Path, uint64_t Id) { + RESTAPIHandler::BindingMap Bindings; + return RESTAPI_ExtRouter(Path, Bindings, Logger(), Server_, Id); + } + + private: + std::vector> RESTServers_; + Poco::ThreadPool Pool_; + RESTAPI_GenericServer Server_; + + RESTAPI_ExtServer() noexcept: + SubSystemServer("RESTAPI_ExtServer", "RESTAPIServer", "openwifi.restapi"), + Pool_("RESTAPI_ExternalPool") + { + } + }; + + inline auto RESTAPI_ExtServer() { return RESTAPI_ExtServer::instance(); }; + + class ExtRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory { + public: + explicit ExtRequestHandlerFactory(RESTAPI_GenericServer & Server) : + Logger_(RESTAPI_ExtServer::instance()->Logger()), + Server_(Server) + { + + } + + inline Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &Request) override { + try { + Poco::URI uri(Request.getURI()); + auto *Path = uri.getPath().c_str(); + Poco::Thread::current()->setName("ExtWebServer_" + std::to_string(TransactionId_)); + return RESTAPI_ExtServer()->CallServer(Path, TransactionId_++); + } catch (...) { + + } + return nullptr; + } + + private: + static inline std::atomic_uint64_t TransactionId_ = 1; + Poco::Logger &Logger_; + RESTAPI_GenericServer &Server_; + }; + + inline int RESTAPI_ExtServer::Start() { + Server_.InitLogging(); + + 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()); + + Poco::Net::HTTPServerParams::Ptr Params = new Poco::Net::HTTPServerParams; + Params->setMaxThreads(50); + Params->setMaxQueued(200); + Params->setKeepAlive(true); + + auto NewServer = std::make_unique(new ExtRequestHandlerFactory(Server_), Pool_, Sock, Params); + NewServer->start(); + RESTServers_.push_back(std::move(NewServer)); + } + + return 0; + } + + class RESTAPI_IntServer : public SubSystemServer { + + public: + static auto instance() { + static auto instance_ = new RESTAPI_IntServer; + return instance_; + } + + inline int Start() override; + inline void Stop() override { + Logger().information("Stopping "); + for( const auto & svr : RESTServers_ ) + svr->stop(); + Pool_.stopAll(); + Pool_.joinAll(); + } + + inline void reinitialize(Poco::Util::Application &self) override; + + inline Poco::Net::HTTPRequestHandler *CallServer(const char *Path, uint64_t Id) { + RESTAPIHandler::BindingMap Bindings; + return RESTAPI_IntRouter(Path, Bindings, Logger(), Server_, Id); + } + private: + std::vector> RESTServers_; + Poco::ThreadPool Pool_; + RESTAPI_GenericServer Server_; + + RESTAPI_IntServer() noexcept: + SubSystemServer("RESTAPI_IntServer", "REST-ISRV", "openwifi.internal.restapi"), + Pool_("RESTAPI_IntServerPool") + { + } + }; + + inline auto RESTAPI_IntServer() { return RESTAPI_IntServer::instance(); }; + + class IntRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory { + public: + explicit IntRequestHandlerFactory(RESTAPI_GenericServer & Server) : + Logger_(RESTAPI_IntServer()->Logger()), + Server_(Server){} + + inline Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &Request) override { + Poco::URI uri(Request.getURI()); + auto *Path = uri.getPath().c_str(); + return RESTAPI_IntServer()->CallServer(Path, TransactionId_); + } + private: + static inline std::atomic_uint64_t TransactionId_ = 1; + Poco::Logger &Logger_; + RESTAPI_GenericServer &Server_; + }; + + inline int RESTAPI_IntServer::Start() { + Logger().information("Starting."); + Server_.InitLogging(); + + 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 IntRequestHandlerFactory(Server_), Pool_, Sock, Params); + NewServer->start(); + RESTServers_.push_back(std::move(NewServer)); + } + + return 0; + } + + 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; + }; + + class SubSystemServer; + typedef std::map MicroServiceMetaMap; + typedef std::vector MicroServiceMetaVec; + typedef std::vector SubSystemVec; + + class MicroService : public Poco::Util::ServerApplication { + public: + explicit MicroService( std::string PropFile, + std::string RootEnv, + std::string ConfigVar, + std::string AppName, + uint64_t BusTimer, + 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)) { + instance_ = this; + RandomEngine_.seed(std::chrono::steady_clock::now().time_since_epoch().count()); + } + + [[nodiscard]] std::string Version() { return Version_; } + [[nodiscard]] const Poco::SharedPtr & Key() { return AppKey_; } + [[nodiscard]] inline const std::string & DataDir() { return DataDir_; } + [[nodiscard]] inline const std::string & WWWAssetsDir() { return WWWAssetsDir_; } + [[nodiscard]] bool Debug() const { return DebugMode_; } + [[nodiscard]] uint64_t ID() const { return ID_; } + [[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]] const SubSystemVec & GetFullSubSystems() { return SubSystems_; } + inline uint64_t DaemonBusTimer() const { return DAEMON_BUS_TIMER; }; + [[nodiscard]] const std::string & AppName() { return DAEMON_APP_NAME; } + static inline uint64_t GetPID() { return Poco::Process::id(); }; + [[nodiscard]] inline const std::string GetPublicAPIEndPoint() { return MyPublicEndPoint_ + "/api/v1"; }; + [[nodiscard]] inline const std::string & GetUIURI() const { return UIURI_;}; + [[nodiscard]] inline uint64_t Random(uint64_t ceiling) { + return (RandomEngine_() % ceiling); + } + + [[nodiscard]] inline uint64_t Random(uint64_t min, uint64_t max) { + return ((RandomEngine_() % (max-min)) + min); + } + + inline Poco::Logger & GetLogger(const std::string &Name) { + static auto initilized = false; + + if(!initilized) { + initilized = true; + InitializeLoggingSystem(); + } + return Poco::Logger::get(Name); + } + + static inline void Exit(int Reason); + inline void BusMessageReceived(const std::string &Key, const std::string & Message); + inline MicroServiceMetaVec GetServices(const std::string & Type); + inline MicroServiceMetaVec GetServices(); + inline void LoadConfigurationFile(); + inline void Reload(); + inline void LoadMyConfig(); + inline void initialize(Poco::Util::Application &self) override; + inline void uninitialize() override; + inline void reinitialize(Poco::Util::Application &self) override; + inline void defineOptions(Poco::Util::OptionSet &options) override; + inline void handleHelp(const std::string &name, const std::string &value); + inline void handleVersion(const std::string &name, const std::string &value); + inline void handleDebug(const std::string &name, const std::string &value); + inline void handleLogs(const std::string &name, const std::string &value); + inline void handleConfig(const std::string &name, const std::string &value); + inline void displayHelp(); + inline void InitializeSubSystemServers(); + inline void StartSubSystemServers(); + inline void StopSubSystemServers(); + [[nodiscard]] static inline std::string CreateUUID(); + inline bool SetSubsystemLogLevel(const std::string &SubSystem, const std::string &Level); + inline void Reload(const std::string &Sub); + inline Types::StringVec GetSubSystems() const; + inline Types::StringPairVec GetLogLevels(); + inline const Types::StringVec & GetLogLevelNames(); + inline uint64_t ConfigGetInt(const std::string &Key,uint64_t Default); + inline uint64_t ConfigGetInt(const std::string &Key); + inline uint64_t ConfigGetBool(const std::string &Key,bool Default); + inline uint64_t ConfigGetBool(const std::string &Key); + inline std::string ConfigGetString(const std::string &Key,const std::string & Default); + inline std::string ConfigGetString(const std::string &Key); + inline std::string ConfigPath(const std::string &Key,const std::string & Default); + inline std::string ConfigPath(const std::string &Key); + inline std::string Encrypt(const std::string &S); + inline std::string Decrypt(const std::string &S); + inline std::string CreateHash(const std::string &S); + inline std::string MakeSystemEventMessage( const std::string & Type ) const; + [[nodiscard]] inline bool IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request); + inline static void SavePID(); + inline int main(const ArgVec &args) override; + static MicroService & instance() { return *instance_; } + inline void InitializeLoggingSystem(); + inline void SaveConfig() { PropConfigurationFile_->save(ConfigFileName_); } + inline auto UpdateConfig() { return PropConfigurationFile_; } + private: + static MicroService * instance_; + bool HelpRequested_ = false; + std::string LogDir_; + std::string ConfigFileName_; + Poco::UUIDGenerator UUIDGenerator_; + uint64_t ID_ = 1; + Poco::SharedPtr AppKey_; + bool DebugMode_ = false; + std::string DataDir_; + std::string WWWAssetsDir_; + 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_{ OW_VERSION::VERSION + "("+ OW_VERSION::BUILD + ")" + " - " + OW_VERSION::HASH }; + BusEventManager BusEventManager_; + std::mutex InfraMutex_; + std::default_random_engine RandomEngine_; + Poco::Util::PropertyFileConfiguration * PropConfigurationFile_ = nullptr; + 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; + }; + + inline void MicroService::Exit(int Reason) { + std::exit(Reason); + } + + inline void MicroService::BusMessageReceived(const std::string &Key, const std::string & Message) { + std::lock_guard 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."); + } + + auto i=Services_.begin(); + auto Now = (uint64_t )std::time(nullptr); + for(;i!=Services_.end();) { + if((Now - i->second.LastUpdate)>60) { + i = Services_.erase(i); + } else + ++i; + } + + } catch (const Poco::Exception &E) { + logger().log(E); + } + } + + inline MicroServiceMetaVec MicroService::GetServices(const std::string & Type) { + std::lock_guard 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; + } + + inline MicroServiceMetaVec MicroService::GetServices() { + std::lock_guard G(InfraMutex_); + + MicroServiceMetaVec Res; + for(const auto &[Id,ServiceRec]:Services_) { + Res.push_back(ServiceRec); + } + return Res; + } + + inline void MicroService::LoadConfigurationFile() { + std::string Location = Poco::Environment::get(DAEMON_CONFIG_ENV_VAR,"."); + ConfigFileName_ = ConfigFileName_.empty() ? Location + "/" + DAEMON_PROPERTIES_FILENAME : ConfigFileName_; + Poco::Path ConfigFile(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); + } + + // loadConfiguration(ConfigFile.toString()); + PropConfigurationFile_ = new Poco::Util::PropertyFileConfiguration(ConfigFile.toString()); + configPtr()->addWriteable(PropConfigurationFile_, PRIO_DEFAULT); + } + + inline void MicroService::Reload() { + LoadConfigurationFile(); + LoadMyConfig(); + } + + inline void MicroService::LoadMyConfig() { + std::string KeyFile = ConfigPath("openwifi.service.key"); + std::string KeyFilePassword = ConfigPath("openwifi.service.key.password" , "" ); + AppKey_ = Poco::SharedPtr(new Poco::Crypto::RSAKey("", KeyFile, KeyFilePassword)); + Cipher_ = CipherFactory_.createCipher(*AppKey_); + ID_ = Utils::GetSystemId(); + if(!DebugMode_) + DebugMode_ = ConfigGetBool("openwifi.system.debug",false); + MyPrivateEndPoint_ = ConfigGetString("openwifi.system.uri.private"); + MyPublicEndPoint_ = ConfigGetString("openwifi.system.uri.public"); + UIURI_ = ConfigGetString("openwifi.system.uri.ui"); + MyHash_ = CreateHash(MyPublicEndPoint_); + } + + void MicroServicePostInitialization(); + + inline void MicroService::InitializeLoggingSystem() { + static auto initialized = false; + + if(!initialized) { + initialized = true; + LoadConfigurationFile(); + + auto LoggingDestination = MicroService::instance().ConfigGetString("logging.type", "file"); + auto LoggingFormat = MicroService::instance().ConfigGetString("logging.format", + "%Y-%m-%d %H:%M:%S %s: [%p] %t"); + if (LoggingDestination == "console") { + Poco::AutoPtr Console(new Poco::ConsoleChannel); + Poco::AutoPtr Formatter(new Poco::PatternFormatter); + Formatter->setProperty("pattern", LoggingFormat); + Poco::AutoPtr FormattingChannel( + new Poco::FormattingChannel(Formatter, Console)); + Poco::Logger::root().setChannel(FormattingChannel); + } else if (LoggingDestination == "colorconsole") { + Poco::AutoPtr Console(new Poco::ColorConsoleChannel); + Poco::AutoPtr Formatter(new Poco::PatternFormatter); + Formatter->setProperty("pattern", LoggingFormat); + Poco::AutoPtr FormattingChannel( + new Poco::FormattingChannel(Formatter, Console)); + Poco::Logger::root().setChannel(FormattingChannel); + } else if (LoggingDestination == "sql") { + + } else if (LoggingDestination == "syslog") { + //"CREATE TABLE T_POCO_LOG (Source VARCHAR, Name VARCHAR, ProcessId INTEGER, Thread VARCHAR, ThreadId INTEGER, Priority INTEGER, Text VARCHAR, DateTime DATE)" + } else { + auto LoggingLocation = + MicroService::instance().ConfigPath("logging.path", "$OWCERT_ROOT/logs") + "/log"; + + Poco::AutoPtr FileChannel(new Poco::FileChannel); + FileChannel->setProperty("rotation", "10 M"); + FileChannel->setProperty("archive", "timestamp"); + FileChannel->setProperty("path", LoggingLocation); + Poco::AutoPtr Formatter(new Poco::PatternFormatter); + Formatter->setProperty("pattern", LoggingFormat); + Poco::AutoPtr FormattingChannel( + new Poco::FormattingChannel(Formatter, FileChannel)); + Poco::Logger::root().setChannel(FormattingChannel); + } + auto Level = Poco::Logger::parseLevel(MicroService::instance().ConfigGetString("logging.level", "debug")); + Poco::Logger::root().setLevel(Level); + } + } + + inline void MicroService::initialize(Poco::Util::Application &self) { + // add the default services + LoadConfigurationFile(); + InitializeLoggingSystem(); + + SubSystems_.push_back(KafkaManager()); + SubSystems_.push_back(ALBHealthCheckServer()); + SubSystems_.push_back(RESTAPI_ExtServer()); + SubSystems_.push_back(RESTAPI_IntServer()); + + Poco::Net::initializeSSL(); + Poco::Net::HTTPStreamFactory::registerFactory(); + Poco::Net::HTTPSStreamFactory::registerFactory(); + Poco::Net::FTPStreamFactory::registerFactory(); + Poco::Net::FTPSStreamFactory::registerFactory(); + + Poco::File DataDir(ConfigPath("openwifi.system.data")); + DataDir_ = DataDir.path(); + if(!DataDir.exists()) { + try { + DataDir.createDirectory(); + } catch (const Poco::Exception &E) { + logger().log(E); + } + } + WWWAssetsDir_ = ConfigPath("openwifi.restapi.wwwassets",""); + if(WWWAssetsDir_.empty()) + WWWAssetsDir_ = DataDir_; + + LoadMyConfig(); + + InitializeSubSystemServers(); + ServerApplication::initialize(self); + + Types::TopicNotifyFunction F = [this](std::string s1,std::string s2) { this->BusMessageReceived(s1,s2); }; + KafkaManager()->RegisterTopicWatcher(KafkaTopics::SERVICE_EVENTS, F); + + MicroServicePostInitialization(); + } + + inline void MicroService::uninitialize() { + // add your own uninitialization code here + ServerApplication::uninitialize(); + } + + inline void MicroService::reinitialize(Poco::Util::Application &self) { + ServerApplication::reinitialize(self); + // add your own reinitialization code here + } + + inline 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))); + + } + + inline void MicroService::handleHelp(const std::string &name, const std::string &value) { + HelpRequested_ = true; + displayHelp(); + stopOptionsProcessing(); + } + + inline void MicroService::handleVersion(const std::string &name, const std::string &value) { + HelpRequested_ = true; + std::cout << Version() << std::endl; + stopOptionsProcessing(); + } + + inline void MicroService::handleDebug(const std::string &name, const std::string &value) { + if(value == "true") + DebugMode_ = true ; + } + + inline void MicroService::handleLogs(const std::string &name, const std::string &value) { + LogDir_ = value; + } + + inline void MicroService::handleConfig(const std::string &name, const std::string &value) { + ConfigFileName_ = value; + } + + inline 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); + } + + inline void MicroService::InitializeSubSystemServers() { + for(auto i:SubSystems_) + addSubsystem(i); + } + + inline void MicroService::StartSubSystemServers() { + for(auto i:SubSystems_) { + i->Start(); + } + BusEventManager_.Start(); + } + + inline void MicroService::StopSubSystemServers() { + BusEventManager_.Stop(); + for(auto i=SubSystems_.rbegin(); i!=SubSystems_.rend(); ++i) { + (*i)->Stop(); + } + } + + [[nodiscard]] inline std::string MicroService::CreateUUID() { + static std::random_device rd; + static std::mt19937_64 gen(rd()); + static std::uniform_int_distribution<> dis(0, 15); + static std::uniform_int_distribution<> dis2(8, 11); + + std::stringstream ss; + int i; + ss << std::hex; + for (i = 0; i < 8; i++) { + ss << dis(gen); + } + ss << "-"; + for (i = 0; i < 4; i++) { + ss << dis(gen); + } + ss << "-4"; + for (i = 0; i < 3; i++) { + ss << dis(gen); + } + ss << "-"; + ss << dis2(gen); + for (i = 0; i < 3; i++) { + ss << dis(gen); + } + ss << "-"; + for (i = 0; i < 12; i++) { + ss << dis(gen); + }; + return ss.str(); + } + + inline 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 { + 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; + } + + inline void MicroService::Reload(const std::string &Sub) { + for (auto i : SubSystems_) { + if (Poco::toLower(Sub) == Poco::toLower(i->Name())) { + i->reinitialize(Poco::Util::Application::instance()); + return; + } + } + } + + inline Types::StringVec MicroService::GetSubSystems() const { + Types::StringVec Result; + for(auto i:SubSystems_) + Result.push_back(Poco::toLower(i->Name())); + return Result; + } + + inline Types::StringPairVec MicroService::GetLogLevels() { + Types::StringPairVec Result; + + for(auto &i:SubSystems_) { + auto P = std::make_pair( i->Name(), Utils::LogLevelToString(i->GetLoggingLevel())); + Result.push_back(P); + } + return Result; + } + + inline const Types::StringVec & MicroService::GetLogLevelNames() { + static Types::StringVec LevelNames{"none", "fatal", "critical", "error", "warning", "notice", "information", "debug", "trace" }; + return LevelNames; + } + + inline uint64_t MicroService::ConfigGetInt(const std::string &Key,uint64_t Default) { + return (uint64_t) config().getInt64(Key,Default); + } + + inline uint64_t MicroService::ConfigGetInt(const std::string &Key) { + return config().getInt(Key); + } + + inline uint64_t MicroService::ConfigGetBool(const std::string &Key,bool Default) { + return config().getBool(Key,Default); + } + + inline uint64_t MicroService::ConfigGetBool(const std::string &Key) { + return config().getBool(Key); + } + + inline std::string MicroService::ConfigGetString(const std::string &Key,const std::string & Default) { + return config().getString(Key, Default); + } + + inline std::string MicroService::ConfigGetString(const std::string &Key) { + return config().getString(Key); + } + + inline std::string MicroService::ConfigPath(const std::string &Key,const std::string & Default) { + std::string R = config().getString(Key, Default); + return Poco::Path::expand(R); + } + + inline std::string MicroService::ConfigPath(const std::string &Key) { + std::string R = config().getString(Key); + return Poco::Path::expand(R); + } + + inline std::string MicroService::Encrypt(const std::string &S) { + return Cipher_->encryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);; + } + + inline std::string MicroService::Decrypt(const std::string &S) { + return Cipher_->decryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);; + } + + inline std::string MicroService::CreateHash(const std::string &S) { + SHA2_.update(S); + return Utils::ToHex(SHA2_.digest()); + } + + inline 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(); + } + + [[nodiscard]] inline 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; + } + + inline void MicroService::SavePID() { + try { + std::ofstream O; + O.open(MicroService::instance().DataDir() + "/pidfile",std::ios::binary | std::ios::trunc); + O << Poco::Process::id(); + O.close(); + } catch (...) + { + std::cout << "Could not save system ID" << std::endl; + } + } + + inline SubSystemServer::SubSystemServer(std::string Name, const std::string &LoggingPrefix, + std::string SubSystemConfigPrefix): + Name_(std::move(Name)), + LoggerPrefix_(LoggingPrefix), + SubSystemConfigPrefix_(std::move(SubSystemConfigPrefix)) { + } + + inline 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; + } + + AppServiceRegistry::AppServiceRegistry() { + FileName = MicroService::instance().DataDir() + "/registry.json"; + Poco::File F(FileName); + + try { + if(F.exists()) { + std::ostringstream OS; + std::ifstream IF(FileName); + Poco::StreamCopier::copyStream(IF, OS); + Registry_ = nlohmann::json::parse(OS.str()); + } + } catch (...) { + Registry_ = nlohmann::json::parse("{}"); + } + } + + inline void SubSystemServer::initialize(Poco::Util::Application &self) { + auto i = 0; + bool good = true; + + Log_ = std::make_unique(Poco::Logger::get(LoggerPrefix_)); + + ConfigServersList_.clear(); + while (good) { + std::string root{SubSystemConfigPrefix_ + ".host." + std::to_string(i) + "."}; + + std::string address{root + "address"}; + if (MicroService::instance().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 = MicroService::instance().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(MicroService::instance().ConfigGetString(address, ""), + MicroService::instance().ConfigGetInt(port, 0), + MicroService::instance().ConfigPath(key, ""), + MicroService::instance().ConfigPath(cert, ""), + MicroService::instance().ConfigPath(rootca, ""), + MicroService::instance().ConfigPath(issuer, ""), + MicroService::instance().ConfigPath(clientcas, ""), + MicroService::instance().ConfigPath(cas, ""), + MicroService::instance().ConfigGetString(key_password, ""), + MicroService::instance().ConfigGetString(name, ""), M, + (int)MicroService::instance().ConfigGetInt(backlog, 64)); + ConfigServersList_.push_back(entry); + i++; + } + } + } + + inline int ALBHealthCheckServer::Start() { + if(MicroService::instance().ConfigGetBool("alb.enable",false)) { + Running_=true; + Port_ = (int)MicroService::instance().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; + } + + inline void BusEventManager::run() { + Running_ = true; + auto Msg = MicroService::instance().MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_JOIN); + KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,MicroService::instance().PrivateEndPoint(),Msg, false); + while(Running_) { + Poco::Thread::trySleep((unsigned long)MicroService::instance().DaemonBusTimer()); + if(!Running_) + break; + Msg = MicroService::instance().MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE); + KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,MicroService::instance().PrivateEndPoint(),Msg, false); + } + Msg = MicroService::instance().MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_LEAVE); + KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,MicroService::instance().PrivateEndPoint(),Msg, false); + }; + + inline void BusEventManager::Start() { + if(KafkaManager()->Enabled()) { + Thread_.start(*this); + } + } + + inline void BusEventManager::Stop() { + if(KafkaManager()->Enabled()) { + Running_ = false; + Thread_.wakeUp(); + Thread_.join(); + } + } + + inline void KafkaManager::initialize(Poco::Util::Application & self) { + SubSystemServer::initialize(self); + KafkaEnabled_ = MicroService::instance().ConfigGetBool("openwifi.kafka.enable",false); + } + + inline void KafkaProducer::run() { + cppkafka::Configuration Config({ + { "client.id", MicroService::instance().ConfigGetString("openwifi.kafka.client.id") }, + { "metadata.broker.list", MicroService::instance().ConfigGetString("openwifi.kafka.brokerlist") } + }); + KafkaManager()->SystemInfoWrapper_ = R"lit({ "system" : { "id" : )lit" + + std::to_string(MicroService::instance().ID()) + + R"lit( , "host" : ")lit" + MicroService::instance().PrivateEndPoint() + + R"lit(" } , "payload" : )lit" ; + cppkafka::Producer Producer(Config); + Running_ = true; + while(Running_) { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + try + { + std::lock_guard G(Mutex_); + auto Num=0; + while (!KafkaManager()->Queue_.empty()) { + const auto M = KafkaManager()->Queue_.front(); + Producer.produce( + cppkafka::MessageBuilder(M.Topic).key(M.Key).payload(M.PayLoad)); + KafkaManager()->Queue_.pop(); + Num++; + } + if(Num) + Producer.flush(); + } catch (const cppkafka::HandleException &E ) { + KafkaManager()->Logger().warning(Poco::format("Caught a Kafka exception (producer): %s",std::string{E.what()})); + } catch (const Poco::Exception &E) { + KafkaManager()->Logger().log(E); + } + } + Producer.flush(); + } + + inline void KafkaConsumer::run() { + cppkafka::Configuration Config({ + { "client.id", MicroService::instance().ConfigGetString("openwifi.kafka.client.id") }, + { "metadata.broker.list", MicroService::instance().ConfigGetString("openwifi.kafka.brokerlist") }, + { "group.id", MicroService::instance().ConfigGetString("openwifi.kafka.group.id") }, + { "enable.auto.commit", MicroService::instance().ConfigGetBool("openwifi.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()) { + KafkaManager()->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()) { + KafkaManager()->Logger().information(Poco::format("Partition revocation: %Lu...", + (uint64_t)partitions.front().get_partition())); + } + }); + + bool AutoCommit = MicroService::instance().ConfigGetBool("openwifi.kafka.auto.commit",false); + auto BatchSize = MicroService::instance().ConfigGetInt("openwifi.kafka.consumer.batchsize",20); + + Types::StringVec Topics; + for(const auto &i:KafkaManager()->Notifiers_) + Topics.push_back(i.first); + + Consumer.subscribe(Topics); + + Running_ = true; + while(Running_) { + 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()) { + KafkaManager()->Logger().error(Poco::format("Error: %s", Msg.get_error().to_string())); + }if(!AutoCommit) + Consumer.async_commit(Msg); + continue; + } + std::lock_guard G(Mutex_); + auto It = KafkaManager()->Notifiers_.find(Msg.get_topic()); + if (It != KafkaManager()->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) { + KafkaManager()->Logger().warning(Poco::format("Caught a Kafka exception (consumer): %s",std::string{E.what()})); + } catch (const Poco::Exception &E) { + KafkaManager()->Logger().log(E); + } + } + Consumer.unsubscribe(); + } + + inline void RESTAPI_ExtServer::reinitialize(Poco::Util::Application &self) { + MicroService::instance().LoadConfigurationFile(); + Logger().information("Reinitializing."); + Stop(); + Start(); + } + + void RESTAPI_IntServer::reinitialize(Poco::Util::Application &self) { + MicroService::instance().LoadConfigurationFile(); + Logger().information("Reinitializing."); + Stop(); + Start(); + } + + class RESTAPI_system_command : public RESTAPIHandler { + public: + RESTAPI_system_command(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer & Server, uint64_t TransactionId, bool Internal) + : RESTAPIHandler(bindings, L, + std::vector{Poco::Net::HTTPRequest::HTTP_POST, + Poco::Net::HTTPRequest::HTTP_GET, + Poco::Net::HTTPRequest::HTTP_OPTIONS}, + Server, + TransactionId, + Internal) {} + static const std::list PathName() { return std::list{"/api/v1/system"};} + + inline void DoGet() { + std::string Arg; + if(HasParameter("command",Arg) && Arg=="info") { + Poco::JSON::Object Answer; + Answer.set(RESTAPI::Protocol::VERSION, MicroService::instance().Version()); + Answer.set(RESTAPI::Protocol::UPTIME, MicroService::instance().uptime().totalSeconds()); + Answer.set(RESTAPI::Protocol::START, MicroService::instance().startTime().epochTime()); + Answer.set(RESTAPI::Protocol::OS, Poco::Environment::osName()); + Answer.set(RESTAPI::Protocol::PROCESSORS, Poco::Environment::processorCount()); + Answer.set(RESTAPI::Protocol::HOSTNAME, Poco::Environment::nodeName()); + Answer.set(RESTAPI::Protocol::UI, MicroService::instance().GetUIURI()); + + Poco::JSON::Array Certificates; + auto SubSystems = MicroService::instance().GetFullSubSystems(); + std::set CertNames; + + for(const auto &i:SubSystems) { + auto Hosts=i->HostSize(); + for(uint64_t j=0;jHost(j).CertFile(); + if(!CertFileName.empty()) { + auto InsertResult = CertNames.insert(CertFileName); + if(InsertResult.second) { + Poco::JSON::Object Inner; + Poco::Path F(CertFileName); + Inner.set("filename", F.getFileName()); + Poco::Crypto::X509Certificate C(CertFileName); + auto ExpiresOn = C.expiresOn(); + Inner.set("expiresOn",ExpiresOn.timestamp().epochTime()); + Certificates.add(Inner); + } + } + } + } + Answer.set("certificates", Certificates); + return ReturnObject(Answer); + } + BadRequest(RESTAPI::Errors::InvalidCommand); + } + + inline void DoPost() final { + auto Obj = ParseStream(); + if (Obj->has(RESTAPI::Protocol::COMMAND)) { + auto Command = Poco::toLower(Obj->get(RESTAPI::Protocol::COMMAND).toString()); + if (Command == RESTAPI::Protocol::SETLOGLEVEL) { + if (Obj->has(RESTAPI::Protocol::SUBSYSTEMS) && + Obj->isArray(RESTAPI::Protocol::SUBSYSTEMS)) { + auto ParametersBlock = Obj->getArray(RESTAPI::Protocol::SUBSYSTEMS); + for (const auto &i : *ParametersBlock) { + Poco::JSON::Parser pp; + auto InnerObj = pp.parse(i).extract(); + if (InnerObj->has(RESTAPI::Protocol::TAG) && + InnerObj->has(RESTAPI::Protocol::VALUE)) { + auto Name = GetS(RESTAPI::Protocol::TAG, InnerObj); + auto Value = GetS(RESTAPI::Protocol::VALUE, InnerObj); + MicroService::instance().SetSubsystemLogLevel(Name, Value); + Logger_.information( + Poco::format("Setting log level for %s at %s", Name, Value)); + } + } + return OK(); + } + } else if (Command == RESTAPI::Protocol::GETLOGLEVELS) { + auto CurrentLogLevels = MicroService::instance().GetLogLevels(); + Poco::JSON::Object Result; + Poco::JSON::Array Array; + for (auto &[Name, Level] : CurrentLogLevels) { + Poco::JSON::Object Pair; + Pair.set(RESTAPI::Protocol::TAG, Name); + Pair.set(RESTAPI::Protocol::VALUE, Level); + Array.add(Pair); + } + Result.set(RESTAPI::Protocol::TAGLIST, Array); + return ReturnObject(Result); + } else if (Command == RESTAPI::Protocol::GETLOGLEVELNAMES) { + Poco::JSON::Object Result; + Poco::JSON::Array LevelNamesArray; + const Types::StringVec &LevelNames = MicroService::instance().GetLogLevelNames(); + for (const auto &i : LevelNames) + LevelNamesArray.add(i); + Result.set(RESTAPI::Protocol::LIST, LevelNamesArray); + return ReturnObject(Result); + } else if (Command == RESTAPI::Protocol::GETSUBSYSTEMNAMES) { + Poco::JSON::Object Result; + Poco::JSON::Array LevelNamesArray; + const Types::StringVec &SubSystemNames = MicroService::instance().GetSubSystems(); + for (const auto &i : SubSystemNames) + LevelNamesArray.add(i); + Result.set(RESTAPI::Protocol::LIST, LevelNamesArray); + return ReturnObject(Result); + } else if (Command == RESTAPI::Protocol::STATS) { + + } else if (Command == RESTAPI::Protocol::RELOAD) { + if (Obj->has(RESTAPI::Protocol::SUBSYSTEMS) && + Obj->isArray(RESTAPI::Protocol::SUBSYSTEMS)) { + auto SubSystems = Obj->getArray(RESTAPI::Protocol::SUBSYSTEMS); + std::vector Names; + for (const auto &i : *SubSystems) + Names.push_back(i.toString()); + std::thread ReloadThread([Names](){ + std::this_thread::sleep_for(10000ms); + for(const auto &i:Names) { + if(i=="daemon") + MicroService::instance().Reload(); + else + MicroService::instance().Reload(i); + } + }); + ReloadThread.detach(); + } + return OK(); + } + } else { + return BadRequest(RESTAPI::Errors::InvalidCommand); + } + BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); + } + + void DoPut() final {}; + void DoDelete() final {}; + }; + + inline Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestGet::Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken) { + try { + auto Services = MicroService::instance().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(msTimeout_/1000, msTimeout_ % 1000)); + + Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_GET, + Path, + Poco::Net::HTTPMessage::HTTP_1_1); + + if(BearerToken.empty()) { + Request.add("X-API-KEY", Svc.AccessKey); + Request.add("X-INTERNAL-NAME", MicroService::instance().PublicEndPoint()); + } else { + // Authorization: Bearer ${token} + Request.add("Authorization", "Bearer " + BearerToken); + } + + 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 Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT; + } + + inline Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestPut::Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken) { + try { + auto Services = MicroService::instance().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(msTimeout_/1000, msTimeout_ % 1000)); + + Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_PUT, + Path, + Poco::Net::HTTPMessage::HTTP_1_1); + std::ostringstream obody; + Poco::JSON::Stringifier::stringify(Body_,obody); + + Request.setContentType("application/json"); + Request.setContentLength(obody.str().size()); + + if(BearerToken.empty()) { + Request.add("X-API-KEY", Svc.AccessKey); + Request.add("X-INTERNAL-NAME", MicroService::instance().PublicEndPoint()); + } else { + // Authorization: Bearer ${token} + Request.add("Authorization", "Bearer " + BearerToken); + } + + std::ostream & os = Session.sendRequest(Request); + os << obody.str(); + + 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(); + } else { + Poco::JSON::Parser P; + ResponseObject = P.parse(is).extract(); + } + return Response.getStatus(); + } + } + catch (const Poco::Exception &E) + { + std::cerr << E.displayText() << std::endl; + } + return Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT; + } + + inline Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestPost::Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken) { + try { + auto Services = MicroService::instance().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(msTimeout_/1000, msTimeout_ % 1000)); + + Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_POST, + Path, + Poco::Net::HTTPMessage::HTTP_1_1); + std::ostringstream obody; + Poco::JSON::Stringifier::stringify(Body_,obody); + + Request.setContentType("application/json"); + Request.setContentLength(obody.str().size()); + + if(BearerToken.empty()) { + Request.add("X-API-KEY", Svc.AccessKey); + Request.add("X-INTERNAL-NAME", MicroService::instance().PublicEndPoint()); + } else { + // Authorization: Bearer ${token} + Request.add("Authorization", "Bearer " + BearerToken); + } + + std::ostream & os = Session.sendRequest(Request); + os << obody.str(); + + 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(); + } else { + Poco::JSON::Parser P; + ResponseObject = P.parse(is).extract(); + } + return Response.getStatus(); + } + } + catch (const Poco::Exception &E) + { + std::cerr << E.displayText() << std::endl; + } + return Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT; + } + + + inline void RESTAPI_GenericServer::InitLogging() { + std::string Public = MicroService::instance().ConfigGetString("apilogging.public.methods","PUT,POST,DELETE"); + SetFlags(true, Public); + std::string Private = MicroService::instance().ConfigGetString("apilogging.private.methods","PUT,POST,DELETE"); + SetFlags(false, Private); + + std::string PublicBadTokens = MicroService::instance().ConfigGetString("apilogging.public.badtokens.methods",""); + LogBadTokens_[0] = (Poco::icompare(PublicBadTokens,"true")==0); + std::string PrivateBadTokens = MicroService::instance().ConfigGetString("apilogging.private.badtokens.methods",""); + LogBadTokens_[1] = (Poco::icompare(PrivateBadTokens,"true")==0); + } + +#ifdef TIP_SECURITY_SERVICE + [[nodiscard]] bool AuthServiceIsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired , bool Sub ); +#endif + inline bool RESTAPIHandler::IsAuthorized( bool & Expired , bool Sub ) { + if(Internal_ && Request->has("X-INTERNAL-NAME")) { + auto Allowed = MicroService::instance().IsValidAPIKEY(*Request); + if(!Allowed) { + if(Server_.LogBadTokens(false)) { + Logger_.debug(Poco::format("I-REQ-DENIED(%s): Method='%s' Path='%s", + Utils::FormatIPv6(Request->clientAddress().toString()), + Request->getMethod(), Request->getURI())); + } + } else { + auto Id = Request->get("X-INTERNAL-NAME", "unknown"); + if(Server_.LogIt(Request->getMethod(),true)) { + Logger_.debug(Poco::format("I-REQ-ALLOWED(%s): User='%s' Method='%s' Path='%s", + Utils::FormatIPv6(Request->clientAddress().toString()), Id, + Request->getMethod(), Request->getURI())); + } + } + return Allowed; + } 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 (AuthServiceIsAuthorized(*Request, SessionToken_, UserInfo_, Expired, Sub)) { +#else + if (AuthClient()->IsAuthorized( SessionToken_, UserInfo_, Expired, Sub)) { +#endif + if(Server_.LogIt(Request->getMethod(),true)) { + Logger_.debug(Poco::format("X-REQ-ALLOWED(%s): User='%s@%s' Method='%s' Path='%s'", + UserInfo_.userinfo.email, + Utils::FormatIPv6(Request->clientAddress().toString()), + Request->clientAddress().toString(), + Request->getMethod(), + Request->getURI())); + } + return true; + } else { + if(Server_.LogBadTokens(true)) { + Logger_.debug(Poco::format("X-REQ-DENIED(%s): Method='%s' Path='%s'", + Utils::FormatIPv6(Request->clientAddress().toString()), + Request->getMethod(), Request->getURI())); + } + } + return false; + } + } + + inline MicroService * MicroService::instance_ = nullptr; +} + +namespace OpenWifi::Utils { + [[nodiscard]] inline uint64_t GetSystemId() { + uint64_t ID=0; + if(!AppServiceRegistry().Get("systemid",ID)) { + return InitializeSystemId(); + } + return ID; + } +} + +namespace OpenWifi::CIDR { + + 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; + } + + static bool CidrIPinRange(const Poco::Net::IPAddress &IP, const std::string &Range) { + 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; + } + + // + // Ranges can be a single IP, of IP1-IP2, of A set of IPs: IP1,IP2,IP3, or a cidr IP/24 + // These can work for IPv6 too... + // + static bool ValidateRange(const std::string &R) { + + auto Tokens = Poco::StringTokenizer(R,"-"); + if(Tokens.count()==2) { + Poco::Net::IPAddress a,b; + if(!Poco::Net::IPAddress::tryParse(Tokens[0],a) && Poco::Net::IPAddress::tryParse(Tokens[1],b)) + return false; + return a.family() == b.family(); + } + + Tokens = Poco::StringTokenizer(R,","); + if(Tokens.count()>1) { + return std::all_of(Tokens.begin(), Tokens.end(), [](const std::string &A) { + Poco::Net::IPAddress a; + return Poco::Net::IPAddress::tryParse(A,a); + } ); + } + + Tokens = Poco::StringTokenizer(R,"/"); + if(Tokens.count()==2) { + Poco::Net::IPAddress a; + if(!Poco::Net::IPAddress::tryParse(Tokens[0],a)) + return false; + if(std::atoi(Tokens[1].c_str())==0) + return false; + return true; + } + + Poco::Net::IPAddress a; + return Poco::Net::IPAddress::tryParse(R,a); + } + + static bool IpInRange(const Poco::Net::IPAddress & target, const std::string & R) { + + auto Tokens = Poco::StringTokenizer(R,"-"); + if(Tokens.count()==2) { + auto a = Poco::Net::IPAddress::parse(Tokens[0]); + auto b = Poco::Net::IPAddress::parse(Tokens[1]); + if(target.family() != a.family()) + return false; + return (a<=target && b>=target); + } + + Tokens = Poco::StringTokenizer(R,","); + if(Tokens.count()>1) { + return std::any_of(Tokens.begin(), Tokens.end(), [target](const std::string &Element) { + return Poco::Net::IPAddress::parse(Element) == target ; }); + } + + Tokens = Poco::StringTokenizer(R,"/"); + if(Tokens.count()==2) { + return CidrIPinRange(target,R); + } + + return Poco::Net::IPAddress::parse(R)==target; + } + + [[nodiscard]] inline bool IpInRanges(const std::string &IP, const Types::StringVec &R) { + Poco::Net::IPAddress Target; + + if(!Poco::Net::IPAddress::tryParse(IP,Target)) + return false; + + return std::any_of(cbegin(R),cend(R),[Target](const std::string &i) { return IpInRange(Target,i); }); + } + + [[nodiscard]] inline bool ValidateIpRanges(const Types::StringVec & Ranges) { + return std::all_of(cbegin(Ranges), cend(Ranges), ValidateRange); + } +} diff --git a/src/framework/OpenWifiTypes.h b/src/framework/OpenWifiTypes.h new file mode 100644 index 0000000..c5b3804 --- /dev/null +++ b/src/framework/OpenWifiTypes.h @@ -0,0 +1,40 @@ +// +// Created by stephane bourque on 2021-11-16. +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace OpenWifi::Types { + typedef std::pair StringPair; + typedef std::vector StringPairVec; + typedef std::queue StringPairQueue; + typedef std::vector StringVec; + typedef std::set StringSet; + typedef std::map> StringMapStringSet; + typedef std::function TopicNotifyFunction; + typedef std::list> TopicNotifyFunctionList; + typedef std::map NotifyTable; + typedef std::map CountedMap; + typedef std::vector TagList; + typedef std::string UUID_t; + typedef std::vector UUIDvec_t; +} + +namespace OpenWifi { + inline void UpdateCountedMap(OpenWifi::Types::CountedMap &M, const std::string &S, uint64_t Increment=1) { + auto it = M.find(S); + if(it==M.end()) + M[S] = Increment; + else + it->second += Increment; + } +} diff --git a/src/framework/RESTAPI_errors.h b/src/framework/RESTAPI_errors.h new file mode 100644 index 0000000..b203797 --- /dev/null +++ b/src/framework/RESTAPI_errors.h @@ -0,0 +1,63 @@ +// +// Created by stephane bourque on 2021-09-12. +// + +#pragma once + +namespace OpenWifi::RESTAPI::Errors { + static const std::string MissingUUID{"Missing UUID."}; + static const std::string MissingSerialNumber{"Missing Serial Number."}; + static const std::string InternalError{"Internal error. Please try later."}; + static const std::string InvalidJSONDocument{"Invalid JSON document."}; + static const std::string UnsupportedHTTPMethod{"Unsupported HTTP Method"}; + static const std::string StillInUse{"Element still in use."}; + static const std::string CouldNotBeDeleted{"Element could not be deleted."}; + static const std::string NameMustBeSet{"The name property must be set."}; + static const std::string ConfigBlockInvalid{"Configuration block type invalid."}; + static const std::string UnknownId{"Unknown management policy."}; + static const std::string InvalidDeviceTypes{"Unknown or invalid device type(s)."}; + static const std::string RecordNotCreated{"Record could not be created."}; + static const std::string RecordNotUpdated{"Record could not be updated."}; + static const std::string UnknownManagementPolicyUUID{"Unknown management policy UUID."}; + static const std::string CannotDeleteRoot{"Root Entity cannot be removed, only modified."}; + static const std::string MustCreateRootFirst{"Root entity must be created first."}; + static const std::string ParentUUIDMustExist{"Parent UUID must exist."}; + static const std::string ConfigurationMustExist{"Configuration must exist."}; + static const std::string MissingOrInvalidParameters{"Invalid or missing parameters."}; + static const std::string UnknownSerialNumber{"Unknown Serial Number."}; + static const std::string InvalidSerialNumber{"Invalid Serial Number."}; + static const std::string SerialNumberExists{"Serial Number already exists."}; + static const std::string ValidNonRootUUID{"Must be a non-root, and valid UUID."}; + static const std::string VenueMustExist{"Venue does not exist."}; + static const std::string NotBoth{"You cannot specify both Entity and Venue"}; + static const std::string EntityMustExist{"Entity must exist."}; + static const std::string ParentOrEntityMustBeSet{"Parent or Entity must be set."}; + static const std::string ContactMustExist{"Contact must exist."}; + static const std::string LocationMustExist{"Location must exist."}; + static const std::string OnlyWSSupported{"This endpoint only supports WebSocket."}; + static const std::string SerialNumberMismatch{"Serial Number mismatch."}; + static const std::string InvalidCommand{"Invalid command."}; + static const std::string NoRecordsDeleted{"No records deleted."}; + static const std::string DeviceNotConnected{"Device is not currently connected."}; + static const std::string CannotCreateWS{"Telemetry system could not create WS endpoint. Please try again."}; + static const std::string BothDeviceTypeRevision{"Both deviceType and revision must be set."}; + static const std::string IdOrSerialEmpty{"SerialNumber and Id must not be empty."}; + static const std::string MissingUserID{"Missing user ID."}; + static const std::string IdMustBe0{"To create a user, you must set the ID to 0"}; + static const std::string InvalidUserRole{"Invalid userRole."}; + static const std::string InvalidEmailAddress{"Invalid email address."}; + static const std::string PasswordRejected{"Password was rejected. This maybe an old password."}; + static const std::string InvalidIPRanges{"Invalid IP range specifications."}; + static const std::string InvalidLOrderBy{"Invalid orderBy specification."}; + static const std::string NeedMobileNumber{"You must provide at least one validated phone number."}; + static const std::string BadMFAMethod{"MFA only supports sms or email."}; + static const std::string InvalidCredentials{"Invalid credentials (username/password)."}; + static const std::string InvalidPassword{"Password does not conform to basic password rules."}; + static const std::string UserPendingVerification{"User access denied pending email verification."}; + static const std::string PasswordMustBeChanged{"Password must be changed."}; + static const std::string UnrecognizedRequest{"Ill-formed request. Please consult documentation."}; + static const std::string MissingAuthenticationInformation{"Missing authentication information."}; + static const std::string InsufficientAccessRights{"Insufficient access rights to complete the operation."}; + static const std::string ExpiredToken{"Token has expired, user must login."}; +} + diff --git a/src/framework/RESTAPI_protocol.h b/src/framework/RESTAPI_protocol.h new file mode 100644 index 0000000..664a91d --- /dev/null +++ b/src/framework/RESTAPI_protocol.h @@ -0,0 +1,138 @@ +// +// 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. +// + +#pragma once + +namespace OpenWifi::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 * HOST = "host"; + static const char * OS = "os"; + static const char * HOSTNAME = "hostname"; + static const char * PROCESSORS = "processors"; + static const char * REASON = "reason"; + static const char * RELOAD = "reload"; + static const char * SUBSYSTEMS = "subsystems"; + 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 * PING = "ping"; + 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 * WITHEXTENDEDINFO = "withExtendedInfo"; + + 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 * RESENDMFACODE = "resendMFACode"; + static const char * COMPLETEMFACHALLENGE = "completeMFAChallenge"; + static const char * ME = "me"; + static const char * TELEMETRY = "telemetry"; + static const char * INTERVAL = "interval"; + static const char * UI = "UI"; + +} diff --git a/src/framework/StorageClass.h b/src/framework/StorageClass.h new file mode 100644 index 0000000..9a4ca56 --- /dev/null +++ b/src/framework/StorageClass.h @@ -0,0 +1,166 @@ +// +// Created by stephane bourque on 2021-10-06. +// + +#pragma once + +#include "Poco/Data/Session.h" +#include "Poco/Data/SessionPool.h" +#include "Poco/Data/SQLite/Connector.h" +#include "Poco/JSON/Object.h" + +#ifndef SMALL_BUILD +#include "Poco/Data/PostgreSQL/Connector.h" +#include "Poco/Data/MySQL/Connector.h" +#endif + +#include "framework/MicroService.h" + +namespace OpenWifi { + enum DBType { + sqlite, + pgsql, + mysql + }; + + class StorageClass : public SubSystemServer { + public: + StorageClass() noexcept: + SubSystemServer("StorageClass", "STORAGE-SVR", "storage") + { + } + + int Start() override { + std::lock_guard Guard(Mutex_); + + Logger().setLevel(Poco::Message::PRIO_NOTICE); + Logger().notice("Starting."); + std::string DBType = MicroService::instance().ConfigGetString("storage.type"); + + if (DBType == "sqlite") { + Setup_SQLite(); + } else if (DBType == "postgresql") { + Setup_PostgreSQL(); + } else if (DBType == "mysql") { + Setup_MySQL(); + } + return 0; + } + + void Stop() override { + Pool_->shutdown(); + } + + [[nodiscard]] inline std::string ComputeRange(uint64_t From, uint64_t HowMany) { + if(dbType_==sqlite) { + return " LIMIT " + std::to_string(From) + ", " + std::to_string(HowMany) + " "; + } else if(dbType_==pgsql) { + return " LIMIT " + std::to_string(HowMany) + " OFFSET " + std::to_string(From) + " "; + } else if(dbType_==mysql) { + return " LIMIT " + std::to_string(HowMany) + " OFFSET " + std::to_string(From) + " "; + } + return " LIMIT " + std::to_string(HowMany) + " OFFSET " + std::to_string(From) + " "; + } + + inline std::string 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; + } + + private: + inline int Setup_SQLite(); + inline int Setup_MySQL(); + inline int Setup_PostgreSQL(); + + protected: + Poco::SharedPtr Pool_; + Poco::Data::SQLite::Connector SQLiteConn_; + Poco::Data::PostgreSQL::Connector PostgresConn_; + Poco::Data::MySQL::Connector MySQLConn_; + DBType dbType_ = sqlite; + }; + +#ifdef SMALL_BUILD + int Service::Setup_MySQL() { Daemon()->exit(Poco::Util::Application::EXIT_CONFIG); return 0; } + int Service::Setup_PostgreSQL() { Daemon()->exit(Poco::Util::Application::EXIT_CONFIG); return 0; } +#else + + inline int StorageClass::Setup_SQLite() { + Logger().notice("SQLite StorageClass enabled."); + dbType_ = sqlite; + auto DBName = MicroService::instance().DataDir() + "/" + MicroService::instance().ConfigGetString("storage.type.sqlite.db"); + auto NumSessions = MicroService::instance().ConfigGetInt("storage.type.sqlite.maxsessions", 64); + auto IdleTime = MicroService::instance().ConfigGetInt("storage.type.sqlite.idletime", 60); + SQLiteConn_.registerConnector(); + Pool_ = Poco::SharedPtr(new Poco::Data::SessionPool(SQLiteConn_.name(), DBName, 4, NumSessions, IdleTime)); + return 0; + } + + inline int StorageClass::Setup_MySQL() { + Logger().notice("MySQL StorageClass enabled."); + dbType_ = mysql; + auto NumSessions = MicroService::instance().ConfigGetInt("storage.type.mysql.maxsessions", 64); + auto IdleTime = MicroService::instance().ConfigGetInt("storage.type.mysql.idletime", 60); + auto Host = MicroService::instance().ConfigGetString("storage.type.mysql.host"); + auto Username = MicroService::instance().ConfigGetString("storage.type.mysql.username"); + auto Password = MicroService::instance().ConfigGetString("storage.type.mysql.password"); + auto Database = MicroService::instance().ConfigGetString("storage.type.mysql.database"); + auto Port = MicroService::instance().ConfigGetString("storage.type.mysql.port"); + + std::string ConnectionStr = + "host=" + Host + + ";user=" + Username + + ";password=" + Password + + ";db=" + Database + + ";port=" + Port + + ";compress=true;auto-reconnect=true"; + + MySQLConn_.registerConnector(); + Pool_ = Poco::SharedPtr(new Poco::Data::SessionPool(MySQLConn_.name(), ConnectionStr, 4, NumSessions, IdleTime)); + + return 0; + } + + inline int StorageClass::Setup_PostgreSQL() { + Logger().notice("PostgreSQL StorageClass enabled."); + dbType_ = pgsql; + auto NumSessions = MicroService::instance().ConfigGetInt("storage.type.postgresql.maxsessions", 64); + auto IdleTime = MicroService::instance().ConfigGetInt("storage.type.postgresql.idletime", 60); + auto Host = MicroService::instance().ConfigGetString("storage.type.postgresql.host"); + auto Username = MicroService::instance().ConfigGetString("storage.type.postgresql.username"); + auto Password = MicroService::instance().ConfigGetString("storage.type.postgresql.password"); + auto Database = MicroService::instance().ConfigGetString("storage.type.postgresql.database"); + auto Port = MicroService::instance().ConfigGetString("storage.type.postgresql.port"); + auto ConnectionTimeout = MicroService::instance().ConfigGetString("storage.type.postgresql.connectiontimeout"); + + std::string ConnectionStr = + "host=" + Host + + " user=" + Username + + " password=" + Password + + " dbname=" + Database + + " port=" + Port + + " connect_timeout=" + ConnectionTimeout; + + PostgresConn_.registerConnector(); + Pool_ = Poco::SharedPtr(new Poco::Data::SessionPool(PostgresConn_.name(), ConnectionStr, 4, NumSessions, IdleTime)); + + return 0; + } +#endif + +} diff --git a/src/framework/orm.h b/src/framework/orm.h new file mode 100644 index 0000000..51e9b0b --- /dev/null +++ b/src/framework/orm.h @@ -0,0 +1,883 @@ +// +// 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. +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Poco/Tuple.h" +#include "Poco/Data/SessionPool.h" +#include "Poco/Data/Statement.h" +#include "Poco/Data/RecordSet.h" +#include "Poco/Data/SQLite/Connector.h" +#include "Poco/Logger.h" +#include "Poco/StringTokenizer.h" +#include "StorageClass.h" + +namespace ORM { + + enum FieldType { + FT_INT, + FT_BIGINT, + FT_TEXT, + FT_VARCHAR, + FT_BLOB, + FT_BOOLEAN + }; + + enum Indextype { + ASC, + DESC + }; + + struct Field { + std::string Name; + FieldType Type; + int Size=0; + bool Index=false; + + + Field(std::string N, FieldType T, int S=0, bool Index=false) : + Name(std::move(N)), + Type(T), + Size(S), + Index(Index) {} + + explicit Field(std::string N) : + Name(std::move(N)) + { + Type = FT_TEXT; + } + + Field(std::string N, int S) : + Name(std::move(N)), Size(S) + { + if(Size>0 && Size<255) + Type = FT_VARCHAR; + else + Type = FT_TEXT; + } + + Field(std::string N, int S, bool I): + Name(std::move(N)), Size(S), Index(I) + { + if(Size>0 && Size<255) + Type = FT_VARCHAR; + else + Type = FT_TEXT; + } + }; + typedef std::vector FieldVec; + + struct IndexEntry { + std::string FieldName; + Indextype Type; + }; + typedef std::vector IndexEntryVec; + + struct Index { + std::string Name; + IndexEntryVec Entries; + }; + typedef std::vector IndexVec; + + inline std::string FieldTypeToChar(OpenWifi::DBType Type, FieldType T, int Size=0) { + switch(T) { + case FT_INT: return "INT"; + case FT_BIGINT: return "BIGINT"; + case FT_TEXT: return "TEXT"; + case FT_BOOLEAN: return "BOOLEAN"; + case FT_VARCHAR: + if(Size) + return std::string("VARCHAR(") + std::to_string(Size) + std::string(")"); + else + return "TEXT"; + case FT_BLOB: + if(Type==OpenWifi::DBType::mysql) + return "LONGBLOB"; + else if(Type==OpenWifi::DBType::pgsql) + return "BYTEA"; + else if(Type==OpenWifi::DBType::sqlite) + return "BLOB"; + default: + assert(false); + return ""; + } + assert(false); + return ""; + } + + inline std::string Escape(const std::string &S) { + std::string R; + + for(const auto &i:S) + if(i=='\'') + R += "''"; + else + R += i; + return R; + } + + enum SqlComparison { EQ = 0, NEQ, LT, LTE, GT, GTE }; + enum SqlBinaryOp { AND = 0 , OR }; + + static const std::vector BOPS{" and ", " or "}; + static const std::vector SQLCOMPS{"=","!=","<","<=",">",">="}; + + inline std::string to_string(uint64_t V) { + return std::to_string(V); + } + + inline std::string to_string(int V) { + return std::to_string(V); + } + + inline std::string to_string(bool V) { + return std::to_string(V); + } + + inline std::string to_string(const std::string &S) { + return S; + } + + inline std::string to_string(const char * S) { + return S; + } + + template class DBCache { + public: + DBCache(unsigned Size, unsigned Timeout) + { + + } + virtual void Create(const RecordType &R)=0; + virtual bool GetFromCache(const std::string &FieldName, const std::string &Value, RecordType &R)=0; + virtual void UpdateCache(const RecordType &R)=0; + virtual void Delete(const std::string &FieldName, const std::string &Value)=0; + private: + + }; + + template class DB { + public: + DB( OpenWifi::DBType dbtype, + const char *TableName, + const FieldVec & Fields, + const IndexVec & Indexes, + Poco::Data::SessionPool & Pool, + Poco::Logger &L, + const char *Prefix, + DBCache * Cache=nullptr): + Type_(dbtype), + TableName_(TableName), + Pool_(Pool), + Logger_(L), + Prefix_(Prefix), + Cache_(Cache) + { + assert(RecordTuple::length == Fields.size()); + + bool first = true; + int Place=0; + + for(const auto &i:Fields) { + FieldNames_[i.Name] = Place; + if(!first) { + CreateFields_ += ", "; + SelectFields_ += ", "; + UpdateFields_ += ", "; + SelectList_ += ", "; + } else { + SelectList_ += "("; + } + + CreateFields_ += i.Name + " " + FieldTypeToChar(Type_, i.Type,i.Size) + (i.Index ? " unique primary key" : ""); + SelectFields_ += i.Name ; + UpdateFields_ += i.Name + "=?"; + SelectList_ += "?"; + first = false; + Place++; + } + SelectList_ += ")"; + + if(!Indexes.empty()) { + if(Type_==OpenWifi::DBType::sqlite || Type_==OpenWifi::DBType::pgsql) { + for(const auto &j:Indexes) { + std::string IndexLine; + + IndexLine = std::string("CREATE INDEX IF NOT EXISTS ") + j.Name + std::string(" ON ") + TableName_+ " ("; + bool first_entry=true; + for(const auto &k:j.Entries) { + assert(FieldNames_.find(k.FieldName) != FieldNames_.end()); + if(!first_entry) { + IndexLine += " , "; + } + first_entry = false; + IndexLine += k.FieldName + std::string(" ") + std::string(k.Type == Indextype::ASC ? "ASC" : "DESC") ; + } + IndexLine += " )"; + IndexCreation_.template emplace_back(IndexLine); + } + } else if(Type_==OpenWifi::DBType::mysql) { + bool firstIndex = true; + std::string IndexLine; + for(const auto &j:Indexes) { + if(!firstIndex) + IndexLine += ", "; + firstIndex = false; + IndexLine += " INDEX " + j.Name + " ( " ; + bool first_entry=true; + for(const auto &k:j.Entries) { + assert(FieldNames_.find(k.FieldName) != FieldNames_.end()); + if(!first_entry) { + IndexLine += " ,"; + } + first_entry = false; + IndexLine += k.FieldName + std::string(k.Type == Indextype::ASC ? " ASC" : " DESC"); + } + IndexLine += " ) "; + } + IndexCreation_.template emplace_back(IndexLine); + } + } + } + + [[nodiscard]] const std::string & CreateFields() const { return CreateFields_; }; + [[nodiscard]] const std::string & SelectFields() const { return SelectFields_; }; + [[nodiscard]] const std::string & SelectList() const { return SelectList_; }; + [[nodiscard]] const std::string & UpdateFields() const { return UpdateFields_; }; + + inline std::string OP(const char *F, SqlComparison O , bool V) { + assert( FieldNames_.find(F) != FieldNames_.end() ); + return std::string{"("} + F + SQLCOMPS[O] + (V ? "true" : "false") + ")" ; + } + + inline std::string OP(const char *F, SqlComparison O , int V) { + assert( FieldNames_.find(F) != FieldNames_.end() ); + return std::string{"("} + F + SQLCOMPS[O] + std::to_string(V) + ")" ; + } + + inline std::string OP(const char *F, SqlComparison O , uint64_t V) { + assert( FieldNames_.find(F) != FieldNames_.end() ); + return std::string{"("} + F + SQLCOMPS[O] + std::to_string(V) + ")" ; + } + + std::string OP(const char *F, SqlComparison O , const std::string & V) { + assert( FieldNames_.find(F) != FieldNames_.end() ); + return std::string{"("} + F + SQLCOMPS[O] + "'" + Escape(V) + "')" ; + } + + std::string OP(const char *F, SqlComparison O , const char * V) { + assert( FieldNames_.find(F) != FieldNames_.end() ); + return std::string{"("} + F + SQLCOMPS[O] + "'" + Escape(V) + "')" ; + } + + static std::string OP( const std::string &P1, SqlBinaryOp BOP , const std::string &P2) { + return std::string("(")+P1 + BOPS[BOP] + P2 +")"; + } + + std::string OP( bool Paran, const std::string &P1, SqlBinaryOp BOP , const std::string &P2) { + return P1 + BOPS[BOP] + P2 +")"; + } + + template std::string OP( bool ParanOpen, const std::string &P1, SqlBinaryOp BOP , const std::string &P2, Others... More) { + return P1 + BOPS[BOP] + OP(ParanOpen, P2, More...) + ")"; + } + + template std::string OP( const std::string &P1, SqlBinaryOp BOP , const std::string &P2, Others... More) { + return std::string{"("} + P1 + BOPS[BOP] + OP(true, P2, More...); + } + + inline bool Create() { + std::string S; + + switch(Type_) { + case OpenWifi::DBType::mysql: { + try { + Poco::Data::Session Session = Pool_.get(); + std::string Statement = IndexCreation_.empty() ? "create table if not exists " + TableName_ +" ( " + CreateFields_ + " )" : + "create table if not exists " + TableName_ +" ( " + CreateFields_ + " ), " + IndexCreation_[0] + " )"; + Session << Statement , Poco::Data::Keywords::now; + return true; + } catch (const Poco::Exception &E) { + Logger_.error("Failure to create MySQL DB resources."); + Logger_.log(E); + } + return false; + } + break; + + case OpenWifi::DBType::sqlite: { + try { + Poco::Data::Session Session = Pool_.get(); + std::string Statement = "create table if not exists " + TableName_ + " ( " + CreateFields_ + " )"; + Session << Statement , Poco::Data::Keywords::now; + for(const auto &i:IndexCreation_) { + Session << i , Poco::Data::Keywords::now; + } + return true; + } catch (const Poco::Exception &E) { + Logger_.error("Failure to create SQLITE DB resources."); + Logger_.log(E); + } + } + break; + + case OpenWifi::DBType::pgsql: { + try { + Poco::Data::Session Session = Pool_.get(); + std::string Statement = "create table if not exists " + TableName_ + " ( " + CreateFields_ + " )"; + Session << Statement , Poco::Data::Keywords::now; + for(const auto &i:IndexCreation_) { + Session << i , Poco::Data::Keywords::now; + } + return true; + } catch (const Poco::Exception &E) { + Logger_.error("Failure to create POSTGRESQL DB resources."); + Logger_.log(E); + } + } + break; + } + return false; + } + + [[nodiscard]] std::string ConvertParams(const std::string & S) const { + if(Type_!=OpenWifi::DBType::pgsql) + return S; + + std::string R; + R.reserve(S.size()*2+1); + auto Idx=1; + for(auto const & i:S) + { + if(i=='?') { + R += '$'; + R.append(std::to_string(Idx++)); + } else { + R += i; + } + } + + return R; + } + + void Convert( const RecordTuple &in , RecordType &out); + void Convert( const RecordType &in , RecordTuple &out); + + inline const std::string & Prefix() { return Prefix_; }; + + bool CreateRecord( const RecordType & R) { + try { + Poco::Data::Session Session = Pool_.get(); + Poco::Data::Statement Insert(Session); + + RecordTuple RT; + Convert(R, RT); + std::string St = "insert into " + TableName_ + " ( " + SelectFields_ + " ) values " + SelectList_; + Insert << ConvertParams(St) , + Poco::Data::Keywords::use(RT); + Insert.execute(); + + if(Cache_) + Cache_->Create(R); + return true; + + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + template bool GetRecord( const char * FieldName, const T & Value, RecordType & R) { + try { + assert( FieldNames_.find(FieldName) != FieldNames_.end() ); + + if(Cache_) { + if(Cache_->GetFromCache(FieldName, Value, R)) + return true; + } + + Poco::Data::Session Session = Pool_.get(); + Poco::Data::Statement Select(Session); + RecordTuple RT; + + std::string St = "select " + SelectFields_ + " from " + TableName_ + " where " + FieldName + "=?" ; + + auto tValue{Value}; + + Select << ConvertParams(St) , + Poco::Data::Keywords::into(RT), + Poco::Data::Keywords::use(tValue); + Select.execute(); + + if(Select.rowsExtracted()==1) { + Convert(RT,R); + if(Cache_) + Cache_->UpdateCache(R); + return true; + } + return false; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + typedef std::vector StringVec; + + template < typename T, + typename T0, typename T1> bool GR(const char *FieldName, T & R,T0 &V0, T1 &V1) { + try { + + assert( FieldNames_.find(FieldName) != FieldNames_.end() ); + + Poco::Data::Session Session = Pool_.get(); + Poco::Data::Statement Select(Session); + RecordTuple RT; + + std::string St = "select " + SelectFields_ + " from " + TableName_ + + " where " + FieldName[0] + "=? and " + FieldName[1] + "=?" ; + Select << ConvertParams(St) , + Poco::Data::Keywords::into(RT), + Poco::Data::Keywords::use(V0), + Poco::Data::Keywords::use(V1); + + if(Select.execute()==1) { + Convert(RT,R); + return true; + } + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + typedef std::vector RecordList; + + bool GetRecords( uint64_t Offset, uint64_t HowMany, std::vector & Records, const std::string & Where = "", const std::string & OrderBy = "") { + try { + Poco::Data::Session Session = Pool_.get(); + Poco::Data::Statement Select(Session); + RecordList RL; + std::string St = "select " + SelectFields_ + " from " + TableName_ + + (Where.empty() ? "" : " where " + Where) + OrderBy + + ComputeRange(Offset, HowMany) ; + + Select << St , + Poco::Data::Keywords::into(RL); + Select.execute(); + + if(Select.rowsExtracted()>0) { + for(auto &i:RL) { + RecordType R; + Convert(i, R); + Records.template emplace_back(R); + } + return true; + } + return false; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + template bool UpdateRecord( const char *FieldName, const T & Value, const RecordType & R) { + try { + assert( FieldNames_.find(FieldName) != FieldNames_.end() ); + + Poco::Data::Session Session = Pool_.get(); + Poco::Data::Statement Update(Session); + + RecordTuple RT; + + Convert(R, RT); + + auto tValue(Value); + + std::string St = "update " + TableName_ + " set " + UpdateFields_ + " where " + FieldName + "=?" ; + Update << ConvertParams(St) , + Poco::Data::Keywords::use(RT), + Poco::Data::Keywords::use(tValue); + Update.execute(); + if(Cache_) + Cache_->UpdateCache(R); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + template bool ReplaceRecord( const char *FieldName, const T & Value, RecordType & R) { + try { + if(Exists(FieldName, Value)) { + return UpdateRecord(FieldName,Value,R); + } + return CreateRecord(R); + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + template bool GetNameAndDescription(const char *FieldName, const T & Value, std::string & Name, std::string & Description ) { + try { + assert( FieldNames_.find(FieldName) != FieldNames_.end() ); + Poco::Data::Session Session = Pool_.get(); + Poco::Data::Statement Select(Session); + RecordTuple RT; + + std::string St = "select " + SelectFields_ + " from " + TableName_ + " where " + FieldName + "=?" ; + RecordType R; + auto tValue{Value}; + Select << ConvertParams(St) , + Poco::Data::Keywords::into(RT), + Poco::Data::Keywords::use(tValue); + + if(Select.execute()==1) { + Convert(RT,R); + Name = R.info.name; + Description = R.info.description; + return true; + } + return false; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + template bool DeleteRecord( const char *FieldName, const T & Value) { + try { + assert( FieldNames_.find(FieldName) != FieldNames_.end() ); + + Poco::Data::Session Session = Pool_.get(); + Poco::Data::Statement Delete(Session); + + std::string St = "delete from " + TableName_ + " where " + FieldName + "=?" ; + auto tValue{Value}; + + Delete << ConvertParams(St) , + Poco::Data::Keywords::use(tValue); + Delete.execute(); + if(Cache_) + Cache_->Delete(FieldName, Value); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool DeleteRecords( const std::string & WhereClause ) { + try { + assert( !WhereClause.empty()); + Poco::Data::Session Session = Pool_.get(); + Poco::Data::Statement Delete(Session); + + std::string St = "delete from " + TableName_ + " where " + WhereClause; + Delete << St; + Delete.execute(); + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Exists(const char *FieldName, const std::string & Value) { + try { + assert( FieldNames_.find(FieldName) != FieldNames_.end() ); + + RecordType R; + if(GetRecord(FieldName,Value,R)) + return true; + return false; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool Iterate( std::function F) { + try { + + uint64_t Offset=1; + uint64_t Batch=50; + bool Done=false; + while(!Done) { + std::vector Records; + if(GetRecords(Offset,Batch,Records)) { + for(const auto &i:Records) { + if(!F(i)) + return true; + } + if(Records.size() bool ManipulateVectorMember( X T, const char *FieldName, std::string & ParentUUID, std::string & ChildUUID, bool Add) { + try { + assert( FieldNames_.find(FieldName) != FieldNames_.end() ); + + RecordType R; + if(GetRecord(FieldName, ParentUUID, R)) { + auto it = std::find((R.*T).begin(),(R.*T).end(),ChildUUID); + if(Add) { + if(it!=(R.*T).end() && *it == ChildUUID) + return false; + (R.*T).push_back(ChildUUID); + std::sort((R.*T).begin(),(R.*T).end()); + } else { + if(it!=(R.*T).end() && *it == ChildUUID) + (R.*T).erase(it); + else + return false; + } + UpdateRecord(FieldName, ParentUUID, R); + return true; + } + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + bool RunScript(const std::vector & Statements, bool IgnoreExceptions=true) { + try { + Poco::Data::Session Session = Pool_.get(); + Poco::Data::Statement Command(Session); + + for(const auto &i:Statements) { + try { + Command << i, Poco::Data::Keywords::now; + } catch (const Poco::Exception &E) { + Logger_.log(E); + Logger_.error(Poco::format("The following statement '%s' generated an exception during a table upgrade. This maya or may not be a problem.", i)); + } + if(!IgnoreExceptions) { + return false; + } + Command.reset(Session); + } + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + + virtual uint32_t Version() { + return 0; + } + + virtual bool Upgrade(uint32_t from, uint32_t &to) { + to = from; + return true; + } + + inline bool AddChild( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::children, FieldName, ParentUUID, ChildUUID, true); + } + + inline bool DeleteChild( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::children, FieldName, ParentUUID, ChildUUID, false); + } + + inline bool AddLocation( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::locations, FieldName, ParentUUID, ChildUUID, true); + } + + inline bool DeleteLocation( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::locations, FieldName, ParentUUID, ChildUUID, false); + } + + inline bool AddContact( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::contacts, FieldName, ParentUUID, ChildUUID, true); + } + + inline bool DeleteContact( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::contacts, FieldName, ParentUUID, ChildUUID, false); + } + + inline bool AddVenue( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::venues, FieldName, ParentUUID, ChildUUID, true); + } + + inline bool DeleteVenue( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::venues, FieldName, ParentUUID, ChildUUID, false); + } + + inline bool AddDevice( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::devices, FieldName, ParentUUID, ChildUUID, true); + } + + inline bool DeleteDevice( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::devices, FieldName, ParentUUID, ChildUUID, false); + } + + inline bool AddEntity( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::entities, FieldName, ParentUUID, ChildUUID, true); + } + + inline bool DeleteEntity( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::entities, FieldName, ParentUUID, ChildUUID, false); + } + + inline bool AddUser( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::users, FieldName, ParentUUID, ChildUUID, true); + } + + inline bool DelUser( const char *FieldName, std::string & ParentUUID, std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::users, FieldName, ParentUUID, ChildUUID, false); + } + + inline bool AddInUse(const char *FieldName, std::string & ParentUUID, const std::string & Prefix, const std::string & ChildUUID) { + std::string FakeUUID{ Prefix + ":" + ChildUUID}; + return ManipulateVectorMember(&RecordType::inUse,FieldName, ParentUUID, FakeUUID, true); + } + + inline bool DeleteInUse(const char *FieldName, std::string & ParentUUID, const std::string & Prefix, const std::string & ChildUUID) { + std::string FakeUUID{ Prefix + ":" + ChildUUID}; + return ManipulateVectorMember(&RecordType::inUse,FieldName, ParentUUID, FakeUUID, false); + } + + inline bool DeleteContact(const char *FieldName, std::string & ParentUUID, const std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::contacts,FieldName, ParentUUID, ChildUUID, false); + } + + inline bool AddContact(const char *FieldName, std::string & ParentUUID, const std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::contacts,FieldName, ParentUUID, ChildUUID, true); + } + + inline bool DeleteLocation(const char *FieldName, std::string & ParentUUID, const std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::locations,FieldName, ParentUUID, ChildUUID, false); + } + + inline bool AddLocation(const char *FieldName, std::string & ParentUUID, const std::string & ChildUUID) { + return ManipulateVectorMember(&RecordType::locations,FieldName, ParentUUID, ChildUUID, true); + } + + inline bool GetInUse(const char *FieldName, std::string & UUID, std::vector & UUIDs ) { + RecordType R; + if(GetRecord(FieldName,UUID,R)) { + UUIDs = R.inUse; + return true; + } + return false; + } + + [[nodiscard]] inline std::string ComputeRange(uint64_t From, uint64_t HowMany) { + if(From<1) From=0; + switch(Type_) { + case OpenWifi::DBType::sqlite: + return " LIMIT " + std::to_string(From) + ", " + std::to_string(HowMany) + " "; + case OpenWifi::DBType::pgsql: + return " LIMIT " + std::to_string(HowMany) + " OFFSET " + std::to_string(From) + " "; + case OpenWifi::DBType::mysql: + return " LIMIT " + std::to_string(HowMany) + " OFFSET " + std::to_string(From) + " "; + default: + return " LIMIT " + std::to_string(HowMany) + " OFFSET " + std::to_string(From) + " "; + } + } + + Poco::Logger & Logger() { return Logger_; } + + bool DeleteRecordsFromCache(const char *FieldName, const std::string &Value ) { + if(Cache_) + Cache_->Delete(FieldName, Value); + return true; + } + + protected: + Poco::Data::SessionPool &Pool_; + Poco::Logger &Logger_; + std::string TableName_; + DBCache *Cache_= nullptr; + private: + OpenWifi::DBType Type_; + std::string CreateFields_; + std::string SelectFields_; + std::string SelectList_; + std::string UpdateFields_; + std::vector IndexCreation_; + std::map FieldNames_; + std::string Prefix_; + }; +} + diff --git a/src/framework/uCentral_Protocol.h b/src/framework/uCentral_Protocol.h new file mode 100644 index 0000000..8a6094a --- /dev/null +++ b/src/framework/uCentral_Protocol.h @@ -0,0 +1,130 @@ +// +// 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. +// +#pragma once + +#include "Poco/String.h" + +namespace OpenWifi::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"; + static const char * CONNECTIONIP = "connectionIp"; + static const char * TELEMETRY = "telemetry"; + + enum EVENT_MSG { + ET_UNKNOWN, + ET_CONNECT, + ET_STATE, + ET_HEALTHCHECK, + ET_LOG, + ET_CRASHLOG, + ET_PING, + ET_CFGPENDING, + ET_RECOVERY, + ET_DEVICEUPDATE, + ET_TELEMETRY + }; + + inline 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 if (!Poco::icompare(Method, TELEMETRY)) { + return ET_TELEMETRY; + } else + return ET_UNKNOWN; + }; +} diff --git a/src/ow_version.h.in b/src/ow_version.h.in new file mode 100644 index 0000000..f61c19e --- /dev/null +++ b/src/ow_version.h.in @@ -0,0 +1,13 @@ +// +// Created by stephane bourque on 2021-12-06. +// + +#pragma once + +#include + +namespace OW_VERSION { + inline static const std::string VERSION{"@CMAKE_PROJECT_VERSION@"}; + inline static const std::string BUILD{"@BUILD_NUM@"}; + inline static const std::string HASH{"@GIT_HASH@"}; +} \ No newline at end of file