mirror of
https://github.com/Telecominfraproject/wlan-cloud-owprov.git
synced 2025-10-29 17:52:28 +00:00
5415 lines
205 KiB
C++
5415 lines
205 KiB
C++
//
|
|
// 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 <array>
|
|
#include <iostream>
|
|
#include <cstdlib>
|
|
#include <vector>
|
|
#include <set>
|
|
#include <thread>
|
|
#include <chrono>
|
|
#include <fstream>
|
|
#include <regex>
|
|
#include <random>
|
|
#include <iomanip>
|
|
#include <queue>
|
|
#include <variant>
|
|
|
|
|
|
// This must be defined for poco_debug and poco_trace macros to function.
|
|
|
|
#ifndef POCO_LOG_DEBUG
|
|
#define POCO_LOG_DEBUG true
|
|
#endif
|
|
|
|
namespace OpenWifi {
|
|
inline uint64_t Now() { return std::time(nullptr); };
|
|
}
|
|
|
|
namespace OpenWifi::Utils {
|
|
std::vector<unsigned char> base64decode(const std::string& input);
|
|
std::string base64encode(const unsigned char *input, uint32_t size);
|
|
}
|
|
|
|
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 "Poco/SplitterChannel.h"
|
|
#include "Poco/JWT/Signer.h"
|
|
#include "Poco/DeflatingStream.h"
|
|
#include "Poco/Net/SocketReactor.h"
|
|
#include "Poco/Net/WebSocket.h"
|
|
#include "Poco/Environment.h"
|
|
#include "Poco/NObserver.h"
|
|
#include "Poco/Net/SocketNotification.h"
|
|
#include "Poco/Base64Decoder.h"
|
|
#include "Poco/ThreadLocal.h"
|
|
#include "cppkafka/cppkafka.h"
|
|
|
|
#include "framework/MicroServiceErrorHandler.h"
|
|
#include "framework/OpenWifiTypes.h"
|
|
#include "framework/KafkaTopics.h"
|
|
#include "framework/ow_constants.h"
|
|
#include "RESTObjects/RESTAPI_SecurityObjects.h"
|
|
#include "nlohmann/json.hpp"
|
|
#include "ow_version.h"
|
|
#include "fmt/core.h"
|
|
|
|
#define _OWDEBUG_ std::cout<< __FILE__ <<":" << __LINE__ << std::endl;
|
|
// #define _OWDEBUG_ Logger().debug(Poco::format("%s: %lu",__FILE__,__LINE__));
|
|
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,
|
|
SECURITY_SERVICE_UNREACHABLE,
|
|
CANNOT_REFRESH_TOKEN
|
|
};
|
|
|
|
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<bool>();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool Get(const char *Key, uint64_t & Value ) {
|
|
if(Registry_[Key].is_number_unsigned()) {
|
|
Value = Registry_[Key].get<uint64_t>();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool Get(const char *Key, std::string & Value ) {
|
|
if(Registry_[Key].is_string()) {
|
|
Value = Registry_[Key].get<std::string>();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
std::string FileName;
|
|
nlohmann::json Registry_;
|
|
};
|
|
|
|
inline auto AppServiceRegistry() { return AppServiceRegistry::instance(); }
|
|
|
|
}
|
|
|
|
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<Poco::JSON::Object::Ptr>();
|
|
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 char * S) {
|
|
Obj.set(Field,S);
|
|
}
|
|
|
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, int16_t Value) {
|
|
Obj.set(Field, Value);
|
|
}
|
|
|
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, int32_t Value) {
|
|
Obj.set(Field, Value);
|
|
}
|
|
|
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, int64_t Value) {
|
|
Obj.set(Field, Value);
|
|
}
|
|
|
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, uint16_t Value) {
|
|
Obj.set(Field, Value);
|
|
}
|
|
|
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, uint32_t Value) {
|
|
Obj.set(Field, Value);
|
|
}
|
|
|
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, uint64_t Value) {
|
|
Obj.set(Field,Value);
|
|
}
|
|
|
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Poco::Data::BLOB &Value) {
|
|
auto Result = Utils::base64encode((const unsigned char *)Value.rawContent(),Value.size());
|
|
Obj.set(Field,Result);
|
|
}
|
|
|
|
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 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);
|
|
}
|
|
|
|
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::Counted3DMapSII &M) {
|
|
Poco::JSON::Array A;
|
|
for(const auto &[OrgName,MonthlyNumberMap]:M) {
|
|
Poco::JSON::Object OrgObject;
|
|
OrgObject.set("tag",OrgName);
|
|
Poco::JSON::Array MonthlyArray;
|
|
for(const auto &[Month,Counter]:MonthlyNumberMap) {
|
|
Poco::JSON::Object Inner;
|
|
Inner.set("value", Month);
|
|
Inner.set("counter", Counter);
|
|
MonthlyArray.add(Inner);
|
|
}
|
|
OrgObject.set("index",MonthlyArray);
|
|
A.add(OrgObject);
|
|
}
|
|
Obj.set(Field, A);
|
|
}
|
|
|
|
template<typename T> void field_to_json(Poco::JSON::Object &Obj,
|
|
const char *Field,
|
|
const T &V,
|
|
std::function<std::string(const T &)> F) {
|
|
Obj.set(Field, F(V));
|
|
}
|
|
|
|
template<class T> void field_to_json(Poco::JSON::Object &Obj, const char *Field, const std::vector<T> &Value) {
|
|
Poco::JSON::Array Arr;
|
|
for(const auto &i:Value) {
|
|
Poco::JSON::Object AO;
|
|
i.to_json(AO);
|
|
Arr.add(AO);
|
|
}
|
|
Obj.set(Field, Arr);
|
|
}
|
|
|
|
template<class T> void field_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<typename T> bool field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, T & V,
|
|
std::function<T(const std::string &)> F) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field))
|
|
V = F(Obj->get(Field).toString());
|
|
return true;
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, std::string &S) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field))
|
|
S = Obj->get(Field).toString();
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, double & Value) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field))
|
|
Value = (double)Obj->get(Field);
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, float & Value) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field))
|
|
Value = (float)Obj->get(Field);
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, bool &Value) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field))
|
|
Value = (Obj->get(Field).toString() == "true");
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, int16_t &Value) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field))
|
|
Value = (int16_t)Obj->get(Field);
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, int32_t &Value) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field))
|
|
Value = (int32_t) Obj->get(Field);
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, int64_t &Value) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field))
|
|
Value = (int64_t)Obj->get(Field);
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, uint16_t &Value) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field))
|
|
Value = (uint16_t)Obj->get(Field);
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, uint32_t &Value) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field))
|
|
Value = (uint32_t)Obj->get(Field);
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, uint64_t &Value) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field))
|
|
Value = (uint64_t)Obj->get(Field);
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Poco::Data::BLOB &Value) {
|
|
if(Obj->has(Field) && !Obj->isNull(Field)) {
|
|
auto Result = Utils::base64decode(Obj->get(Field).toString());
|
|
Value.assignRaw((const unsigned char *)&Result[0],Result.size());
|
|
}
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Types::StringPairVec &Vec) {
|
|
if(Obj->isArray(Field) && !Obj->isNull(Field)) {
|
|
auto O = Obj->getArray(Field);
|
|
for(const auto &i:*O) {
|
|
std::string S1,S2;
|
|
auto Inner = i.extract<Poco::JSON::Object::Ptr>();
|
|
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(const Poco::JSON::Object::Ptr &Obj, const char *Field, Types::StringVec &Value) {
|
|
if(Obj->isArray(Field) && !Obj->isNull(Field)) {
|
|
Value.clear();
|
|
Poco::JSON::Array::Ptr A = Obj->getArray(Field);
|
|
for(const auto &i:*A) {
|
|
Value.push_back(i.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Types::TagList &Value) {
|
|
if(Obj->isArray(Field) && !Obj->isNull(Field)) {
|
|
Value.clear();
|
|
Poco::JSON::Array::Ptr A = Obj->getArray(Field);
|
|
for(const auto &i:*A) {
|
|
Value.push_back(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class T> void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, std::vector<T> &Value) {
|
|
if(Obj->isArray(Field) && !Obj->isNull(Field)) {
|
|
Poco::JSON::Array::Ptr Arr = Obj->getArray(Field);
|
|
for(auto &i:*Arr) {
|
|
auto InnerObj = i.extract<Poco::JSON::Object::Ptr>();
|
|
T NewItem;
|
|
NewItem.from_json(InnerObj);
|
|
Value.push_back(NewItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class T> void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, T &Value) {
|
|
if(Obj->isObject(Field) && !Obj->isNull(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<class T> std::string to_string(const std::vector<T> & ObjectArray) {
|
|
Poco::JSON::Array OutputArr;
|
|
if(ObjectArray.empty())
|
|
return "[]";
|
|
for(auto const &i:ObjectArray) {
|
|
Poco::JSON::Object O;
|
|
i.to_json(O);
|
|
OutputArr.add(O);
|
|
}
|
|
std::ostringstream OS;
|
|
Poco::JSON::Stringifier::condense(OutputArr,OS);
|
|
return OS.str();
|
|
}
|
|
|
|
template<class T> std::string to_string(const std::vector<std::vector<T>> & 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<T>::value) {
|
|
InnerArr.add(j);
|
|
} if constexpr(std::is_same_v<T,std::string>) {
|
|
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<class T> std::string to_string(const T & Object) {
|
|
Poco::JSON::Object OutputObj;
|
|
Object.to_json(OutputObj);
|
|
std::ostringstream OS;
|
|
Poco::JSON::Stringifier::condense(OutputObj,OS);
|
|
return OS.str();
|
|
}
|
|
|
|
inline Types::StringVec to_object_array(const std::string & ObjectString) {
|
|
|
|
Types::StringVec Result;
|
|
if(ObjectString.empty())
|
|
return Result;
|
|
|
|
try {
|
|
Poco::JSON::Parser P;
|
|
auto Object = P.parse(ObjectString).template extract<Poco::JSON::Array::Ptr>();
|
|
for (auto const &i : *Object) {
|
|
Result.push_back(i.toString());
|
|
}
|
|
} catch (...) {
|
|
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
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<Poco::JSON::Array::Ptr>();
|
|
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<Poco::JSON::Array::Ptr>();
|
|
for (const auto &i : *Object) {
|
|
auto InnerObject = i.template extract<Poco::JSON::Array::Ptr>();
|
|
if(InnerObject->size()==2) {
|
|
auto S1 = InnerObject->getElement<std::string>(0);
|
|
auto S2 = InnerObject->getElement<std::string>(1);
|
|
R.push_back(std::make_pair(S1,S2));
|
|
}
|
|
}
|
|
} catch (...) {
|
|
|
|
}
|
|
|
|
return R;
|
|
}
|
|
|
|
template<class T> std::vector<T> to_object_array(const std::string & ObjectString) {
|
|
std::vector<T> Result;
|
|
if(ObjectString.empty())
|
|
return Result;
|
|
|
|
try {
|
|
Poco::JSON::Parser P;
|
|
auto Object = P.parse(ObjectString).template extract<Poco::JSON::Array::Ptr>();
|
|
for (auto const &i : *Object) {
|
|
auto InnerObject = i.template extract<Poco::JSON::Object::Ptr>();
|
|
T Obj;
|
|
Obj.from_json(InnerObject);
|
|
Result.push_back(Obj);
|
|
}
|
|
} catch (...) {
|
|
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
template<class T> std::vector<std::vector<T>> to_array_of_array_of_object(const std::string & ObjectString) {
|
|
std::vector<std::vector<T>> Result;
|
|
if(ObjectString.empty())
|
|
return Result;
|
|
try {
|
|
Poco::JSON::Parser P1;
|
|
auto OutterArray = P1.parse(ObjectString).template extract<Poco::JSON::Array::Ptr>();
|
|
for (auto const &i : *OutterArray) {
|
|
Poco::JSON::Parser P2;
|
|
auto InnerArray = P2.parse(i).template extract<Poco::JSON::Array::Ptr>();
|
|
std::vector<T> InnerVector;
|
|
for(auto const &j: *InnerArray) {
|
|
auto Object = j.template extract<Poco::JSON::Object::Ptr>();
|
|
T Obj;
|
|
Obj.from_json(Object);
|
|
InnerVector.push_back(Obj);
|
|
}
|
|
Result.push_back(InnerVector);
|
|
}
|
|
} catch (...) {
|
|
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
template<class T> T to_object(const std::string & ObjectString) {
|
|
T Result;
|
|
|
|
if(ObjectString.empty())
|
|
return Result;
|
|
|
|
Poco::JSON::Parser P;
|
|
auto Object = P.parse(ObjectString).template extract<Poco::JSON::Object::Ptr>();
|
|
Result.from_json(Object);
|
|
|
|
return Result;
|
|
}
|
|
|
|
template<class T> bool from_request(T & Obj, Poco::Net::HTTPServerRequest &Request) {
|
|
Poco::JSON::Parser IncomingParser;
|
|
auto RawObject = IncomingParser.parse(Request.stream()).extract<Poco::JSON::Object::Ptr>();
|
|
Obj.from_json(RawObject);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
namespace OpenWifi::Utils {
|
|
|
|
inline bool NormalizeMac(std::string & Mac) {
|
|
Poco::replaceInPlace(Mac,":","");
|
|
Poco::replaceInPlace(Mac,"-","");
|
|
if(Mac.size()!=12)
|
|
return false;
|
|
for(const auto &i:Mac) {
|
|
if(!std::isxdigit(i))
|
|
return false;
|
|
}
|
|
Poco::toLowerInPlace(Mac);
|
|
return true;
|
|
}
|
|
|
|
inline void SetThreadName(const char *name) {
|
|
#ifdef __linux__
|
|
Poco::Thread::current()->setName(name);
|
|
pthread_setname_np(pthread_self(), name);
|
|
#endif
|
|
#ifdef __APPLE__
|
|
Poco::Thread::current()->setName(name);
|
|
pthread_setname_np(name);
|
|
#endif
|
|
}
|
|
|
|
inline void SetThreadName(Poco::Thread &thr, const char *name) {
|
|
#ifdef __linux__
|
|
thr.setName(name);
|
|
pthread_setname_np(thr.tid(), name);
|
|
#endif
|
|
#ifdef __APPLE__
|
|
thr.setName(name);
|
|
#endif
|
|
}
|
|
|
|
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 bool ValidUUID(const std::string &UUID) {
|
|
if(UUID.size()>36)
|
|
return false;
|
|
uint dashes=0;
|
|
return (std::all_of(UUID.begin(),UUID.end(),[&](auto i){ if(i=='-') dashes++; return i=='-' || std::isxdigit(i);})) && (dashes>0);
|
|
}
|
|
|
|
template <typename ...Args> std::string ComputeHash(Args&&... args) {
|
|
Poco::SHA2Engine E;
|
|
auto as_string = [](auto p) {
|
|
if constexpr(std::is_arithmetic_v<decltype(p)>) {
|
|
return std::to_string(p);
|
|
} else {
|
|
return p;
|
|
}
|
|
};
|
|
(E.update(as_string(args)),...);
|
|
return Poco::SHA2Engine::digestToHex(E.digest());
|
|
}
|
|
|
|
[[nodiscard]] inline std::vector<std::string> Split(const std::string &List, char Delimiter=',' ) {
|
|
std::vector<std::string> ReturnList;
|
|
|
|
unsigned long P=0;
|
|
|
|
while(P<List.size())
|
|
{
|
|
unsigned long P2 = List.find_first_of(Delimiter, P);
|
|
if(P2==std::string::npos) {
|
|
ReturnList.push_back(List.substr(P));
|
|
break;
|
|
}
|
|
else
|
|
ReturnList.push_back(List.substr(P,P2-P));
|
|
P=P2+1;
|
|
}
|
|
return ReturnList;
|
|
}
|
|
|
|
|
|
[[nodiscard]] inline std::string FormatIPv6(const std::string & I )
|
|
{
|
|
if(I.substr(0,8) == "[::ffff:")
|
|
{
|
|
unsigned long PClosingBracket = I.find_first_of(']');
|
|
|
|
std::string ip = I.substr(8, PClosingBracket-8);
|
|
std::string port = I.substr(PClosingBracket+1);
|
|
return ip + port;
|
|
}
|
|
|
|
return I;
|
|
}
|
|
|
|
inline void padTo(std::string& str, size_t num, char paddingChar = '\0') {
|
|
str.append(num - str.length() % num, paddingChar);
|
|
}
|
|
|
|
[[nodiscard]] inline std::string SerialToMAC(const std::string &Serial) {
|
|
std::string R = Serial;
|
|
|
|
if(R.size()<12)
|
|
padTo(R,12,'0');
|
|
else if (R.size()>12)
|
|
R = R.substr(0,12);
|
|
|
|
char buf[18];
|
|
|
|
buf[0] = R[0]; buf[1] = R[1] ; buf[2] = ':' ;
|
|
buf[3] = R[2] ; buf[4] = R[3]; buf[5] = ':' ;
|
|
buf[6] = R[4]; buf[7] = R[5] ; buf[8] = ':' ;
|
|
buf[9] = R[6] ; buf[10]= R[7]; buf[11] = ':';
|
|
buf[12] = R[8] ; buf[13]= R[9]; buf[14] = ':';
|
|
buf[15] = R[10] ; buf[16]= R[11];buf[17] = 0;
|
|
|
|
return buf;
|
|
}
|
|
|
|
inline uint64_t MACToInt(const std::string &MAC) {
|
|
uint64_t Result = 0 ;
|
|
for(const auto &c:MAC) {
|
|
if(c==':')
|
|
continue;
|
|
Result <<= 4;
|
|
if(c>='0' && c<='9') {
|
|
Result += (c - '0');
|
|
} else if (c>='a' && c<='f') {
|
|
Result += (c-'a'+10);
|
|
} else if (c>='A' && c<='F') {
|
|
Result += (c-'A'+10);
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
[[nodiscard]] inline std::string ToHex(const std::vector<unsigned char> & B) {
|
|
std::string R;
|
|
R.reserve(B.size()*2);
|
|
|
|
static const char hex[] = "0123456789abcdef";
|
|
|
|
for(const auto &i:B)
|
|
{
|
|
R += (hex[ (i & 0xf0) >> 4]);
|
|
R += (hex[ (i & 0x0f) ]);
|
|
}
|
|
|
|
return R;
|
|
}
|
|
|
|
inline static const char kEncodeLookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
inline static const char kPadCharacter = '=';
|
|
|
|
using byte = std::uint8_t;
|
|
|
|
[[nodiscard]] inline std::string base64encode(const byte *input, uint32_t size) {
|
|
std::string encoded;
|
|
encoded.reserve(((size / 3) + (size % 3 > 0)) * 4);
|
|
|
|
std::uint32_t temp,i,ee;
|
|
ee = (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<byte> 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<byte> 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 = Seconds = 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(H1<H2)
|
|
return true;
|
|
if(H1>H2)
|
|
return false;
|
|
if(M1<M2)
|
|
return true;
|
|
if(M2>M1)
|
|
return false;
|
|
if(S1<=S2)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
[[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) {
|
|
return std::stoull(S,nullptr,16);
|
|
}
|
|
|
|
[[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
|
|
("[_a-z0-9-]+(\\.[_a-z0-9-]+)*(\\+[a-z0-9-]+)?@[a-z0-9-]+(\\.[a-z0-9-]+)*");
|
|
// 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();
|
|
}
|
|
|
|
inline bool ExtractBase64CompressedData(const std::string &CompressedData,
|
|
std::string &UnCompressedData, uint64_t compress_sz ) {
|
|
std::istringstream ifs(CompressedData);
|
|
Poco::Base64Decoder b64in(ifs);
|
|
std::ostringstream ofs;
|
|
Poco::StreamCopier::copyStream(b64in, ofs);
|
|
|
|
int factor = 20;
|
|
unsigned long MaxSize = compress_sz ? (unsigned long) (compress_sz + 5000) : (unsigned long) (ofs.str().size() * factor);
|
|
while(true) {
|
|
std::vector<uint8_t> UncompressedBuffer(MaxSize);
|
|
unsigned long FinalSize = MaxSize;
|
|
auto status = uncompress((uint8_t *)&UncompressedBuffer[0], &FinalSize,
|
|
(uint8_t *)ofs.str().c_str(), ofs.str().size());
|
|
if(status==Z_OK) {
|
|
UncompressedBuffer[FinalSize] = 0;
|
|
UnCompressedData = (char *)&UncompressedBuffer[0];
|
|
return true;
|
|
}
|
|
if(status==Z_BUF_ERROR) {
|
|
if(factor<300) {
|
|
factor+=10;
|
|
MaxSize = ofs.str().size() * factor;
|
|
continue;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
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"};
|
|
static const std::string uSERVICE_OWRRM{ "owrrm"};
|
|
|
|
class ConfigurationEntry {
|
|
public:
|
|
template <typename T> explicit ConfigurationEntry(T def) :
|
|
Default_(def),
|
|
Current_(def){
|
|
}
|
|
|
|
template <typename T> explicit ConfigurationEntry(T def, T cur, const std::string &Hint="") :
|
|
Default_(def),
|
|
Current_(cur),
|
|
Hint_(Hint){
|
|
}
|
|
|
|
inline ConfigurationEntry()=default;
|
|
inline ~ConfigurationEntry()=default;
|
|
|
|
template <typename T> explicit operator T () const { return std::get<T>(Current_); }
|
|
inline ConfigurationEntry & operator=(const char *v) { Current_ = std::string(v); return *this;}
|
|
template <typename T> ConfigurationEntry & operator=(T v) { Current_ = (T) v; return *this;}
|
|
|
|
void reset() {
|
|
Current_ = Default_;
|
|
}
|
|
|
|
private:
|
|
std::variant<bool,uint64_t,std::string> Default_, Current_;
|
|
std::string Hint_;
|
|
};
|
|
inline std::string to_string(const ConfigurationEntry &v) { return (std::string) v; }
|
|
|
|
typedef std::map<std::string,ConfigurationEntry> ConfigurationMap_t;
|
|
|
|
template <typename T> class FIFO {
|
|
public:
|
|
explicit FIFO(uint32_t Size) :
|
|
Size_(Size) {
|
|
Buffer_ = new T [Size_];
|
|
}
|
|
|
|
~FIFO() {
|
|
delete [] Buffer_;
|
|
}
|
|
|
|
mutable Poco::BasicEvent<bool> Writable_;
|
|
mutable Poco::BasicEvent<bool> Readable_;
|
|
|
|
inline bool Read(T &t) {
|
|
{
|
|
std::lock_guard M(Mutex_);
|
|
if (Write_ == Read_) {
|
|
return false;
|
|
}
|
|
|
|
t = Buffer_[Read_++];
|
|
if (Read_ == Size_) {
|
|
Read_ = 0;
|
|
}
|
|
Used_--;
|
|
}
|
|
bool flag = true;
|
|
Writable_.notify(this, flag);
|
|
return true;
|
|
}
|
|
|
|
inline bool Write(const T &t) {
|
|
{
|
|
std::lock_guard M(Mutex_);
|
|
|
|
Buffer_[Write_++] = t;
|
|
if (Write_ == Size_) {
|
|
Write_ = 0;
|
|
}
|
|
Used_++;
|
|
MaxEverUsed_ = std::max(Used_,MaxEverUsed_);
|
|
}
|
|
bool flag = true;
|
|
Readable_.notify(this, flag);
|
|
return false;
|
|
}
|
|
|
|
inline bool isFull() {
|
|
std::lock_guard M(Mutex_);
|
|
return Used_==Buffer_->capacity();
|
|
}
|
|
|
|
inline auto MaxEverUser() const { return MaxEverUsed_; }
|
|
|
|
private:
|
|
std::recursive_mutex Mutex_;
|
|
uint32_t Size_=0;
|
|
uint32_t Read_=0;
|
|
uint32_t Write_=0;
|
|
uint32_t Used_=0;
|
|
uint32_t MaxEverUsed_=0;
|
|
T * Buffer_ = nullptr;
|
|
};
|
|
|
|
template <class Record, typename KeyType = std::string, int Size=256, int Expiry=60000> 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<KeyType,Record> Cache_{Size,Expiry};
|
|
};
|
|
|
|
class BusEventManager : public Poco::Runnable {
|
|
public:
|
|
explicit BusEventManager(Poco::Logger &L) : Logger_(L) {
|
|
|
|
}
|
|
inline void run() final;
|
|
inline void Start();
|
|
inline void Stop();
|
|
inline Poco::Logger & Logger() { return Logger_; }
|
|
private:
|
|
mutable std::atomic_bool Running_ = false;
|
|
Poco::Thread Thread_;
|
|
Poco::Logger &Logger_;
|
|
};
|
|
|
|
class MyPrivateKeyPassphraseHandler : public Poco::Net::PrivateKeyPassphraseHandler {
|
|
public:
|
|
explicit MyPrivateKeyPassphraseHandler(const std::string &Password, Poco::Logger & Logger):
|
|
PrivateKeyPassphraseHandler(true),
|
|
Password_(Password),
|
|
Logger_(Logger)
|
|
{}
|
|
void onPrivateKeyRequested([[maybe_unused]] const void * pSender,std::string & privateKey) {
|
|
Logger_.information("Returning key passphrase.");
|
|
privateKey = Password_;
|
|
};
|
|
inline Poco::Logger & Logger() { return Logger_; }
|
|
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),
|
|
cert_file_(std::move(Cert_file)),
|
|
key_file_(std::move(Key_file)),
|
|
root_ca_(std::move(RootCa)),
|
|
key_file_password_(std::move(Key_file_password)),
|
|
issuer_cert_file_(std::move(Issuer)),
|
|
client_cas_(std::move(ClientCas)),
|
|
cas_(std::move(Cas)),
|
|
name_(std::move(Name)),
|
|
backlog_(backlog),
|
|
level_(M)
|
|
{};
|
|
|
|
[[nodiscard]] inline const std::string &Address() const { return address_; };
|
|
[[nodiscard]] inline uint32_t Port() const { return port_; };
|
|
[[nodiscard]] inline auto KeyFile() const { return key_file_; };
|
|
[[nodiscard]] inline auto CertFile() const { return cert_file_; };
|
|
[[nodiscard]] inline auto RootCA() const { return root_ca_; };
|
|
[[nodiscard]] inline auto KeyFilePassword() const { return key_file_password_; };
|
|
[[nodiscard]] inline auto IssuerCertFile() const { return issuer_cert_file_; };
|
|
[[nodiscard]] inline auto Name() const { return name_; };
|
|
[[nodiscard]] inline int Backlog() const { return backlog_; }
|
|
[[nodiscard]] inline auto Cas() const { return cas_; }
|
|
|
|
[[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<Poco::Net::Context>(new Poco::Net::Context(Poco::Net::Context::TLS_SERVER_USE, P));
|
|
|
|
if(!key_file_password_.empty()) {
|
|
auto PassphraseHandler = Poco::SharedPtr<MyPrivateKeyPassphraseHandler>( new MyPrivateKeyPassphraseHandler(key_file_password_,L));
|
|
Poco::Net::SSLManager::instance().initializeServer(PassphraseHandler, nullptr,Context);
|
|
}
|
|
|
|
if (!cert_file_.empty() && !key_file_.empty()) {
|
|
Poco::Crypto::X509Certificate Cert(cert_file_);
|
|
Poco::Crypto::X509Certificate Root(root_ca_);
|
|
|
|
Context->useCertificate(Cert);
|
|
Context->addChainCertificate(Root);
|
|
|
|
Context->addCertificateAuthority(Root);
|
|
|
|
if (level_ == Poco::Net::Context::VERIFY_STRICT) {
|
|
if (issuer_cert_file_.empty()) {
|
|
L.fatal("In strict mode, you must supply ans issuer certificate");
|
|
}
|
|
if (client_cas_.empty()) {
|
|
L.fatal("In strict mode, client cas must be supplied");
|
|
}
|
|
Poco::Crypto::X509Certificate Issuing(issuer_cert_file_);
|
|
Context->addChainCertificate(Issuing);
|
|
Context->addCertificateAuthority(Issuing);
|
|
}
|
|
|
|
Poco::Crypto::RSAKey Key("", key_file_, key_file_password_);
|
|
Context->usePrivateKey(Key);
|
|
|
|
SSL_CTX *SSLCtx = Context->sslContext();
|
|
if (!SSL_CTX_check_private_key(SSLCtx)) {
|
|
L.fatal(fmt::format("Wrong Certificate({}) for Key({})", 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(60);
|
|
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);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] inline Poco::Net::ServerSocket CreateSocket([[maybe_unused]] Poco::Logger &L) const {
|
|
Poco::Net::Context::Params P;
|
|
|
|
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::ServerSocket(SockAddr, backlog_);
|
|
} else {
|
|
Poco::Net::IPAddress Addr(address_);
|
|
Poco::Net::SocketAddress SockAddr(Addr, port_);
|
|
return Poco::Net::ServerSocket(SockAddr, backlog_);
|
|
}
|
|
}
|
|
|
|
inline void LogCertInfo(Poco::Logger &L, const Poco::Crypto::X509Certificate &C) const {
|
|
L.information("=============================================================================================");
|
|
L.information(fmt::format("> Issuer: {}", C.issuerName()));
|
|
L.information("---------------------------------------------------------------------------------------------");
|
|
L.information(fmt::format("> Common Name: {}",
|
|
C.issuerName(Poco::Crypto::X509Certificate::NID_COMMON_NAME)));
|
|
L.information(fmt::format("> Country: {}",
|
|
C.issuerName(Poco::Crypto::X509Certificate::NID_COUNTRY)));
|
|
L.information(fmt::format("> Locality: {}",
|
|
C.issuerName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME)));
|
|
L.information(fmt::format("> State/Prov: {}",
|
|
C.issuerName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE)));
|
|
L.information(fmt::format("> Org name: {}",
|
|
C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME)));
|
|
L.information(
|
|
fmt::format("> Org unit: {}",
|
|
C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME)));
|
|
L.information(
|
|
fmt::format("> Email: {}",
|
|
C.issuerName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS)));
|
|
L.information(fmt::format("> Serial#: {}",
|
|
C.issuerName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER)));
|
|
L.information("---------------------------------------------------------------------------------------------");
|
|
L.information(fmt::format("> Subject: {}", C.subjectName()));
|
|
L.information("---------------------------------------------------------------------------------------------");
|
|
L.information(fmt::format("> Common Name: {}",
|
|
C.subjectName(Poco::Crypto::X509Certificate::NID_COMMON_NAME)));
|
|
L.information(fmt::format("> Country: {}",
|
|
C.subjectName(Poco::Crypto::X509Certificate::NID_COUNTRY)));
|
|
L.information(fmt::format("> Locality: {}",
|
|
C.subjectName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME)));
|
|
L.information(
|
|
fmt::format("> State/Prov: {}",
|
|
C.subjectName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE)));
|
|
L.information(
|
|
fmt::format("> Org name: {}",
|
|
C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME)));
|
|
L.information(
|
|
fmt::format("> Org unit: {}",
|
|
C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME)));
|
|
L.information(
|
|
fmt::format("> Email: {}",
|
|
C.subjectName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS)));
|
|
L.information(fmt::format("> Serial#: {}",
|
|
C.subjectName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER)));
|
|
L.information("---------------------------------------------------------------------------------------------");
|
|
L.information(fmt::format("> Signature Algo: {}", C.signatureAlgorithm()));
|
|
auto From = Poco::DateTimeFormatter::format(C.validFrom(), Poco::DateTimeFormat::HTTP_FORMAT);
|
|
L.information(fmt::format("> Valid from: {}", From));
|
|
auto Expires =
|
|
Poco::DateTimeFormatter::format(C.expiresOn(), Poco::DateTimeFormat::HTTP_FORMAT);
|
|
L.information(fmt::format("> Expires on: {}", Expires));
|
|
L.information(fmt::format("> Version: {}", (int)C.version()));
|
|
L.information(fmt::format("> Serial #: {}", C.serialNumber()));
|
|
L.information("=============================================================================================");
|
|
}
|
|
|
|
inline void LogCert(Poco::Logger &L) const {
|
|
try {
|
|
Poco::Crypto::X509Certificate C(cert_file_);
|
|
L.information("=============================================================================================");
|
|
L.information("=============================================================================================");
|
|
L.information(fmt::format("Certificate Filename: {}", 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(fmt::format("Issues Certificate Filename: {}", issuer_cert_file_));
|
|
LogCertInfo(L, C1);
|
|
L.information("=============================================================================================");
|
|
}
|
|
|
|
if (!client_cas_.empty()) {
|
|
std::vector<Poco::Crypto::X509Certificate> Certs =
|
|
Poco::Net::X509Certificate::readPEM(client_cas_);
|
|
|
|
L.information("=============================================================================================");
|
|
L.information("=============================================================================================");
|
|
L.information(fmt::format("Client CAs Filename: {}", client_cas_));
|
|
L.information("=============================================================================================");
|
|
auto i = 1;
|
|
for (const auto &C3 : Certs) {
|
|
L.information(fmt::format(" Index: {}", 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<Poco::Crypto::X509Certificate> Certs =
|
|
Poco::Net::X509Certificate::readPEM(root_ca_);
|
|
|
|
L.information("=============================================================================================");
|
|
L.information("=============================================================================================");
|
|
L.information(fmt::format("CA Filename: {}", root_ca_));
|
|
L.information("=============================================================================================");
|
|
auto i = 1;
|
|
for (const auto &C : Certs) {
|
|
L.information(fmt::format(" Index: {}", i));
|
|
L.information("=============================================================================================");
|
|
LogCertInfo(L, C);
|
|
i++;
|
|
}
|
|
L.information("=============================================================================================");
|
|
} catch (const Poco::Exception &E) {
|
|
L.log(E);
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::string address_;
|
|
uint32_t port_;
|
|
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_;
|
|
std::string name_;
|
|
int backlog_;
|
|
Poco::Net::Context::VerificationMode level_;
|
|
};
|
|
|
|
class SubSystemServer : public Poco::Util::Application::Subsystem {
|
|
public:
|
|
SubSystemServer(const std::string & Name, const std::string &LoggingPrefix,
|
|
const std::string & SubSystemConfigPrefix);
|
|
|
|
inline void initialize(Poco::Util::Application &self) override;
|
|
inline void uninitialize() override {
|
|
}
|
|
inline void reinitialize([[maybe_unused]] Poco::Util::Application &self) override {
|
|
Logger_->L_.information("Reloading of this subsystem is not supported.");
|
|
}
|
|
inline void defineOptions([[maybe_unused]] 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() const { return Logger_->L_; }
|
|
inline void SetLoggingLevel(const std::string & levelName) {
|
|
Logger_->L_.setLevel(Poco::Logger::parseLevel(levelName));
|
|
}
|
|
inline int GetLoggingLevel() { return Logger_->L_.getLevel(); }
|
|
|
|
virtual int Start() = 0;
|
|
virtual void Stop() = 0;
|
|
|
|
struct LoggerWrapper {
|
|
Poco::Logger & L_;
|
|
LoggerWrapper(Poco::Logger &L) :
|
|
L_(L) {}
|
|
};
|
|
|
|
protected:
|
|
std::recursive_mutex Mutex_;
|
|
std::vector<PropertiesFileServerEntry> ConfigServersList_;
|
|
|
|
private:
|
|
std::unique_ptr<LoggerWrapper> 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<bool,8> LogFlags_{false};
|
|
std::array<bool,2> 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::milliseconds>(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)<Period) {
|
|
E->Count++;
|
|
Cache_.update(H,E);
|
|
if(E->Count > MaxCalls) {
|
|
poco_warning(Logger(),fmt::format("RATE-LIMIT-EXCEEDED: from '{}'", 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<uint64_t,ClientCacheEntry> Cache_{2048};
|
|
std::hash<std::string> 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<std::string> Select;
|
|
bool Lifetime=false, LastOnly=false, Newest=false, CountOnly=false, AdditionalInfo=false;
|
|
};
|
|
typedef std::map<std::string, std::string> BindingMap;
|
|
|
|
struct RateLimit {
|
|
int64_t Interval=1000;
|
|
int64_t MaxCalls=10;
|
|
};
|
|
|
|
RESTAPIHandler( BindingMap map,
|
|
Poco::Logger &l,
|
|
std::vector<std::string> Methods,
|
|
RESTAPI_GenericServer & Server,
|
|
uint64_t TransactionId,
|
|
bool Internal,
|
|
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)),
|
|
Internal_(Internal),
|
|
RateLimited_(RateLimited),
|
|
SubOnlyService_(SubscriberOnly),
|
|
AlwaysAuthorize_(AlwaysAuthorize),
|
|
Server_(Server),
|
|
MyRates_(Profile),
|
|
TransactionId_(TransactionId)
|
|
{
|
|
}
|
|
|
|
inline bool RoleIsAuthorized([[maybe_unused]] const std::string & Path, [[maybe_unused]] const std::string & Method, [[maybe_unused]] std::string & Reason) {
|
|
return true;
|
|
}
|
|
|
|
inline void handleRequest(Poco::Net::HTTPServerRequest &RequestIn,
|
|
Poco::Net::HTTPServerResponse &ResponseIn) final {
|
|
try {
|
|
Request = &RequestIn;
|
|
Response = &ResponseIn;
|
|
|
|
// std::string th_name = "restsvr_" + std::to_string(TransactionId_);
|
|
// Utils::SetThreadName(th_name.c_str());
|
|
|
|
if(Request->getContentLength()>0) {
|
|
if(Request->getContentType().find("application/json")!=std::string::npos) {
|
|
ParsedBody_ = IncomingParser_.parse(Request->stream()).extract<Poco::JSON::Object::Ptr>();
|
|
}
|
|
}
|
|
|
|
if(RateLimited_ && RESTAPI_RateLimiter()->IsRateLimited(RequestIn,MyRates_.Interval, MyRates_.MaxCalls)) {
|
|
return UnAuthorized(RESTAPI::Errors::RATE_LIMIT_EXCEEDED);
|
|
}
|
|
|
|
if (!ContinueProcessing())
|
|
return;
|
|
|
|
bool Expired=false, Contacted=false;
|
|
if (AlwaysAuthorize_ && !IsAuthorized(Expired, Contacted, SubOnlyService_)) {
|
|
if(Expired)
|
|
return UnAuthorized(RESTAPI::Errors::EXPIRED_TOKEN);
|
|
if(Contacted)
|
|
return UnAuthorized(RESTAPI::Errors::INVALID_TOKEN);
|
|
else
|
|
return UnAuthorized(RESTAPI::Errors::SECURITY_SERVICE_UNREACHABLE);
|
|
}
|
|
|
|
std::string Reason;
|
|
if(!RoleIsAuthorized(RequestIn.getURI(), Request->getMethod(), Reason)) {
|
|
return UnAuthorized(RESTAPI::Errors::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<std::string> & SelectedRecords() const { return QB_.Select; }
|
|
|
|
inline static bool ParseBindings(const std::string & Request, const std::list<std::string> & EndPoints, BindingMap &bindings) {
|
|
bindings.clear();
|
|
auto PathItems = Poco::StringTokenizer(Request, "/");
|
|
|
|
for(const auto &EndPoint:EndPoints) {
|
|
auto ParamItems = Poco::StringTokenizer(EndPoint, "/");
|
|
if (PathItems.count() != ParamItems.count())
|
|
continue;
|
|
|
|
bool Matched = true;
|
|
for (size_t i = 0; i < PathItems.count(); 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;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
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(),[&](const std::pair<std::string,std::string> &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=false) {
|
|
auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[&](const std::pair<std::string,std::string> &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_),[&](const std::pair<std::string,std::string> &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_),[&](const std::pair<std::string,std::string> &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_),[&](const std::pair<std::string,std::string> &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<std::string> &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, Types::UUIDvec_t & Value) {
|
|
if(O->has(Field) && O->isArray(Field)) {
|
|
auto Arr = O->getArray(Field);
|
|
for(const auto &i:*Arr)
|
|
Value.emplace_back(i.toString());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, double &Value) {
|
|
if(O->has(Field)) {
|
|
Value = (double) O->get(Field);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, Poco::Data::BLOB &Value) {
|
|
if(O->has(Field)) {
|
|
std::string Content = O->get(Field).toString();
|
|
auto DecodedBlob = Utils::base64decode(Content);
|
|
Value.assignRaw((const unsigned char *)&DecodedBlob[0],DecodedBlob.size());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
template <typename T> bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, const T &value, T & assignee) {
|
|
if(O->has(Field)) {
|
|
assignee = value;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline void SetCommonHeaders(bool CloseConnection=false) {
|
|
Response->setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
|
|
Response->setChunkedTransferEncoding(true);
|
|
Response->setContentType("application/json");
|
|
auto Origin = Request->find("Origin");
|
|
if (Origin != Request->end()) {
|
|
Response->set("Access-Control-Allow-Origin", Origin->second);
|
|
} else {
|
|
Response->set("Access-Control-Allow-Origin", "*");
|
|
}
|
|
Response->set("Vary", "Origin, Accept-Encoding");
|
|
if(CloseConnection) {
|
|
Response->set("Connection", "close");
|
|
Response->setKeepAlive(false);
|
|
} else {
|
|
Response->setKeepAlive(true);
|
|
Response->set("Connection", "Keep-Alive");
|
|
Response->set("Keep-Alive", "timeout=30, max=1000");
|
|
}
|
|
}
|
|
|
|
inline void ProcessOptions() {
|
|
Response->setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
|
|
Response->setChunkedTransferEncoding(true);
|
|
auto Origin = Request->find("Origin");
|
|
if (Origin != Request->end()) {
|
|
Response->set("Access-Control-Allow-Origin", Origin->second);
|
|
} else {
|
|
Response->set("Access-Control-Allow-Origin", "*");
|
|
}
|
|
Response->set("Access-Control-Allow-Methods", MakeList(Methods_));
|
|
auto RequestHeaders = Request->find("Access-Control-Request-Headers");
|
|
if(RequestHeaders!=Request->end())
|
|
Response->set("Access-Control-Allow-Headers", RequestHeaders->second);
|
|
Response->set("Vary", "Origin, Accept-Encoding");
|
|
Response->set("Access-Control-Allow-Credentials", "true");
|
|
Response->set("Access-Control-Max-Age", "86400");
|
|
Response->set("Connection", "Keep-Alive");
|
|
Response->set("Keep-Alive", "timeout=30, max=1000");
|
|
|
|
Response->setContentLength(0);
|
|
Response->setStatus(Poco::Net::HTTPResponse::HTTP_OK);
|
|
Response->send();
|
|
}
|
|
|
|
inline void PrepareResponse(Poco::Net::HTTPResponse::HTTPStatus Status = Poco::Net::HTTPResponse::HTTP_OK,
|
|
bool CloseConnection = false) {
|
|
Response->setStatus(Status);
|
|
SetCommonHeaders(CloseConnection);
|
|
}
|
|
|
|
inline void BadRequest(const OpenWifi::RESTAPI::Errors::msg &E, const std::string & Extra="") {
|
|
PrepareResponse(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST);
|
|
Poco::JSON::Object ErrorObject;
|
|
ErrorObject.set("ErrorCode",400);
|
|
ErrorObject.set("ErrorDetails",Request->getMethod());
|
|
if(Extra.empty())
|
|
ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ;
|
|
else
|
|
ErrorObject.set("ErrorDescription",fmt::format("{}: {} ({})",E.err_num,E.err_txt, Extra)) ;
|
|
|
|
std::ostream &Answer = Response->send();
|
|
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
|
}
|
|
|
|
/* inline void BadRequest(uint64_t ErrorCode, const std::string & ErrorText) {
|
|
PrepareResponse(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST);
|
|
Poco::JSON::Object ErrorObject;
|
|
ErrorObject.set("ErrorCode", ErrorCode);
|
|
ErrorObject.set("ErrorDetails",Request->getMethod());
|
|
ErrorObject.set("ErrorDescription", ErrorText) ;
|
|
std::ostream &Answer = Response->send();
|
|
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
|
}
|
|
*/
|
|
inline void InternalError(const OpenWifi::RESTAPI::Errors::msg &E) {
|
|
PrepareResponse(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR);
|
|
Poco::JSON::Object ErrorObject;
|
|
ErrorObject.set("ErrorCode",500);
|
|
ErrorObject.set("ErrorDetails",Request->getMethod());
|
|
ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ;
|
|
std::ostream &Answer = Response->send();
|
|
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
|
}
|
|
|
|
inline void UnAuthorized(const OpenWifi::RESTAPI::Errors::msg &E) {
|
|
PrepareResponse(Poco::Net::HTTPResponse::HTTP_FORBIDDEN);
|
|
Poco::JSON::Object ErrorObject;
|
|
ErrorObject.set("ErrorCode",E.err_num);
|
|
ErrorObject.set("ErrorDetails",Request->getMethod());
|
|
ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ;
|
|
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());
|
|
const auto & E = OpenWifi::RESTAPI::Errors::Error404;
|
|
ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ;
|
|
std::ostream &Answer = Response->send();
|
|
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
|
poco_debug(Logger_,fmt::format("RES-NOTFOUND: User='{}@{}' Method='{}' Path='{}",
|
|
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);
|
|
SetCommonHeaders();
|
|
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", "no-store");
|
|
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);
|
|
SetCommonHeaders();
|
|
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", "no-store");
|
|
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);
|
|
SetCommonHeaders();
|
|
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", "no-store");
|
|
Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
|
Response->sendFile(File.path(),MT.ContentType);
|
|
}
|
|
|
|
inline void SendFile(Poco::TemporaryFile &TempAvatar, [[maybe_unused]] const std::string &Type, const std::string & Name) {
|
|
Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK);
|
|
SetCommonHeaders();
|
|
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", "no-store");
|
|
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);
|
|
SetCommonHeaders();
|
|
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", "no-store");
|
|
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);
|
|
SetCommonHeaders();
|
|
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 & Contacted, bool SubOnly = false );
|
|
|
|
inline void ReturnObject(Poco::JSON::Object &Object) {
|
|
PrepareResponse();
|
|
if(Request!= nullptr) {
|
|
// can we compress ???
|
|
auto AcceptedEncoding = Request->find("Accept-Encoding");
|
|
if(AcceptedEncoding!=Request->end()) {
|
|
if( AcceptedEncoding->second.find("gzip")!=std::string::npos ||
|
|
AcceptedEncoding->second.find("compress")!=std::string::npos) {
|
|
Response->set("Content-Encoding", "gzip");
|
|
std::ostream &Answer = Response->send();
|
|
Poco::DeflatingOutputStream deflater(Answer, Poco::DeflatingStreamBuf::STREAM_GZIP);
|
|
Poco::JSON::Stringifier::stringify(Object, deflater);
|
|
deflater.close();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
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<typename T> void ReturnObject(const char *Name, const std::vector<T> & 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 ;
|
|
|
|
Poco::Net::HTTPServerRequest *Request= nullptr;
|
|
Poco::Net::HTTPServerResponse *Response= nullptr;
|
|
SecurityObjects::UserInfoAndPolicy UserInfo_;
|
|
QueryBlock QB_;
|
|
const std::string & Requester() const { return REST_Requester_; }
|
|
protected:
|
|
BindingMap Bindings_;
|
|
Poco::URI::QueryParameters Parameters_;
|
|
Poco::Logger &Logger_;
|
|
std::string SessionToken_;
|
|
std::vector<std::string> Methods_;
|
|
bool Internal_=false;
|
|
bool RateLimited_=false;
|
|
bool QueryBlockInitialized_=false;
|
|
bool SubOnlyService_=false;
|
|
bool AlwaysAuthorize_=true;
|
|
Poco::JSON::Parser IncomingParser_;
|
|
RESTAPI_GenericServer & Server_;
|
|
RateLimit MyRates_;
|
|
uint64_t TransactionId_;
|
|
Poco::JSON::Object::Ptr ParsedBody_;
|
|
std::string REST_Requester_;
|
|
};
|
|
|
|
class RESTAPI_UnknownRequestHandler : public RESTAPIHandler {
|
|
public:
|
|
RESTAPI_UnknownRequestHandler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer & Server, uint64_t TransactionId, bool Internal)
|
|
: RESTAPIHandler(bindings, L, std::vector<std::string>{}, Server, TransactionId, Internal) {}
|
|
inline void DoGet() override {};
|
|
inline void DoPost() override {};
|
|
inline void DoPut() override {};
|
|
inline void DoDelete() override {};
|
|
};
|
|
|
|
template<class T>
|
|
constexpr auto test_has_PathName_method(T*)
|
|
-> decltype( T::PathName() , std::true_type{} )
|
|
{
|
|
return std::true_type{};
|
|
}
|
|
constexpr auto test_has_PathName_method(...) -> std::false_type
|
|
{
|
|
return std::false_type{};
|
|
}
|
|
|
|
template<typename T, typename... Args>
|
|
RESTAPIHandler * RESTAPI_Router(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings,
|
|
Poco::Logger & Logger, 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, TransactionId, false);
|
|
}
|
|
|
|
if constexpr (sizeof...(Args) == 0) {
|
|
return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server, TransactionId, false);
|
|
} else {
|
|
return RESTAPI_Router<Args...>(RequestedPath, Bindings, Logger, Server, TransactionId);
|
|
}
|
|
}
|
|
|
|
template<typename T, typename... Args>
|
|
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, TransactionId, true );
|
|
}
|
|
|
|
if constexpr (sizeof...(Args) == 0) {
|
|
return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server, TransactionId, true);
|
|
} else {
|
|
return RESTAPI_Router_I<Args...>(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 OpenAPIRequestDelete {
|
|
public:
|
|
explicit OpenAPIRequestDelete( 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(const std::string & BearerToken = "");
|
|
|
|
private:
|
|
std::string Type_;
|
|
std::string EndPoint_;
|
|
Types::StringPairVec QueryData_;
|
|
uint64_t msTimeout_;
|
|
Poco::JSON::Object Body_;
|
|
};
|
|
|
|
class KafkaMessage: public Poco::Notification {
|
|
public:
|
|
KafkaMessage( const std::string &Topic, const std::string &Key, const std::string & Payload) :
|
|
Topic_(Topic), Key_(Key), Payload_(Payload)
|
|
{
|
|
|
|
}
|
|
|
|
inline const std::string & Topic() { return Topic_; }
|
|
inline const std::string & Key() { return Key_; }
|
|
inline const std::string & Payload() { return Payload_; }
|
|
|
|
private:
|
|
std::string Topic_;
|
|
std::string Key_;
|
|
std::string Payload_;
|
|
|
|
};
|
|
|
|
class KafkaProducer : public Poco::Runnable {
|
|
public:
|
|
|
|
inline void run () override;
|
|
inline void Start() {
|
|
if(!Running_) {
|
|
Running_=true;
|
|
Worker_.start(*this);
|
|
}
|
|
}
|
|
|
|
inline void Stop() {
|
|
if(Running_) {
|
|
Running_=false;
|
|
Queue_.wakeUpAll();
|
|
Worker_.join();
|
|
}
|
|
}
|
|
|
|
inline void Produce(const std::string &Topic, const std::string &Key, const std::string &Payload) {
|
|
std::lock_guard G(Mutex_);
|
|
Queue_.enqueueNotification( new KafkaMessage(Topic,Key,Payload));
|
|
}
|
|
|
|
private:
|
|
std::recursive_mutex Mutex_;
|
|
Poco::Thread Worker_;
|
|
mutable std::atomic_bool Running_=false;
|
|
Poco::NotificationQueue Queue_;
|
|
};
|
|
|
|
class KafkaConsumer : public Poco::Runnable {
|
|
public:
|
|
inline void run() override;
|
|
|
|
void Start() {
|
|
if(!Running_) {
|
|
Running_=true;
|
|
Worker_.start(*this);
|
|
}
|
|
}
|
|
|
|
void Stop() {
|
|
if(Running_) {
|
|
Running_=false;
|
|
Worker_.wakeUp();
|
|
Worker_.join();
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::recursive_mutex Mutex_;
|
|
Poco::Thread Worker_;
|
|
mutable std::atomic_bool Running_=false;
|
|
};
|
|
|
|
class KafkaDispatcher : public Poco::Runnable {
|
|
public:
|
|
|
|
inline void Start() {
|
|
if(!Running_) {
|
|
Running_=true;
|
|
Worker_.start(*this);
|
|
}
|
|
}
|
|
|
|
inline void Stop() {
|
|
if(Running_) {
|
|
Running_=false;
|
|
Queue_.wakeUpAll();
|
|
Worker_.join();
|
|
}
|
|
}
|
|
|
|
inline auto RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F) {
|
|
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_++;
|
|
}
|
|
|
|
inline void UnregisterTopicWatcher(const std::string &Topic, int Id) {
|
|
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 Dispatch(const std::string &Topic, const std::string &Key, const std::string &Payload) {
|
|
std::lock_guard G(Mutex_);
|
|
auto It = Notifiers_.find(Topic);
|
|
if(It!=Notifiers_.end()) {
|
|
Queue_.enqueueNotification(new KafkaMessage(Topic, Key, Payload));
|
|
}
|
|
}
|
|
|
|
inline void run() override {
|
|
Poco::AutoPtr<Poco::Notification> Note(Queue_.waitDequeueNotification());
|
|
Utils::SetThreadName("kafka:dispatch");
|
|
while(Note && Running_) {
|
|
auto Msg = dynamic_cast<KafkaMessage*>(Note.get());
|
|
if(Msg!= nullptr) {
|
|
auto It = Notifiers_.find(Msg->Topic());
|
|
if (It != Notifiers_.end()) {
|
|
const auto & FL = It->second;
|
|
for(const auto &[CallbackFunc,_]:FL) {
|
|
CallbackFunc(Msg->Key(), Msg->Payload());
|
|
}
|
|
}
|
|
}
|
|
Note = Queue_.waitDequeueNotification();
|
|
}
|
|
}
|
|
|
|
inline void Topics(std::vector<std::string> &T) {
|
|
T.clear();
|
|
for(const auto &[TopicName,_]:Notifiers_)
|
|
T.push_back(TopicName);
|
|
}
|
|
|
|
private:
|
|
std::recursive_mutex Mutex_;
|
|
Types::NotifyTable Notifiers_;
|
|
Poco::Thread Worker_;
|
|
mutable std::atomic_bool Running_=false;
|
|
uint64_t FunctionId_=1;
|
|
Poco::NotificationQueue Queue_;
|
|
};
|
|
|
|
class KafkaManager : public SubSystemServer {
|
|
public:
|
|
|
|
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();
|
|
Dispatcher_.Start();
|
|
return 0;
|
|
}
|
|
|
|
inline void Stop() override {
|
|
if(KafkaEnabled_) {
|
|
poco_information(Logger(),"Stopping...");
|
|
Dispatcher_.Stop();
|
|
ProducerThr_.Stop();
|
|
ConsumerThr_.Stop();
|
|
poco_information(Logger(),"Stopped...");
|
|
return;
|
|
}
|
|
}
|
|
|
|
inline void PostMessage(const std::string &topic, const std::string & key, const std::string &PayLoad, bool WrapMessage = true ) {
|
|
if(KafkaEnabled_) {
|
|
ProducerThr_.Produce(topic,key,WrapMessage ? WrapSystemId(PayLoad) : PayLoad);
|
|
}
|
|
}
|
|
|
|
inline void Dispatch(const std::string &Topic, const std::string & Key, const std::string &Payload) {
|
|
Dispatcher_.Dispatch(Topic, Key, Payload);
|
|
}
|
|
|
|
[[nodiscard]] inline std::string WrapSystemId(const std::string & PayLoad) {
|
|
return SystemInfoWrapper_ + PayLoad + "}";
|
|
}
|
|
|
|
[[nodiscard]] inline bool Enabled() const { return KafkaEnabled_; }
|
|
|
|
inline uint64_t RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F) {
|
|
if(KafkaEnabled_) {
|
|
return Dispatcher_.RegisterTopicWatcher(Topic,F);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
inline void UnregisterTopicWatcher(const std::string &Topic, uint64_t Id) {
|
|
if(KafkaEnabled_) {
|
|
Dispatcher_.UnregisterTopicWatcher(Topic, Id);
|
|
}
|
|
}
|
|
|
|
inline void Topics(std::vector<std::string> &T) {
|
|
Dispatcher_.Topics(T);
|
|
}
|
|
|
|
private:
|
|
bool KafkaEnabled_ = false;
|
|
std::string SystemInfoWrapper_;
|
|
KafkaProducer ProducerThr_;
|
|
KafkaConsumer ConsumerThr_;
|
|
KafkaDispatcher Dispatcher_;
|
|
|
|
inline void PartitionAssignment(const cppkafka::TopicPartitionList& partitions) {
|
|
Logger().information(fmt::format("Partition assigned: {}...", partitions.front().get_partition()));
|
|
}
|
|
inline void PartitionRevocation(const cppkafka::TopicPartitionList& partitions) {
|
|
Logger().information(fmt::format("Partition revocation: {}...",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 {
|
|
poco_information(Logger(),"Stopping...");
|
|
std::lock_guard G(Mutex_);
|
|
Cache_.clear();
|
|
poco_information(Logger(),"Stopped...");
|
|
}
|
|
|
|
inline void RemovedCachedToken(const std::string &Token) {
|
|
Cache_.remove(Token);
|
|
}
|
|
|
|
inline static bool IsTokenExpired(const SecurityObjects::WebToken &T) {
|
|
return ((T.expires_in_+T.created_) < OpenWifi::Now());
|
|
}
|
|
|
|
inline bool RetrieveTokenInformation(const std::string & SessionToken,
|
|
SecurityObjects::UserInfoAndPolicy & UInfo,
|
|
std::uint64_t TID,
|
|
bool & Expired, bool & Contacted, bool Sub=false) {
|
|
try {
|
|
Types::StringPairVec QueryData;
|
|
QueryData.push_back(std::make_pair("token",SessionToken));
|
|
OpenAPIRequestGet Req( uSERVICE_SECURITY,
|
|
Sub ? "/api/v1/validateSubToken" : "/api/v1/validateToken",
|
|
QueryData,
|
|
10000);
|
|
Poco::JSON::Object::Ptr Response;
|
|
|
|
auto StatusCode = Req.Do(Response);
|
|
if(StatusCode==Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT) {
|
|
Contacted = false;
|
|
return false;
|
|
}
|
|
|
|
Contacted = true;
|
|
if(StatusCode==Poco::Net::HTTPServerResponse::HTTP_OK) {
|
|
if(Response->has("tokenInfo") && Response->has("userInfo")) {
|
|
UInfo.from_json(Response);
|
|
if(IsTokenExpired(UInfo.webtoken)) {
|
|
Expired = true;
|
|
return false;
|
|
}
|
|
Expired = false;
|
|
Cache_.update(SessionToken, UInfo);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
} catch (...) {
|
|
poco_error(Logger(),fmt::format("Failed to retrieve token={} for TID={}", SessionToken, TID));
|
|
}
|
|
Expired = false;
|
|
return false;
|
|
}
|
|
|
|
inline bool IsAuthorized(const std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo,
|
|
std::uint64_t TID,
|
|
bool & Expired, bool & Contacted, bool Sub = false) {
|
|
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, TID, Expired, Contacted, Sub);
|
|
}
|
|
|
|
private:
|
|
Poco::ExpireLRUCache<std::string,OpenWifi::SecurityObjects::UserInfoAndPolicy> 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, uint64_t id)
|
|
: Logger_(L), id_(id)
|
|
{
|
|
}
|
|
|
|
void handleRequest([[maybe_unused]] Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response) override
|
|
{
|
|
Utils::SetThreadName("alb-request");
|
|
try {
|
|
if((id_ % 100) == 0) {
|
|
poco_debug(Logger_,fmt::format("ALB-REQUEST({}): ALB Request {}.",
|
|
Request.clientAddress().toString(), id_));
|
|
}
|
|
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 << "process Alive and kicking!";
|
|
} catch (...) {
|
|
|
|
}
|
|
}
|
|
|
|
private:
|
|
Poco::Logger & Logger_;
|
|
uint64_t id_;
|
|
};
|
|
|
|
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_, req_id_++);
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
private:
|
|
Poco::Logger &Logger_;
|
|
inline static std::atomic_uint64_t req_id_=1;
|
|
};
|
|
|
|
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 {
|
|
poco_information(Logger(),"Stopping...");
|
|
if(Running_)
|
|
Server_->stopAll(true);
|
|
poco_information(Logger(),"Stopped...");
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<Poco::Net::HTTPServer> Server_;
|
|
std::unique_ptr<Poco::Net::ServerSocket> Socket_;
|
|
int Port_ = 0;
|
|
mutable std::atomic_bool Running_=false;
|
|
};
|
|
|
|
inline auto ALBHealthCheckServer() { return ALBHealthCheckServer::instance(); }
|
|
|
|
Poco::Net::HTTPRequestHandler * RESTAPI_ExtRouter(const std::string &Path, RESTAPIHandler::BindingMap &Bindings,
|
|
Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t Id);
|
|
|
|
Poco::Net::HTTPRequestHandler * RESTAPI_IntRouter(const std::string &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->stopAll(true);
|
|
Pool_.stopAll();
|
|
Pool_.joinAll();
|
|
RESTServers_.clear();
|
|
Logger().information("Stopped...");
|
|
}
|
|
|
|
|
|
inline void reinitialize(Poco::Util::Application &self) override;
|
|
|
|
inline Poco::Net::HTTPRequestHandler *CallServer(const std::string &Path, uint64_t Id) {
|
|
RESTAPIHandler::BindingMap Bindings;
|
|
Utils::SetThreadName(fmt::format("x-rest:{}",Id).c_str());
|
|
return RESTAPI_ExtRouter(Path, Bindings, Logger(), Server_, Id);
|
|
}
|
|
const Poco::ThreadPool & Pool() { return Pool_; }
|
|
|
|
private:
|
|
std::vector<std::unique_ptr<Poco::Net::HTTPServer>> RESTServers_;
|
|
Poco::ThreadPool Pool_{"x-rest",8,128};
|
|
RESTAPI_GenericServer Server_;
|
|
|
|
RESTAPI_ExtServer() noexcept:
|
|
SubSystemServer("RESTAPI_ExtServer", "REST-XSRV", "openwifi.restapi")
|
|
{
|
|
}
|
|
};
|
|
|
|
inline auto RESTAPI_ExtServer() { return RESTAPI_ExtServer::instance(); };
|
|
|
|
class ExtRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory {
|
|
public:
|
|
ExtRequestHandlerFactory() = default;
|
|
inline Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &Request) override {
|
|
try {
|
|
Poco::URI uri(Request.getURI());
|
|
auto TID = NextTransactionId_++;
|
|
Utils::SetThreadName(fmt::format("x-rest:{}",TID).c_str());
|
|
return RESTAPI_ExtServer()->CallServer(uri.getPath(), TID);
|
|
} catch (...) {
|
|
|
|
}
|
|
return nullptr;
|
|
}
|
|
private:
|
|
static inline std::atomic_uint64_t NextTransactionId_ = 1;
|
|
};
|
|
|
|
class LogMuxer : public Poco::Channel {
|
|
public:
|
|
|
|
inline std::string getProperty( [[maybe_unused]] const std::string &p ) const final {
|
|
return "";
|
|
}
|
|
|
|
inline void close() final {
|
|
}
|
|
|
|
inline void open() final {
|
|
}
|
|
|
|
inline static std::string to_string(Poco::Message::Priority p) {
|
|
switch(p) {
|
|
case Poco::Message::PRIO_INFORMATION: return "information";
|
|
case Poco::Message::PRIO_CRITICAL: return "critical";
|
|
case Poco::Message::PRIO_DEBUG: return "debug";
|
|
case Poco::Message::PRIO_ERROR: return "error";
|
|
case Poco::Message::PRIO_FATAL: return "level";
|
|
case Poco::Message::PRIO_NOTICE: return "notice";
|
|
case Poco::Message::PRIO_TRACE: return "trace";
|
|
case Poco::Message::PRIO_WARNING: return "warning";
|
|
default: return "none";
|
|
}
|
|
}
|
|
|
|
inline void log(const Poco::Message &m) final {
|
|
if(Enabled_) {
|
|
/*
|
|
nlohmann::json log_msg;
|
|
log_msg["msg"] = m.getText();
|
|
log_msg["level"] = to_string(m.getPriority());
|
|
log_msg["timestamp"] = Poco::DateTimeFormatter::format(m.getTime(), Poco::DateTimeFormat::ISO8601_FORMAT);
|
|
log_msg["source"] = m.getSource();
|
|
log_msg["thread_name"] = m.getThread();
|
|
log_msg["thread_id"] = m.getTid();
|
|
|
|
std::cout << log_msg << std::endl;
|
|
*/
|
|
std::lock_guard G(Mutex_);
|
|
std::vector<uint64_t> Remove;
|
|
for(const auto &[Id,CallBack]:CallBacks_) {
|
|
try {
|
|
CallBack(m);
|
|
} catch (...) {
|
|
Remove.push_back(Id);
|
|
}
|
|
}
|
|
for(const auto &i:Remove)
|
|
CallBacks_.erase(i);
|
|
}
|
|
}
|
|
|
|
inline void setProperty([[maybe_unused]] const std::string &name, [[maybe_unused]] const std::string &value) final {
|
|
|
|
}
|
|
|
|
inline static auto instance() {
|
|
static auto instance_ = new LogMuxer;
|
|
return instance_;
|
|
}
|
|
inline void Enable(bool enable) { Enabled_ = enable; }
|
|
typedef std::function<void(const Poco::Message &M)> logmuxer_callback_func_t;
|
|
inline void RegisterCallback(const logmuxer_callback_func_t & R, uint64_t &Id) {
|
|
std::lock_guard G(Mutex_);
|
|
Id = CallBackId_++;
|
|
CallBacks_[Id] = R;
|
|
}
|
|
private:
|
|
std::recursive_mutex Mutex_;
|
|
std::map<uint64_t,logmuxer_callback_func_t> CallBacks_;
|
|
inline static uint64_t CallBackId_=1;
|
|
bool Enabled_ = false;
|
|
};
|
|
inline auto LogMuxer() { return LogMuxer::instance(); }
|
|
|
|
|
|
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->stopAll(true);
|
|
Pool_.stopAll();
|
|
Pool_.joinAll();
|
|
Logger().information("Stopped...");
|
|
}
|
|
|
|
inline void reinitialize(Poco::Util::Application &self) override;
|
|
|
|
inline Poco::Net::HTTPRequestHandler *CallServer(const std::string &Path, uint64_t Id) {
|
|
RESTAPIHandler::BindingMap Bindings;
|
|
Utils::SetThreadName(fmt::format("i-rest:{}",Id).c_str());
|
|
return RESTAPI_IntRouter(Path, Bindings, Logger(), Server_, Id);
|
|
}
|
|
const Poco::ThreadPool & Pool() { return Pool_; }
|
|
private:
|
|
std::vector<std::unique_ptr<Poco::Net::HTTPServer>> RESTServers_;
|
|
Poco::ThreadPool Pool_{"i-rest",4,64};
|
|
RESTAPI_GenericServer Server_;
|
|
|
|
RESTAPI_IntServer() noexcept:
|
|
SubSystemServer("RESTAPI_IntServer", "REST-ISRV", "openwifi.internal.restapi")
|
|
{
|
|
}
|
|
};
|
|
|
|
inline auto RESTAPI_IntServer() { return RESTAPI_IntServer::instance(); };
|
|
|
|
class IntRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory {
|
|
public:
|
|
inline IntRequestHandlerFactory() = default;
|
|
inline Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &Request) override {
|
|
auto TID=NextTransactionId_++;
|
|
Utils::SetThreadName(fmt::format("i-rest:{}",TID).c_str());
|
|
Poco::URI uri(Request.getURI());
|
|
return RESTAPI_IntServer()->CallServer(uri.getPath(), TID);
|
|
}
|
|
private:
|
|
static inline std::atomic_uint64_t NextTransactionId_ = 1;
|
|
};
|
|
|
|
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<std::string, MicroServiceMeta> MicroServiceMetaMap;
|
|
typedef std::vector<MicroServiceMeta> MicroServiceMetaVec;
|
|
typedef std::vector<SubSystemServer *> 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)),
|
|
Logger_(Poco::Logger::get("FRAMEWORK")) {
|
|
instance_ = this;
|
|
RandomEngine_.seed(std::chrono::steady_clock::now().time_since_epoch().count());
|
|
// Logger_ = Poco::Logger::root().get("BASE-SVC");
|
|
}
|
|
|
|
[[nodiscard]] std::string Version() { return Version_; }
|
|
[[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 initialized = false;
|
|
|
|
if(!initialized) {
|
|
initialized = true;
|
|
InitializeLoggingSystem();
|
|
}
|
|
return Poco::Logger::get(Name);
|
|
}
|
|
*/
|
|
virtual void GetExtraConfiguration(Poco::JSON::Object & Cfg) {
|
|
Cfg.set("additionalConfiguration",false);
|
|
}
|
|
|
|
|
|
static inline void Exit(int Reason);
|
|
inline void BusMessageReceived(const std::string &Key, const std::string & Payload);
|
|
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 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_; }
|
|
inline void AddActivity(const std::string &Activity) {
|
|
if(!DataDir_.empty()) {
|
|
std::string ActivityFile{ DataDir_ + "/activity.log"};
|
|
try {
|
|
std::ofstream of(ActivityFile,std::ios_base::app | std::ios_base::out );
|
|
auto t = std::chrono::system_clock::now();
|
|
std::time_t now = std::chrono::system_clock::to_time_t(t);
|
|
of << Activity << " at " << std::ctime(&now) ;
|
|
} catch (...) {
|
|
|
|
}
|
|
}
|
|
}
|
|
inline bool NoAPISecurity() const { return NoAPISecurity_; }
|
|
[[nodiscard]] inline std::string Sign(Poco::JWT::Token &T, const std::string &Algo) {
|
|
if(NoBuiltInCrypto_) {
|
|
return T.toString();
|
|
} else {
|
|
return Signer_.sign(T,Algo);
|
|
}
|
|
}
|
|
|
|
inline Poco::ThreadPool & TimerPool() { return TimerPool_; }
|
|
|
|
private:
|
|
static MicroService * instance_;
|
|
bool HelpRequested_ = false;
|
|
std::string LogDir_;
|
|
std::string ConfigFileName_;
|
|
Poco::UUIDGenerator UUIDGenerator_;
|
|
uint64_t ID_ = 1;
|
|
Poco::SharedPtr<Poco::Crypto::RSAKey> AppKey_;
|
|
bool DebugMode_ = false;
|
|
std::string DataDir_;
|
|
std::string WWWAssetsDir_;
|
|
Poco::Crypto::CipherFactory & CipherFactory_ = Poco::Crypto::CipherFactory::defaultFactory();
|
|
Poco::Crypto::Cipher * Cipher_ = nullptr;
|
|
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 };
|
|
std::recursive_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;
|
|
SubSystemVec SubSystems_;
|
|
bool NoAPISecurity_=false;
|
|
bool NoBuiltInCrypto_=false;
|
|
Poco::JWT::Signer Signer_;
|
|
Poco::Logger &Logger_;
|
|
Poco::ThreadPool TimerPool_{"timer:pool",2,32};
|
|
std::unique_ptr<BusEventManager> BusEventManager_;
|
|
};
|
|
|
|
inline void MicroService::Exit(int Reason) {
|
|
std::exit(Reason);
|
|
}
|
|
|
|
inline void MicroService::BusMessageReceived([[maybe_unused]] const std::string &Key, const std::string & Payload) {
|
|
std::lock_guard G(InfraMutex_);
|
|
try {
|
|
Poco::JSON::Parser P;
|
|
auto Object = P.parse(Payload).extract<Poco::JSON::Object::Ptr>();
|
|
|
|
if (Object->has(KafkaTopics::ServiceEvents::Fields::ID) &&
|
|
Object->has(KafkaTopics::ServiceEvents::Fields::EVENT)) {
|
|
uint64_t ID = Object->get(KafkaTopics::ServiceEvents::Fields::ID);
|
|
auto Event = Object->get(KafkaTopics::ServiceEvents::Fields::EVENT).toString();
|
|
if (ID != ID_) {
|
|
if( Event==KafkaTopics::ServiceEvents::EVENT_JOIN ||
|
|
Event==KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE ||
|
|
Event==KafkaTopics::ServiceEvents::EVENT_LEAVE ) {
|
|
if( Object->has(KafkaTopics::ServiceEvents::Fields::TYPE) &&
|
|
Object->has(KafkaTopics::ServiceEvents::Fields::PUBLIC) &&
|
|
Object->has(KafkaTopics::ServiceEvents::Fields::PRIVATE) &&
|
|
Object->has(KafkaTopics::ServiceEvents::Fields::VRSN) &&
|
|
Object->has(KafkaTopics::ServiceEvents::Fields::KEY)) {
|
|
auto PrivateEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString();
|
|
if (Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE && Services_.find(PrivateEndPoint) != Services_.end()) {
|
|
Services_[PrivateEndPoint].LastUpdate = OpenWifi::Now();
|
|
} else if (Event == KafkaTopics::ServiceEvents::EVENT_LEAVE) {
|
|
Services_.erase(PrivateEndPoint);
|
|
poco_debug(logger(),fmt::format("Service {} ID={} leaving system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID));
|
|
} else if (Event == KafkaTopics::ServiceEvents::EVENT_JOIN || Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE) {
|
|
poco_debug(logger(),fmt::format("Service {} ID={} joining system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID));
|
|
Services_[PrivateEndPoint] = 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 = OpenWifi::Now() };
|
|
|
|
std::string SvcList;
|
|
for (const auto &Svc: Services_) {
|
|
if(SvcList.empty())
|
|
SvcList = Svc.second.Type;
|
|
else
|
|
SvcList += ", " + Svc.second.Type;
|
|
}
|
|
logger().information(fmt::format("Current list of microservices: {}", SvcList));
|
|
}
|
|
} else {
|
|
poco_error(logger(),fmt::format("KAFKA-MSG: invalid event '{}', 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 {
|
|
poco_error(logger(),fmt::format("KAFKA-MSG: invalid event '{}', missing token",Event));
|
|
}
|
|
} else {
|
|
poco_error(logger(),fmt::format("Unknown Event: {} Source: {}", Event, ID));
|
|
}
|
|
}
|
|
} else {
|
|
poco_error(logger(),"Bad bus message.");
|
|
}
|
|
|
|
auto i=Services_.begin();
|
|
auto now = OpenWifi::Now();
|
|
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 &[_,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 &[_,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() {
|
|
NoAPISecurity_ = ConfigGetBool("openwifi.security.restapi.disable",false);
|
|
std::string KeyFile = ConfigPath("openwifi.service.key","");
|
|
if(!KeyFile.empty()) {
|
|
std::string KeyFilePassword = ConfigPath("openwifi.service.key.password", "");
|
|
AppKey_ = Poco::SharedPtr<Poco::Crypto::RSAKey>(new Poco::Crypto::RSAKey("", KeyFile, KeyFilePassword));
|
|
Cipher_ = CipherFactory_.createCipher(*AppKey_);
|
|
Signer_.setRSAKey(AppKey_);
|
|
Signer_.addAllAlgorithms();
|
|
NoBuiltInCrypto_ = false;
|
|
} else {
|
|
NoBuiltInCrypto_ = true;
|
|
}
|
|
|
|
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_ = Utils::ComputeHash(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.%i %s: [%p][thr:%I] %t");
|
|
if (LoggingDestination == "console") {
|
|
Poco::AutoPtr<Poco::ConsoleChannel> Console(new Poco::ConsoleChannel);
|
|
Poco::AutoPtr<Poco::AsyncChannel> Async(new Poco::AsyncChannel(Console));
|
|
Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
|
|
Formatter->setProperty("pattern", LoggingFormat);
|
|
Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
|
|
new Poco::FormattingChannel(Formatter, Async));
|
|
Poco::Logger::root().setChannel(FormattingChannel);
|
|
} else if (LoggingDestination == "colorconsole") {
|
|
Poco::AutoPtr<Poco::ColorConsoleChannel> Console(new Poco::ColorConsoleChannel);
|
|
Poco::AutoPtr<Poco::AsyncChannel> Async(new Poco::AsyncChannel(Console));
|
|
Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
|
|
Formatter->setProperty("pattern", LoggingFormat);
|
|
Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
|
|
new Poco::FormattingChannel(Formatter, Async));
|
|
Poco::Logger::root().setChannel(FormattingChannel);
|
|
} else if (LoggingDestination == "sql") {
|
|
//"CREATE TABLE T_POCO_LOG (Source VARCHAR, Name VARCHAR, ProcessId INTEGER, Thread VARCHAR, ThreadId INTEGER, Priority INTEGER, Text VARCHAR, DateTime DATE)"
|
|
|
|
} else if (LoggingDestination == "syslog") {
|
|
|
|
} else {
|
|
auto LoggingLocation =
|
|
MicroService::instance().ConfigPath("logging.path", "$OWCERT_ROOT/logs") + "/log";
|
|
|
|
Poco::AutoPtr<Poco::FileChannel> FileChannel(new Poco::FileChannel);
|
|
FileChannel->setProperty("rotation", "10 M");
|
|
FileChannel->setProperty("archive", "timestamp");
|
|
FileChannel->setProperty("path", LoggingLocation);
|
|
Poco::AutoPtr<Poco::AsyncChannel> Async_File(new Poco::AsyncChannel(FileChannel));
|
|
// Poco::AutoPtr<Poco::AsyncChannel> Async_Muxer(new Poco::AsyncChannel(LogMuxer()));
|
|
// Poco::AutoPtr<Poco::SplitterChannel> Splitter(new Poco::SplitterChannel);
|
|
// Splitter->addChannel(Async_File);
|
|
// Splitter->addChannel(Async_Muxer);
|
|
Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
|
|
Formatter->setProperty("pattern", LoggingFormat);
|
|
Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
|
|
new Poco::FormattingChannel(Formatter, Async_File));
|
|
Poco::Logger::root().setChannel(FormattingChannel);
|
|
}
|
|
auto Level = Poco::Logger::parseLevel(MicroService::instance().ConfigGetString("logging.level", "debug"));
|
|
Poco::Logger::root().setLevel(Level);
|
|
}
|
|
}
|
|
|
|
void DaemonPostInitialization(Poco::Util::Application &self);
|
|
|
|
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());
|
|
#ifndef TIP_SECURITY_SERVICE
|
|
SubSystems_.push_back(AuthClient());
|
|
#endif
|
|
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);
|
|
DaemonPostInitialization(self);
|
|
|
|
Types::TopicNotifyFunction F = [this](const std::string &Key,const std::string &Payload) { this->BusMessageReceived(Key, Payload); };
|
|
KafkaManager()->RegisterTopicWatcher(KafkaTopics::SERVICE_EVENTS, F);
|
|
}
|
|
|
|
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<MicroService>(this, &MicroService::handleHelp)));
|
|
|
|
options.addOption(
|
|
Poco::Util::Option("file", "", "specify the configuration file")
|
|
.required(false)
|
|
.repeatable(false)
|
|
.argument("file")
|
|
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleConfig)));
|
|
|
|
options.addOption(
|
|
Poco::Util::Option("debug", "", "to run in debug, set to true")
|
|
.required(false)
|
|
.repeatable(false)
|
|
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleDebug)));
|
|
|
|
options.addOption(
|
|
Poco::Util::Option("logs", "", "specify the log directory and file (i.e. dir/file.log)")
|
|
.required(false)
|
|
.repeatable(false)
|
|
.argument("dir")
|
|
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleLogs)));
|
|
|
|
options.addOption(
|
|
Poco::Util::Option("version", "", "get the version and quit.")
|
|
.required(false)
|
|
.repeatable(false)
|
|
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleVersion)));
|
|
|
|
}
|
|
|
|
inline void MicroService::handleHelp([[maybe_unused]] const std::string &name, [[maybe_unused]] const std::string &value) {
|
|
HelpRequested_ = true;
|
|
displayHelp();
|
|
stopOptionsProcessing();
|
|
}
|
|
|
|
inline void MicroService::handleVersion([[maybe_unused]] const std::string &name, [[maybe_unused]] const std::string &value) {
|
|
HelpRequested_ = true;
|
|
std::cout << Version() << std::endl;
|
|
stopOptionsProcessing();
|
|
}
|
|
|
|
inline void MicroService::handleDebug([[maybe_unused]] const std::string &name, const std::string &value) {
|
|
if(value == "true")
|
|
DebugMode_ = true ;
|
|
}
|
|
|
|
inline void MicroService::handleLogs([[maybe_unused]] const std::string &name, const std::string &value) {
|
|
LogDir_ = value;
|
|
}
|
|
|
|
inline void MicroService::handleConfig([[maybe_unused]] 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() {
|
|
AddActivity("Starting");
|
|
for(auto i:SubSystems_) {
|
|
i->Start();
|
|
}
|
|
BusEventManager_ = std::make_unique<BusEventManager>(Poco::Logger::create("BusEventManager",Poco::Logger::root().getChannel(),Poco::Logger::root().getLevel()));
|
|
BusEventManager_->Start();
|
|
}
|
|
|
|
inline void MicroService::StopSubSystemServers() {
|
|
AddActivity("Stopping");
|
|
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::cerr << "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) {
|
|
if(NoBuiltInCrypto_) {
|
|
return S;
|
|
}
|
|
return Cipher_->encryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);;
|
|
}
|
|
|
|
inline std::string MicroService::Decrypt(const std::string &S) {
|
|
if(NoBuiltInCrypto_) {
|
|
return S;
|
|
}
|
|
return Cipher_->decryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);;
|
|
}
|
|
|
|
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(const std::string &Name, const std::string &LoggingPrefix,
|
|
const std::string &SubSystemConfigPrefix):
|
|
Name_(Name),
|
|
LoggerPrefix_(LoggingPrefix),
|
|
SubSystemConfigPrefix_(SubSystemConfigPrefix) {
|
|
}
|
|
|
|
inline int RESTAPI_ExtServer::Start() {
|
|
Logger().information("Starting.");
|
|
Server_.InitLogging();
|
|
|
|
for(const auto & Svr: ConfigServersList_) {
|
|
|
|
if(MicroService::instance().NoAPISecurity()) {
|
|
Logger().information(fmt::format("Starting: {}:{}. Security has been disabled for APIs.", Svr.Address(), Svr.Port()));
|
|
} else {
|
|
Logger().information(fmt::format("Starting: {}:{} Keyfile:{} CertFile: {}", Svr.Address(), Svr.Port(),
|
|
Svr.KeyFile(),Svr.CertFile()));
|
|
Svr.LogCert(Logger());
|
|
if (!Svr.RootCA().empty())
|
|
Svr.LogCas(Logger());
|
|
}
|
|
|
|
Poco::Net::HTTPServerParams::Ptr Params = new Poco::Net::HTTPServerParams;
|
|
Params->setKeepAlive(true);
|
|
Params->setName("ws:xrest");
|
|
|
|
std::unique_ptr<Poco::Net::HTTPServer> NewServer;
|
|
if(MicroService::instance().NoAPISecurity()) {
|
|
auto Sock{Svr.CreateSocket(Logger())};
|
|
NewServer = std::make_unique<Poco::Net::HTTPServer>(new ExtRequestHandlerFactory, Pool_, Sock, Params);
|
|
} else {
|
|
auto Sock{Svr.CreateSecureSocket(Logger())};
|
|
NewServer = std::make_unique<Poco::Net::HTTPServer>(new ExtRequestHandlerFactory, Pool_, Sock, Params);
|
|
};
|
|
NewServer->start();
|
|
RESTServers_.push_back(std::move(NewServer));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
inline int RESTAPI_IntServer::Start() {
|
|
Logger().information("Starting.");
|
|
Server_.InitLogging();
|
|
|
|
for(const auto & Svr: ConfigServersList_) {
|
|
|
|
if(MicroService::instance().NoAPISecurity()) {
|
|
Logger().information(fmt::format("Starting: {}:{}. Security has been disabled for APIs.", Svr.Address(), Svr.Port()));
|
|
} else {
|
|
Logger().information(fmt::format("Starting: {}:{}. Keyfile:{} CertFile: {}", Svr.Address(), Svr.Port(),
|
|
Svr.KeyFile(),Svr.CertFile()));
|
|
Svr.LogCert(Logger());
|
|
if (!Svr.RootCA().empty())
|
|
Svr.LogCas(Logger());
|
|
}
|
|
|
|
auto Params = new Poco::Net::HTTPServerParams;
|
|
Params->setKeepAlive(true);
|
|
Params->setName("ws:irest");
|
|
|
|
std::unique_ptr<Poco::Net::HTTPServer> NewServer;
|
|
if(MicroService::instance().NoAPISecurity()) {
|
|
auto Sock{Svr.CreateSocket(Logger())};
|
|
NewServer = std::make_unique<Poco::Net::HTTPServer>(new IntRequestHandlerFactory, Pool_, Sock, Params);
|
|
} else {
|
|
auto Sock{Svr.CreateSecureSocket(Logger())};
|
|
NewServer = std::make_unique<Poco::Net::HTTPServer>(new IntRequestHandlerFactory, Pool_, Sock, Params);
|
|
};
|
|
NewServer->start();
|
|
RESTServers_.push_back(std::move(NewServer));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
inline int MicroService::main([[maybe_unused]] const ArgVec &args) {
|
|
MicroServiceErrorHandler ErrorHandler(*this);
|
|
Poco::ErrorHandler::set(&ErrorHandler);
|
|
|
|
if (!HelpRequested_) {
|
|
SavePID();
|
|
|
|
Poco::Logger &logger = Poco::Logger::get(DAEMON_APP_NAME);
|
|
logger.notice(fmt::format("Starting {} version {}.",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(fmt::format("System ID set to {}",ID_));
|
|
StartSubSystemServers();
|
|
waitForTerminationRequest();
|
|
StopSubSystemServers();
|
|
logger.notice(fmt::format("Stopped {}...",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([[maybe_unused]] Poco::Util::Application &self) {
|
|
auto i = 0;
|
|
bool good = true;
|
|
|
|
auto NewLevel = MicroService::instance().ConfigGetString("logging.level." + Name_, "");
|
|
if(NewLevel.empty())
|
|
Logger_ = std::make_unique<LoggerWrapper>(Poco::Logger::create(LoggerPrefix_, Poco::Logger::root().getChannel(), Poco::Logger::root().getLevel()));
|
|
else
|
|
Logger_ = std::make_unique<LoggerWrapper>(Poco::Logger::create(LoggerPrefix_, Poco::Logger::root().getChannel(), Poco::Logger::parseLevel(NewLevel)));
|
|
|
|
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<Poco::Net::ServerSocket>(Port_);
|
|
auto Params = new Poco::Net::HTTPServerParams;
|
|
Params->setName("ws:alb");
|
|
Server_ = std::make_unique<Poco::Net::HTTPServer>(new ALBRequestHandlerFactory(Logger()), *Socket_, Params);
|
|
Server_->start();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
inline void BusEventManager::run() {
|
|
Running_ = true;
|
|
Utils::SetThreadName("fmwk:EventMgr");
|
|
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()) {
|
|
poco_information(Logger(),"Stopping...");
|
|
Running_ = false;
|
|
Thread_.wakeUp();
|
|
Thread_.join();
|
|
poco_information(Logger(),"Stopped...");
|
|
}
|
|
}
|
|
|
|
inline void KafkaManager::initialize(Poco::Util::Application & self) {
|
|
SubSystemServer::initialize(self);
|
|
KafkaEnabled_ = MicroService::instance().ConfigGetBool("openwifi.kafka.enable",false);
|
|
}
|
|
|
|
inline void KafkaLoggerFun([[maybe_unused]] cppkafka::KafkaHandleBase & handle, int level, const std::string & facility, const std::string &message) {
|
|
switch ((cppkafka::LogLevel) level) {
|
|
case cppkafka::LogLevel::LogNotice: {
|
|
poco_notice(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
|
}
|
|
break;
|
|
case cppkafka::LogLevel::LogDebug: {
|
|
poco_debug(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
|
}
|
|
break;
|
|
case cppkafka::LogLevel::LogInfo: {
|
|
poco_information(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
|
}
|
|
break;
|
|
case cppkafka::LogLevel::LogWarning: {
|
|
poco_warning(KafkaManager()->Logger(), fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
|
}
|
|
break;
|
|
case cppkafka::LogLevel::LogAlert:
|
|
case cppkafka::LogLevel::LogCrit: {
|
|
poco_critical(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
|
}
|
|
break;
|
|
case cppkafka::LogLevel::LogErr:
|
|
case cppkafka::LogLevel::LogEmerg:
|
|
default: {
|
|
poco_error(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
inline void KafkaErrorFun([[maybe_unused]] cppkafka::KafkaHandleBase & handle, int error, const std::string &reason) {
|
|
poco_error(KafkaManager()->Logger(),fmt::format("kafka-error: {}, reason: {}", error, reason));
|
|
}
|
|
|
|
inline void AddKafkaSecurity(cppkafka::Configuration & Config) {
|
|
auto CA = MicroService::instance().ConfigGetString("openwifi.kafka.ssl.ca.location","");
|
|
auto Certificate = MicroService::instance().ConfigGetString("openwifi.kafka.ssl.certificate.location","");
|
|
auto Key = MicroService::instance().ConfigGetString("openwifi.kafka.ssl.key.location","");
|
|
auto Password = MicroService::instance().ConfigGetString("openwifi.kafka.ssl.key.password","");
|
|
|
|
if(CA.empty() || Certificate.empty() || Key.empty())
|
|
return;
|
|
|
|
Config.set("ssl.ca.location", CA);
|
|
Config.set("ssl.certificate.location", Certificate);
|
|
Config.set("ssl.key.location", Key);
|
|
if(!Password.empty())
|
|
Config.set("ssl.key.password", Password);
|
|
}
|
|
|
|
inline void KafkaProducer::run() {
|
|
|
|
Utils::SetThreadName("Kafka:Prod");
|
|
cppkafka::Configuration Config({
|
|
{ "client.id", MicroService::instance().ConfigGetString("openwifi.kafka.client.id") },
|
|
{ "metadata.broker.list", MicroService::instance().ConfigGetString("openwifi.kafka.brokerlist") }
|
|
});
|
|
|
|
AddKafkaSecurity(Config);
|
|
|
|
Config.set_log_callback(KafkaLoggerFun);
|
|
Config.set_error_callback(KafkaErrorFun);
|
|
|
|
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;
|
|
|
|
Poco::AutoPtr<Poco::Notification> Note(Queue_.waitDequeueNotification());
|
|
while(Note && Running_) {
|
|
try {
|
|
auto Msg = dynamic_cast<KafkaMessage *>(Note.get());
|
|
if (Msg != nullptr) {
|
|
Producer.produce(
|
|
cppkafka::MessageBuilder(Msg->Topic()).key(Msg->Key()).payload(Msg->Payload()));
|
|
}
|
|
} catch (const cppkafka::HandleException &E) {
|
|
poco_warning(KafkaManager()->Logger(),fmt::format("Caught a Kafka exception (producer): {}", E.what()));
|
|
} catch( const Poco::Exception &E) {
|
|
KafkaManager()->Logger().log(E);
|
|
} catch (...) {
|
|
poco_error(KafkaManager()->Logger(),"std::exception");
|
|
}
|
|
Note = Queue_.waitDequeueNotification();
|
|
}
|
|
}
|
|
|
|
inline void KafkaConsumer::run() {
|
|
Utils::SetThreadName("Kafka:Cons");
|
|
|
|
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 }
|
|
});
|
|
|
|
AddKafkaSecurity(Config);
|
|
|
|
Config.set_log_callback(KafkaLoggerFun);
|
|
Config.set_error_callback(KafkaErrorFun);
|
|
|
|
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([](cppkafka::TopicPartitionList& partitions) {
|
|
if(!partitions.empty()) {
|
|
KafkaManager()->Logger().information(fmt::format("Partition assigned: {}...",
|
|
partitions.front().get_partition()));
|
|
}
|
|
});
|
|
Consumer.set_revocation_callback([](const cppkafka::TopicPartitionList& partitions) {
|
|
if(!partitions.empty()) {
|
|
KafkaManager()->Logger().information(fmt::format("Partition revocation: {}...",
|
|
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;
|
|
KafkaManager()->Topics(Topics);
|
|
Consumer.subscribe(Topics);
|
|
|
|
Running_ = true;
|
|
while(Running_) {
|
|
try {
|
|
std::vector<cppkafka::Message> MsgVec = Consumer.poll_batch(BatchSize, std::chrono::milliseconds(100));
|
|
for(auto const &Msg:MsgVec) {
|
|
if (!Msg)
|
|
continue;
|
|
if (Msg.get_error()) {
|
|
if (!Msg.is_eof()) {
|
|
poco_error(KafkaManager()->Logger(),fmt::format("Error: {}", Msg.get_error().to_string()));
|
|
}
|
|
if(!AutoCommit)
|
|
Consumer.async_commit(Msg);
|
|
continue;
|
|
}
|
|
KafkaManager()->Dispatch(Msg.get_topic(), Msg.get_key(),Msg.get_payload() );
|
|
if (!AutoCommit)
|
|
Consumer.async_commit(Msg);
|
|
}
|
|
} catch (const cppkafka::HandleException &E) {
|
|
poco_warning(KafkaManager()->Logger(),fmt::format("Caught a Kafka exception (consumer): {}", E.what()));
|
|
} catch (const Poco::Exception &E) {
|
|
KafkaManager()->Logger().log(E);
|
|
} catch (...) {
|
|
poco_error(KafkaManager()->Logger(),"std::exception");
|
|
}
|
|
}
|
|
Consumer.unsubscribe();
|
|
}
|
|
|
|
inline void RESTAPI_ExtServer::reinitialize([[maybe_unused]] Poco::Util::Application &self) {
|
|
MicroService::instance().LoadConfigurationFile();
|
|
Logger().information("Reinitializing.");
|
|
Stop();
|
|
Start();
|
|
}
|
|
|
|
void RESTAPI_IntServer::reinitialize([[maybe_unused]] 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<std::string>{Poco::Net::HTTPRequest::HTTP_POST,
|
|
Poco::Net::HTTPRequest::HTTP_GET,
|
|
Poco::Net::HTTPRequest::HTTP_OPTIONS},
|
|
Server,
|
|
TransactionId,
|
|
Internal) {}
|
|
static auto PathName() { return std::list<std::string>{"/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<std::string> CertNames;
|
|
|
|
for(const auto &i:SubSystems) {
|
|
auto Hosts=i->HostSize();
|
|
for(uint64_t j=0;j<Hosts;++j) {
|
|
auto CertFileName = i->Host(j).CertFile();
|
|
if(!CertFileName.empty()) {
|
|
Poco::File F1(CertFileName);
|
|
if(F1.exists()) {
|
|
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);
|
|
}
|
|
if(GetBoolParameter("extraConfiguration")) {
|
|
Poco::JSON::Object Answer;
|
|
MicroService::instance().GetExtraConfiguration(Answer);
|
|
return ReturnObject(Answer);
|
|
}
|
|
BadRequest(RESTAPI::Errors::InvalidCommand);
|
|
}
|
|
|
|
inline void DoPost() final {
|
|
const auto & Obj = ParsedBody_;
|
|
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<Poco::JSON::Object::Ptr>();
|
|
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(
|
|
fmt::format("Setting log level for {} at {}", 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<std::string> 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);
|
|
|
|
auto Secure = (URI.getScheme() == "https");
|
|
|
|
URI.setPath(EndPoint_);
|
|
for (const auto &qp : QueryData_)
|
|
URI.addQueryParameter(qp.first, qp.second);
|
|
|
|
std::string Path(URI.getPathAndQuery());
|
|
Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_GET,
|
|
Path,
|
|
Poco::Net::HTTPMessage::HTTP_1_1);
|
|
|
|
poco_debug(Poco::Logger::get("REST-CALLER-GET"),fmt::format(" {}", URI.toString()));
|
|
|
|
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);
|
|
}
|
|
|
|
if(Secure) {
|
|
Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort());
|
|
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
|
|
|
Session.sendRequest(Request);
|
|
|
|
Poco::Net::HTTPResponse Response;
|
|
std::istream &is = Session.receiveResponse(Response);
|
|
if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) {
|
|
Poco::JSON::Parser P;
|
|
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
|
}
|
|
return Response.getStatus();
|
|
} else {
|
|
Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort());
|
|
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
|
|
|
Session.sendRequest(Request);
|
|
|
|
Poco::Net::HTTPResponse Response;
|
|
std::istream &is = Session.receiveResponse(Response);
|
|
if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) {
|
|
Poco::JSON::Parser P;
|
|
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
|
}
|
|
return Response.getStatus();
|
|
}
|
|
}
|
|
}
|
|
catch (const Poco::Exception &E)
|
|
{
|
|
Poco::Logger::get("REST-CALLER-GET").log(E);
|
|
}
|
|
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);
|
|
|
|
auto Secure = (URI.getScheme() == "https");
|
|
|
|
URI.setPath(EndPoint_);
|
|
for (const auto &qp : QueryData_)
|
|
URI.addQueryParameter(qp.first, qp.second);
|
|
|
|
poco_debug(Poco::Logger::get("REST-CALLER-PUT"),fmt::format("{}", URI.toString()));
|
|
|
|
std::string Path(URI.getPathAndQuery());
|
|
|
|
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);
|
|
}
|
|
|
|
if(Secure) {
|
|
Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort());
|
|
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
|
|
|
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<Poco::JSON::Object::Ptr>();
|
|
} else {
|
|
Poco::JSON::Parser P;
|
|
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
|
}
|
|
return Response.getStatus();
|
|
} else {
|
|
Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort());
|
|
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
|
|
|
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<Poco::JSON::Object::Ptr>();
|
|
} else {
|
|
Poco::JSON::Parser P;
|
|
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
|
}
|
|
return Response.getStatus();
|
|
}
|
|
}
|
|
}
|
|
catch (const Poco::Exception &E)
|
|
{
|
|
Poco::Logger::get("REST-CALLER-PUT").log(E);
|
|
}
|
|
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);
|
|
|
|
|
|
auto Secure = (URI.getScheme() == "https");
|
|
|
|
URI.setPath(EndPoint_);
|
|
for (const auto &qp : QueryData_)
|
|
URI.addQueryParameter(qp.first, qp.second);
|
|
|
|
poco_debug(Poco::Logger::get("REST-CALLER-POST"),fmt::format(" {}", URI.toString()));
|
|
|
|
std::string Path(URI.getPathAndQuery());
|
|
|
|
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);
|
|
}
|
|
|
|
if(Secure) {
|
|
Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort());
|
|
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
|
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<Poco::JSON::Object::Ptr>();
|
|
} else {
|
|
Poco::JSON::Parser P;
|
|
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
|
}
|
|
return Response.getStatus();
|
|
} else {
|
|
Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort());
|
|
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
|
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<Poco::JSON::Object::Ptr>();
|
|
} else {
|
|
Poco::JSON::Parser P;
|
|
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
|
}
|
|
return Response.getStatus();
|
|
}
|
|
}
|
|
}
|
|
catch (const Poco::Exception &E)
|
|
{
|
|
Poco::Logger::get("REST-CALLER-POST").log(E);
|
|
}
|
|
return Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT;
|
|
}
|
|
|
|
inline Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestDelete::Do(const std::string & BearerToken) {
|
|
try {
|
|
auto Services = MicroService::instance().GetServices(Type_);
|
|
|
|
for(auto const &Svc:Services) {
|
|
Poco::URI URI(Svc.PrivateEndPoint);
|
|
|
|
auto Secure = (URI.getScheme() == "https");
|
|
|
|
URI.setPath(EndPoint_);
|
|
for (const auto &qp : QueryData_)
|
|
URI.addQueryParameter(qp.first, qp.second);
|
|
|
|
poco_debug(Poco::Logger::get("REST-CALLER-DELETE"),fmt::format(" {}", URI.toString()));
|
|
|
|
std::string Path(URI.getPathAndQuery());
|
|
|
|
Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_DELETE,
|
|
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);
|
|
}
|
|
|
|
if(Secure) {
|
|
Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort());
|
|
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
|
Session.sendRequest(Request);
|
|
Poco::Net::HTTPResponse Response;
|
|
Session.receiveResponse(Response);
|
|
return Response.getStatus();
|
|
} else {
|
|
Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort());
|
|
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
|
Session.sendRequest(Request);
|
|
Poco::Net::HTTPResponse Response;
|
|
Session.receiveResponse(Response);
|
|
return Response.getStatus();
|
|
}
|
|
}
|
|
}
|
|
catch (const Poco::Exception &E)
|
|
{
|
|
Poco::Logger::get("REST-CALLER-DELETE").log(E);
|
|
}
|
|
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, std::uint64_t TID, bool & Expired , bool Sub );
|
|
#endif
|
|
inline bool RESTAPIHandler::IsAuthorized( bool & Expired , [[maybe_unused]] bool & Contacted , bool Sub ) {
|
|
if(Internal_ && Request->has("X-INTERNAL-NAME")) {
|
|
auto Allowed = MicroService::instance().IsValidAPIKEY(*Request);
|
|
Contacted = true;
|
|
if(!Allowed) {
|
|
if(Server_.LogBadTokens(false)) {
|
|
poco_debug(Logger_,fmt::format("I-REQ-DENIED({}): TID={} Method={} Path={}",
|
|
Utils::FormatIPv6(Request->clientAddress().toString()),
|
|
TransactionId_,
|
|
Request->getMethod(), Request->getURI()));
|
|
}
|
|
} else {
|
|
auto Id = Request->get("X-INTERNAL-NAME", "unknown");
|
|
REST_Requester_ = Id;
|
|
if(Server_.LogIt(Request->getMethod(),true)) {
|
|
poco_debug(Logger_,fmt::format("I-REQ-ALLOWED({}): TID={} User='{}' Method={} Path={}",
|
|
Utils::FormatIPv6(Request->clientAddress().toString()),
|
|
TransactionId_,
|
|
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_, TransactionId_, Expired, Sub)) {
|
|
#else
|
|
if (AuthClient()->IsAuthorized( SessionToken_, UserInfo_, TransactionId_, Expired, Contacted, Sub)) {
|
|
#endif
|
|
REST_Requester_ = UserInfo_.userinfo.email;
|
|
if(Server_.LogIt(Request->getMethod(),true)) {
|
|
poco_debug(Logger_,fmt::format("X-REQ-ALLOWED({}): TID={} User='{}@{}' Method={} Path={}",
|
|
UserInfo_.userinfo.email,
|
|
TransactionId_,
|
|
Utils::FormatIPv6(Request->clientAddress().toString()),
|
|
Request->clientAddress().toString(),
|
|
Request->getMethod(),
|
|
Request->getURI()));
|
|
}
|
|
return true;
|
|
} else {
|
|
if(Server_.LogBadTokens(true)) {
|
|
poco_debug(Logger_,fmt::format("X-REQ-DENIED({}): TID={} Method={} Path={}",
|
|
Utils::FormatIPv6(Request->clientAddress().toString()),
|
|
TransactionId_,
|
|
Request->getMethod(),
|
|
Request->getURI()));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inline MicroService * MicroService::instance_ = nullptr;
|
|
|
|
template <typename ContentStruct> struct WebSocketNotification {
|
|
inline static uint64_t xid=1;
|
|
uint64_t notification_id=++xid;
|
|
std::string type;
|
|
ContentStruct content;
|
|
|
|
void to_json(Poco::JSON::Object &Obj) const;
|
|
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
|
};
|
|
|
|
template <typename ContentStruct> void WebSocketNotification<ContentStruct>::to_json(Poco::JSON::Object &Obj) const {
|
|
RESTAPI_utils::field_to_json(Obj,"notification_id",notification_id);
|
|
RESTAPI_utils::field_to_json(Obj,"type",type);
|
|
RESTAPI_utils::field_to_json(Obj,"content",content);
|
|
}
|
|
|
|
template <typename ContentStruct> bool WebSocketNotification<ContentStruct>::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
|
try {
|
|
RESTAPI_utils::field_from_json(Obj,"notification_id",notification_id);
|
|
RESTAPI_utils::field_from_json(Obj,"content",content);
|
|
RESTAPI_utils::field_from_json(Obj,"type",type);
|
|
return true;
|
|
} catch(...) {
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class WebSocketClientProcessor {
|
|
public:
|
|
virtual void Processor(const Poco::JSON::Object::Ptr &O, std::string &Answer, bool &Done ) = 0;
|
|
private:
|
|
};
|
|
|
|
/* class MyParallelSocketReactor {
|
|
public:
|
|
explicit MyParallelSocketReactor(uint32_t NumReactors = 8);
|
|
~MyParallelSocketReactor();
|
|
Poco::Net::SocketReactor &Reactor();
|
|
private:
|
|
uint32_t NumReactors_;
|
|
Poco::Net::SocketReactor *Reactors_;
|
|
Poco::ThreadPool ReactorPool_;
|
|
};
|
|
*/
|
|
|
|
class WebSocketClient;
|
|
|
|
class WebSocketClientServer : public SubSystemServer, Poco::Runnable {
|
|
public:
|
|
static auto instance() {
|
|
static auto instance_ = new WebSocketClientServer;
|
|
return instance_;
|
|
}
|
|
|
|
int Start() override;
|
|
void Stop() override;
|
|
void run() override;
|
|
// MyParallelSocketReactor &ReactorPool();
|
|
Poco::Net::SocketReactor & Reactor() { return Reactor_; }
|
|
void NewClient(Poco::Net::WebSocket &WS, const std::string &Id, const std::string &UserName);
|
|
bool Register(WebSocketClient *Client, const std::string &Id);
|
|
void SetProcessor(WebSocketClientProcessor *F);
|
|
void UnRegister(const std::string &Id);
|
|
void SetUser(const std::string &Id, const std::string &UserId);
|
|
[[nodiscard]] inline bool GeoCodeEnabled() const { return GeoCodeEnabled_; }
|
|
[[nodiscard]] inline std::string GoogleApiKey() const { return GoogleApiKey_; }
|
|
[[nodiscard]] bool Send(const std::string &Id, const std::string &Payload);
|
|
|
|
template <typename T> bool
|
|
SendUserNotification(const std::string &userName, const WebSocketNotification<T> &Notification) {
|
|
|
|
Poco::JSON::Object Payload;
|
|
Notification.to_json(Payload);
|
|
Poco::JSON::Object Msg;
|
|
Msg.set("notification",Payload);
|
|
std::ostringstream OO;
|
|
Msg.stringify(OO);
|
|
|
|
return SendToUser(userName,OO.str());
|
|
}
|
|
|
|
template <typename T> void SendNotification(const WebSocketNotification<T> &Notification) {
|
|
Poco::JSON::Object Payload;
|
|
Notification.to_json(Payload);
|
|
Poco::JSON::Object Msg;
|
|
Msg.set("notification",Payload);
|
|
std::ostringstream OO;
|
|
Msg.stringify(OO);
|
|
SendToAll(OO.str());
|
|
}
|
|
|
|
[[nodiscard]] bool SendToUser(const std::string &userName, const std::string &Payload);
|
|
void SendToAll(const std::string &Payload);
|
|
private:
|
|
mutable std::atomic_bool Running_ = false;
|
|
Poco::Thread Thr_;
|
|
// std::unique_ptr<MyParallelSocketReactor> ReactorPool_;
|
|
Poco::Net::SocketReactor Reactor_;
|
|
Poco::Thread ReactorThread_;
|
|
bool GeoCodeEnabled_ = false;
|
|
std::string GoogleApiKey_;
|
|
std::map<std::string, std::pair<WebSocketClient *, std::string>> Clients_;
|
|
WebSocketClientProcessor *Processor_ = nullptr;
|
|
WebSocketClientServer() noexcept;
|
|
};
|
|
|
|
inline auto WebSocketClientServer() { return WebSocketClientServer::instance(); }
|
|
|
|
class WebSocketClient {
|
|
public:
|
|
explicit WebSocketClient(Poco::Net::WebSocket &WS,
|
|
const std::string &Id,
|
|
const std::string &UserName,
|
|
Poco::Logger &L,
|
|
WebSocketClientProcessor *Processor);
|
|
virtual ~WebSocketClient();
|
|
[[nodiscard]] inline const std::string &Id();
|
|
[[nodiscard]] Poco::Logger &Logger();
|
|
inline bool Send(const std::string &Payload);
|
|
private:
|
|
std::unique_ptr<Poco::Net::WebSocket> WS_;
|
|
Poco::Net::SocketReactor &Reactor_;
|
|
std::string Id_;
|
|
std::string UserName_;
|
|
Poco::Logger &Logger_;
|
|
std::atomic_bool Authenticated_ = false;
|
|
SecurityObjects::UserInfoAndPolicy UserInfo_;
|
|
WebSocketClientProcessor *Processor_ = nullptr;
|
|
void OnSocketReadable(const Poco::AutoPtr<Poco::Net::ReadableNotification> &pNf);
|
|
void OnSocketShutdown(const Poco::AutoPtr<Poco::Net::ShutdownNotification> &pNf);
|
|
void OnSocketError(const Poco::AutoPtr<Poco::Net::ErrorNotification> &pNf);
|
|
};
|
|
|
|
inline void WebSocketClientServer::NewClient(Poco::Net::WebSocket & WS, const std::string &Id, const std::string &UserName ) {
|
|
std::lock_guard G(Mutex_);
|
|
auto Client = new WebSocketClient(WS,Id,UserName,Logger(), Processor_);
|
|
Clients_[Id] = std::make_pair(Client,"");
|
|
}
|
|
|
|
inline bool WebSocketClientServer::Register( WebSocketClient * Client, const std::string &Id) {
|
|
std::lock_guard G(Mutex_);
|
|
Clients_[Id] = std::make_pair(Client,"");
|
|
return true;
|
|
}
|
|
|
|
inline void WebSocketClientServer::SetProcessor( WebSocketClientProcessor * F) {
|
|
Processor_ = F;
|
|
}
|
|
|
|
inline void WebSocketClientServer::UnRegister(const std::string &Id) {
|
|
std::lock_guard G(Mutex_);
|
|
Clients_.erase(Id);
|
|
}
|
|
|
|
inline void WebSocketClientServer::SetUser(const std::string &Id, const std::string &UserId) {
|
|
std::lock_guard G(Mutex_);
|
|
|
|
auto it=Clients_.find(Id);
|
|
if(it!=Clients_.end()) {
|
|
Clients_[Id] = std::make_pair(it->second.first,UserId);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] inline bool SendToUser(const std::string &userName, const std::string &Payload);
|
|
inline WebSocketClientServer::WebSocketClientServer() noexcept:
|
|
SubSystemServer("WebSocketClientServer", "UI-WSCLNT-SVR", "websocketclients")
|
|
{
|
|
}
|
|
|
|
inline void WebSocketClientServer::run() {
|
|
Running_ = true ;
|
|
Utils::SetThreadName("ws:uiclnt-svr");
|
|
while(Running_) {
|
|
Poco::Thread::trySleep(2000);
|
|
|
|
if(!Running_)
|
|
break;
|
|
}
|
|
};
|
|
|
|
inline int WebSocketClientServer::Start() {
|
|
GoogleApiKey_ = MicroService::instance().ConfigGetString("google.apikey","");
|
|
GeoCodeEnabled_ = !GoogleApiKey_.empty();
|
|
// ReactorPool_ = std::make_unique<MyParallelSocketReactor>();
|
|
ReactorThread_.start(Reactor_);
|
|
Thr_.start(*this);
|
|
return 0;
|
|
};
|
|
|
|
inline void WebSocketClientServer::Stop() {
|
|
if(Running_) {
|
|
Reactor_.stop();
|
|
ReactorThread_.join();
|
|
Running_ = false;
|
|
Thr_.wakeUp();
|
|
Thr_.join();
|
|
}
|
|
};
|
|
|
|
|
|
inline void WebSocketClient::OnSocketError([[maybe_unused]] const Poco::AutoPtr<Poco::Net::ErrorNotification> &pNf) {
|
|
delete this;
|
|
}
|
|
|
|
inline bool WebSocketClientServer::Send(const std::string &Id, const std::string &Payload) {
|
|
std::lock_guard G(Mutex_);
|
|
|
|
auto It = Clients_.find(Id);
|
|
if(It!=Clients_.end())
|
|
return It->second.first->Send(Payload);
|
|
return false;
|
|
}
|
|
|
|
inline bool WebSocketClientServer::SendToUser(const std::string &UserName, const std::string &Payload) {
|
|
std::lock_guard G(Mutex_);
|
|
uint64_t Sent=0;
|
|
|
|
for(const auto &client:Clients_) {
|
|
if(client.second.second == UserName) {
|
|
try {
|
|
if (client.second.first->Send(Payload))
|
|
Sent++;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return Sent>0;
|
|
}
|
|
|
|
inline void WebSocketClientServer::SendToAll(const std::string &Payload) {
|
|
std::lock_guard G(Mutex_);
|
|
|
|
for(const auto &client:Clients_) {
|
|
try {
|
|
client.second.first->Send(Payload);
|
|
} catch (...) {
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void WebSocketClient::OnSocketReadable([[maybe_unused]] const Poco::AutoPtr<Poco::Net::ReadableNotification> &pNf) {
|
|
int flags;
|
|
int n;
|
|
bool Done=false;
|
|
try {
|
|
Poco::Buffer<char> IncomingFrame(0);
|
|
n = WS_->receiveFrame(IncomingFrame, flags);
|
|
auto Op = flags & Poco::Net::WebSocket::FRAME_OP_BITMASK;
|
|
|
|
if (n == 0) {
|
|
poco_debug(Logger(),fmt::format("CLOSE({}): {} UI Client is closing WS connection.", Id_, UserName_));
|
|
return delete this;
|
|
}
|
|
|
|
switch (Op) {
|
|
case Poco::Net::WebSocket::FRAME_OP_PING: {
|
|
WS_->sendFrame("", 0,
|
|
(int)Poco::Net::WebSocket::FRAME_OP_PONG |
|
|
(int)Poco::Net::WebSocket::FRAME_FLAG_FIN);
|
|
} break;
|
|
case Poco::Net::WebSocket::FRAME_OP_PONG: {
|
|
} break;
|
|
case Poco::Net::WebSocket::FRAME_OP_CLOSE: {
|
|
poco_debug(Logger(),fmt::format("CLOSE({}): {} UI Client is closing WS connection.", Id_, UserName_));
|
|
Done = true;
|
|
} break;
|
|
case Poco::Net::WebSocket::FRAME_OP_TEXT: {
|
|
IncomingFrame.append(0);
|
|
if (!Authenticated_) {
|
|
std::string Frame{IncomingFrame.begin()};
|
|
auto Tokens = Utils::Split(Frame, ':');
|
|
bool Expired = false, Contacted = false;
|
|
if (Tokens.size() == 2 &&
|
|
AuthClient()->IsAuthorized(Tokens[1], UserInfo_, 0, Expired, Contacted)) {
|
|
Authenticated_ = true;
|
|
UserName_ = UserInfo_.userinfo.email;
|
|
poco_debug(Logger(),fmt::format("START({}): {} UI Client is starting WS connection.", Id_, UserName_));
|
|
std::string S{"Welcome! Bienvenue! Bienvenidos!"};
|
|
WS_->sendFrame(S.c_str(), S.size());
|
|
WebSocketClientServer()->SetUser(Id_, UserInfo_.userinfo.email);
|
|
} else {
|
|
std::string S{"Invalid token. Closing connection."};
|
|
WS_->sendFrame(S.c_str(), S.size());
|
|
Done = true;
|
|
}
|
|
|
|
} else {
|
|
try {
|
|
Poco::JSON::Parser P;
|
|
auto Obj =
|
|
P.parse(IncomingFrame.begin()).extract<Poco::JSON::Object::Ptr>();
|
|
std::string Answer;
|
|
if (Processor_ != nullptr)
|
|
Processor_->Processor(Obj, Answer, Done);
|
|
if (!Answer.empty())
|
|
WS_->sendFrame(Answer.c_str(), (int)Answer.size());
|
|
else {
|
|
WS_->sendFrame("{}", 2);
|
|
}
|
|
} catch (const Poco::JSON::JSONException &E) {
|
|
Logger().log(E);
|
|
Done=true;
|
|
}
|
|
}
|
|
} break;
|
|
default: {
|
|
}
|
|
}
|
|
} catch (...) {
|
|
Done=true;
|
|
}
|
|
|
|
if(Done) {
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
inline void WebSocketClient::OnSocketShutdown([[maybe_unused]] const Poco::AutoPtr<Poco::Net::ShutdownNotification> &pNf) {
|
|
delete this;
|
|
}
|
|
|
|
|
|
inline WebSocketClient::WebSocketClient( Poco::Net::WebSocket & WS , const std::string &Id, const std::string &UserName, Poco::Logger & L, WebSocketClientProcessor * Processor) :
|
|
Reactor_(WebSocketClientServer()->Reactor()),
|
|
Id_(Id),
|
|
UserName_(UserName),
|
|
Logger_(L),
|
|
Processor_(Processor) {
|
|
try {
|
|
WS_ = std::make_unique<Poco::Net::WebSocket>(WS);
|
|
Reactor_.addEventHandler(*WS_,
|
|
Poco::NObserver<WebSocketClient, Poco::Net::ReadableNotification>(
|
|
*this, &WebSocketClient::OnSocketReadable));
|
|
Reactor_.addEventHandler(*WS_,
|
|
Poco::NObserver<WebSocketClient, Poco::Net::ShutdownNotification>(
|
|
*this, &WebSocketClient::OnSocketShutdown));
|
|
Reactor_.addEventHandler(*WS_,
|
|
Poco::NObserver<WebSocketClient, Poco::Net::ErrorNotification>(
|
|
*this, &WebSocketClient::OnSocketError));
|
|
WS_->setNoDelay(true);
|
|
WS_->setKeepAlive(true);
|
|
WS_->setBlocking(false);
|
|
|
|
} catch (...) {
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
inline WebSocketClient::~WebSocketClient() {
|
|
try {
|
|
WebSocketClientServer()->UnRegister(Id_);
|
|
Reactor_.removeEventHandler(*WS_,
|
|
Poco::NObserver<WebSocketClient,
|
|
Poco::Net::ReadableNotification>(*this,&WebSocketClient::OnSocketReadable));
|
|
Reactor_.removeEventHandler(*WS_,
|
|
Poco::NObserver<WebSocketClient,
|
|
Poco::Net::ShutdownNotification>(*this,&WebSocketClient::OnSocketShutdown));
|
|
Reactor_.removeEventHandler(*WS_,
|
|
Poco::NObserver<WebSocketClient,
|
|
Poco::Net::ErrorNotification>(*this,&WebSocketClient::OnSocketError));
|
|
(*WS_).shutdown();
|
|
(*WS_).close();
|
|
} catch(...) {
|
|
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] inline const std::string & WebSocketClient::Id() {
|
|
return Id_;
|
|
};
|
|
|
|
[[nodiscard]] inline Poco::Logger & WebSocketClient::Logger() {
|
|
return Logger_;
|
|
}
|
|
|
|
[[nodiscard]] inline bool WebSocketClient::Send(const std::string &Payload) {
|
|
try {
|
|
WS_->sendFrame(Payload.c_str(),Payload.size());
|
|
return true;
|
|
} catch (...) {
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class RESTAPI_webSocketServer : public RESTAPIHandler {
|
|
public:
|
|
inline RESTAPI_webSocketServer(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
|
|
: RESTAPIHandler(bindings, L,
|
|
std::vector<std::string>{ Poco::Net::HTTPRequest::HTTP_GET,
|
|
Poco::Net::HTTPRequest::HTTP_OPTIONS},
|
|
Server, TransactionId, Internal,false) {}
|
|
static auto PathName() { return std::list<std::string>{"/api/v1/ws"};}
|
|
void DoGet() final;
|
|
void DoDelete() final {};
|
|
void DoPost() final {};
|
|
void DoPut() final {};
|
|
private:
|
|
};
|
|
|
|
inline void RESTAPI_webSocketServer::DoGet() {
|
|
try
|
|
{
|
|
if(Request->find("Upgrade") != Request->end() && Poco::icompare((*Request)["Upgrade"], "websocket") == 0) {
|
|
try
|
|
{
|
|
Poco::Net::WebSocket WS(*Request, *Response);
|
|
auto Id = MicroService::CreateUUID();
|
|
WebSocketClientServer()->NewClient(WS,Id,UserInfo_.userinfo.email);
|
|
}
|
|
catch (...) {
|
|
std::cout << "Cannot create websocket client..." << std::endl;
|
|
}
|
|
}
|
|
} catch(...) {
|
|
std::cout << "Cannot upgrade connection..." << std::endl;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
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<const in_addr *>(RangeIP.addr()),
|
|
*static_cast<const in_addr *>(IP.addr()), MaskLength);
|
|
}
|
|
} else if (RangeIP.family() == Poco::Net::IPAddress::IPv6) {
|
|
unsigned long MaskLength;
|
|
if (ConvertStringToLong(TimeTokens[1].c_str(), MaskLength)) {
|
|
return cidr6_match(*static_cast<const in6_addr *>(RangeIP.addr()),
|
|
*static_cast<const in6_addr *>(IP.addr()), MaskLength);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
}
|