Code refactoring.

This commit is contained in:
Преподобный Ален
2022-09-18 23:01:27 +03:00
parent f0e690cf33
commit 7fd514a961
7 changed files with 1274 additions and 2164 deletions

990
src/app/Common.cpp Normal file
View File

@@ -0,0 +1,990 @@
/*++
Module Name:
Common.cpp
Notices:
Dial Module common.
Author:
Copyright (c) Prepodobny Alen
mailto: alienufo@inbox.ru
mailto: ufocomp@gmail.com
--*/
#include "Core.hpp"
#include "Common.hpp"
//----------------------------------------------------------------------------------------------------------------------
#ifdef __cplusplus
extern "C++" {
#endif // __cplusplus
namespace Apostol {
namespace Common {
//--------------------------------------------------------------------------------------------------------------
//-- CCustomModule ---------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------
CCustomModule::CCustomModule(CModuleProcess *AProcess, const CString& ModuleName, const CString& SectionName):
CApostolModule(AProcess, ModuleName, SectionName) {
m_SyncPeriod = BPS_DEFAULT_SYNC_PERIOD;
m_Status = psStopped;
CCustomModule::InitMethods();
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::InitMethods() {
#if defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 9)
m_pMethods->AddObject(_T("GET") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoGet(Connection); }));
m_pMethods->AddObject(_T("POST") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoPost(Connection); }));
m_pMethods->AddObject(_T("HEAD") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoHead(Connection); }));
m_pMethods->AddObject(_T("OPTIONS"), (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoOptions(Connection); }));
m_pMethods->AddObject(_T("PUT") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); }));
m_pMethods->AddObject(_T("DELETE") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); }));
m_pMethods->AddObject(_T("TRACE") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); }));
m_pMethods->AddObject(_T("PATCH") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); }));
m_pMethods->AddObject(_T("CONNECT"), (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); }));
#else
m_pMethods->AddObject(_T("GET"), (CObject *) new CMethodHandler(true, std::bind(&CCustomModule::DoGet, this, _1)));
m_pMethods->AddObject(_T("POST"), (CObject *) new CMethodHandler(true, std::bind(&CCustomModule::DoPost, this, _1)));
m_pMethods->AddObject(_T("HEAD"), (CObject *) new CMethodHandler(true, std::bind(&CCustomModule::DoHead, this, _1)));
m_pMethods->AddObject(_T("OPTIONS"), (CObject *) new CMethodHandler(true, std::bind(&CCustomModule::DoOptions, this, _1)));
m_pMethods->AddObject(_T("PUT"), (CObject *) new CMethodHandler(false, std::bind(&CCustomModule::MethodNotAllowed, this, _1)));
m_pMethods->AddObject(_T("DELETE"), (CObject *) new CMethodHandler(false, std::bind(&CCustomModule::MethodNotAllowed, this, _1)));
m_pMethods->AddObject(_T("TRACE"), (CObject *) new CMethodHandler(false, std::bind(&CCustomModule::MethodNotAllowed, this, _1)));
m_pMethods->AddObject(_T("PATCH"), (CObject *) new CMethodHandler(false, std::bind(&CCustomModule::MethodNotAllowed, this, _1)));
m_pMethods->AddObject(_T("CONNECT"), (CObject *) new CMethodHandler(false, std::bind(&CCustomModule::MethodNotAllowed, this, _1)));
#endif
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::Initialization(CModuleProcess *AProcess) {
CApostolModule::Initialization(AProcess);
if (Enabled()) {
Reload();
}
}
//--------------------------------------------------------------------------------------------------------------
bool CCustomModule::Enabled() {
if (m_ModuleStatus == msUnknown)
m_ModuleStatus = Config()->IniFile().ReadBool(SectionName().c_str(), "enable", true) ? msEnabled : msDisabled;
return m_ModuleStatus == msEnabled;
}
//--------------------------------------------------------------------------------------------------------------
CString CCustomModule::ToString(unsigned long Value) {
TCHAR szString[_INT_T_LEN + 1] = {0};
sprintf(szString, "%lu", Value);
return { szString };
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::JsonStringToKey(const CString &String, CString& Key) {
const CJSON Json(String);
if (Json.HasOwnProperty("error")) {
const auto &error = Json["error"];
if (error.ValueType() == jvtObject)
throw Delphi::Exception::Exception(error["message"].AsString().c_str());
}
if (Json.HasOwnProperty("data")) {
Key = Json["data"].AsString();
}
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::CheckKeyForNull(LPCTSTR Key, LPCTSTR Value) {
if (Value == nullptr)
throw ExceptionFrm("Invalid format: key \"%s\" cannot be empty.", Key);
}
//--------------------------------------------------------------------------------------------------------------
bool CCustomModule::FindURLInLine(const CString &Line, CStringList &List) {
CString URL;
TCHAR ch;
int length = 0;
size_t startPos, pos;
pos = 0;
while ((startPos = Line.Find(HTTP_PREFIX, pos)) != CString::npos) {
URL.Clear();
pos = startPos + HTTP_PREFIX_SIZE;
if (Line.Length() < 5)
return false;
URL.Append(HTTP_PREFIX);
ch = Line.at(pos);
if (ch == 's') {
URL.Append(ch);
pos++;
}
if (Line.Length() < 7 || Line.at(pos++) != ':' || Line.at(pos++) != '/' || Line.at(pos++) != '/')
return false;
URL.Append("://");
length = 0;
ch = Line.at(pos);
while (ch != 0 && (IsChar(ch) || IsNumeral(ch) || ch == ':' || ch == '.' || ch == '-')) {
URL.Append(ch);
length++;
ch = Line.at(++pos);
}
if (length < 3) {
return false;
}
if (startPos == 0) {
List.Add(URL);
} else {
ch = Line.at(startPos - 1);
switch (ch) {
case ' ':
case ',':
case ';':
List.Add(URL);
break;
default:
return false;
}
}
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
int CCustomModule::CheckFee(const CString &Fee) {
if (!Fee.IsEmpty()) {
if (Fee.Length() >= 10)
return -1;
size_t numbers = 0;
size_t delimiter = 0;
size_t percent = 0;
size_t pos = 0;
TCHAR ch;
ch = Fee.at(pos);
while (ch != 0) {
if (IsNumeral(ch))
numbers++;
if (ch == '.')
delimiter++;
if (ch == '%')
percent++;
ch = Fee.at(++pos);
}
if (numbers == 0 || delimiter > 1 || percent > 1 || ((numbers + percent + delimiter) != Fee.Length()))
return -1;
return 1;
}
return 0;
}
//--------------------------------------------------------------------------------------------------------------
bool CCustomModule::CheckVerifyPGPSignature(int VerifyPGPSignature, CString &Message) {
if (VerifyPGPSignature == -2) {
Message = "PGP public key is not meaningful.";
} else if (VerifyPGPSignature == -1) {
Message = "PGP signature not valid.";
} else if (VerifyPGPSignature == 0) {
Message = "PGP signature not found.";
} else if (VerifyPGPSignature > 1) {
Message = "PGP signature status: Unknown.";
}
return VerifyPGPSignature == 1;
}
//--------------------------------------------------------------------------------------------------------------
int CCustomModule::VerifyPGPSignature(const CString &ClearText, const CString &Key, CString &Message) {
const OpenPGP::Key signer(Key.c_str());
const OpenPGP::CleartextSignature cleartext(ClearText.c_str());
if (!cleartext.meaningful())
return -2;
const int verified = OpenPGP::Verify::cleartext_signature(signer, cleartext);
if (verified == 1) {
Message = cleartext.get_message().c_str();
}
return verified;
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::ParsePGPKey(const CString &Key, CStringPairs& ServerList, CStringList& BTCKeys) {
if (Key.IsEmpty())
return;
const Apostol::PGP::Key pgp(Key.c_str());
if (!pgp.meaningful())
return;
CStringList Data;
CPGPUserIdList List;
CStringList KeyList;
pgp.ExportUID(List);
for (int i = 0; i < List.Count(); i++) {
const auto &uid = List[i];
DebugMessage("%s (%s)\n", uid.Name.c_str(), uid.Desc.c_str());
const auto &name = uid.Name.Lower();
const auto &data = uid.Desc.Lower();
if (name == "technical_data") {
SplitColumns(data, Data, ';');
if (Data.Count() == 3) {
m_SyncPeriod = StrToIntDef(Data[1].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD);
} else if (Data.Count() == 2) {
m_SyncPeriod = StrToIntDef(Data[0].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD);
} else if (Data.Count() == 1) {
m_SyncPeriod = StrToIntDef(Data[0].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD);
}
} if (uid.Name.Length() >= 35 && uid.Name.SubString(0, 3) == BM_PREFIX) {
CStringList urlList;
if (FindURLInLine(uid.Desc, urlList)) {
for (int l = 0; l < urlList.Count(); l++) {
ServerList.AddPair(uid.Name, urlList[l]);
}
}
} else if (name.Find("bitcoin_key") != CString::npos) {
const auto &key = wallet::ec_public(data.c_str());
if (verify(key))
KeyList.AddPair(name, key.encoded());
}
}
CString Name;
for (int i = 1; i <= KeyList.Count(); i++) {
Name = "bitcoin_key";
Name << i;
const auto &key = KeyList[Name];
if (!key.IsEmpty()) {
BTCKeys.Add(key);
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::UpdateOAuth2(CContext &Context, const CJSONObject &OAuth2) {
if (OAuth2["type"].AsString() == "service_account") {
UpdateProviders(Context.Providers(), OAuth2);
Context.SetCheckDate(0);
Context.SetStatus(Context::csInitialized);
}
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::UpdateProviders(CProviders &Providers, const CJSONObject &Data) {
const auto &caProviderName = CString(SYSTEM_PROVIDER_NAME);
const auto &caApplicationName = CString(SERVICE_APPLICATION_NAME);
int index = Providers.IndexOfName(caProviderName);
if (index == -1)
index = Providers.AddPair(caProviderName, CProvider(caProviderName));
auto& Provider = Providers[index].Value();
Provider.Applications().AddPair(caApplicationName, Data);
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::CreateAccessToken(const CProvider &Provider, const CString &Application, CContext &Context) {
auto OnDone = [this, &Context](CTCPConnection *Sender) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (Sender);
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
const CJSON Json(pReply->Content);
Context.Session() = Json["session"].AsString();
Context.Secret() = Json["secret"].AsString();
Context.Tokens().Values("access_token", Json["access_token"].AsString());
ModuleStatus(Context);
}
return true;
};
auto OnFail = [&Context](CTCPConnection *Sender, const Delphi::Exception::Exception &E) {
Context.SetStatus(csInitialized);
auto pConnection = dynamic_cast<CHTTPClientConnection *> (Sender);
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
DebugReply(pConnection->Reply());
Log()->Error(APP_LOG_ERR, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
};
auto OnHTTPClient = [this](const CLocation &URI) {
return GetClient(URI.hostname, URI.port);
};
CString server_uri(Context.URL().Origin());
const auto &token_uri = Provider.TokenURI(Application);
const auto &service_token = CToken::CreateToken(Provider, Application);
Context.Tokens().Values("service_token", service_token);
if (!token_uri.IsEmpty()) {
CToken::FetchAccessToken(token_uri.front() == '/' ? server_uri + token_uri : token_uri, service_token, OnHTTPClient, OnDone, OnFail);
}
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::FetchProviders(CDateTime Now, CContext &Context) {
for (int i = 0; i < Context.Providers().Count(); i++) {
auto &Provider = Context.Providers()[i].Value();
for (int j = 0; j < Provider.Applications().Count(); ++j) {
const auto &app = Provider.Applications().Members(j);
if (app["type"].AsString() == "service_account") {
if (Provider.KeyStatus() == ksUnknown) {
CreateAccessToken(Provider, app.String(), Context);
Provider.KeyStatusTime(Now);
Provider.KeyStatus(ksSuccess);
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::CheckProviders(CDateTime Now, CContext &Context) {
for (int i = 0; i < Context.Providers().Count(); i++) {
auto& Provider = Context.Providers()[i].Value();
if (Provider.KeyStatus() != ksUnknown) {
Provider.KeyStatusTime(Now);
Provider.KeyStatus(ksUnknown);
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::CheckDeal(const CDeal &Deal) {
const auto &Data = Deal.Data();
const auto DateTime = UTC();
const auto Date = StringToDate(Data.Date);
const auto Sum = BTCToDouble(Data.Payment.Sum);
if (Data.Order == doCreate) {
if (DateTime < Date)
throw ExceptionFrm("Invalid deal date.");
if ((DateTime - Date) > (CDateTime) 180 / SecsPerDay)
throw ExceptionFrm("Deal date expired.");
}
if (Data.Order == doComplete) {
const CDateTime LeaveBefore = StringToDate(Data.FeedBack.LeaveBefore);
if (DateTime > LeaveBefore)
throw ExceptionFrm("Date feedback expired.");
}
if (Odd(int(Data.Order)) || Data.Order == doExecute || Data.Order == doDelete)
throw ExceptionFrm("Invalid \"order\" value for deal module.");
if (Data.Order == doCancel) {
const CDateTime Until = StringToDate(Data.Payment.Until);
if (DateTime > Until)
throw ExceptionFrm("Deal cancellation expired.");
CString message(Data.Payment.Address);
if (!Data.FeedBack.Comments.IsEmpty()) {
message += LINEFEED;
message += Data.FeedBack.Comments;
}
if (Data.Seller.Signature.IsEmpty() || !VerifyMessage(message, Data.Seller.Address, Data.Seller.Signature))
throw ExceptionFrm("The deal is not signed by the seller.");
}
if (Data.Order == doFeedback) {
CString message(Data.Payment.Address);
if (!Data.FeedBack.Comments.IsEmpty()) {
message += LINEFEED;
message += Data.FeedBack.Comments;
}
if (Data.Customer.Signature.IsEmpty() || !VerifyMessage(message, Data.Customer.Address, Data.Customer.Signature))
throw ExceptionFrm("The deal is not signed by the customer.");
}
if (!valid_address(Data.Seller.Address))
throw ExceptionFrm("Invalid Seller address: %s.", Data.Seller.Address.c_str());
if (!valid_address(Data.Customer.Address))
throw ExceptionFrm("Invalid Customer address: %s.", Data.Customer.Address.c_str());
if (!valid_address(Data.Payment.Address))
throw ExceptionFrm("Invalid Payment address: %s.", Data.Payment.Address.c_str());
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::DoGet(CHTTPServerConnection *AConnection) {
auto pRequest = AConnection->Request();
CString sPath(pRequest->Location.pathname);
// Request sPath must be absolute and not contain "..".
if (sPath.empty() || sPath.front() != '/' || sPath.find(_T("..")) != CString::npos) {
AConnection->SendStockReply(CHTTPReply::bad_request);
return;
}
if (sPath.SubString(0, 5) == "/api/") {
DoAPI(AConnection);
return;
}
SendResource(AConnection, sPath);
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::DoPost(CHTTPServerConnection *AConnection) {
auto pRequest = AConnection->Request();
auto pReply = AConnection->Reply();
pReply->ContentType = CHTTPReply::json;
CStringList slRouts;
SplitColumns(pRequest->Location.pathname, slRouts, '/');
if (slRouts.Count() < 3) {
AConnection->SendStockReply(CHTTPReply::not_found);
return;
}
const auto &caService = slRouts[0].Lower();
const auto &caVersion = slRouts[1].Lower();
const auto &caCommand = slRouts[2].Lower();
const auto &caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : "";
if (caService != "api") {
AConnection->SendStockReply(CHTTPReply::not_found);
return;
}
if (caVersion != "v1") {
AConnection->SendStockReply(CHTTPReply::not_found);
return;
}
CString sRoute;
for (int i = 0; i < slRouts.Count(); ++i) {
sRoute.Append('/');
sRoute.Append(slRouts[i]);
}
try {
if (caCommand == "account") {
DoAccount(AConnection, "POST", sRoute);
} else if (caCommand == "deal") {
DoDeal(AConnection, "POST", sRoute, caAction);
} else if (caCommand == "signature") {
DoSignature(AConnection);
} else {
AConnection->SendStockReply(CHTTPReply::not_found);
}
} catch (std::exception &e) {
ReplyError(AConnection, CHTTPReply::bad_request, e.what());
}
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::DoAPI(CHTTPServerConnection *AConnection) {
auto pRequest = AConnection->Request();
auto pReply = AConnection->Reply();
pReply->ContentType = CHTTPReply::json;
CStringList slRouts;
SplitColumns(pRequest->Location.pathname, slRouts, '/');
if (slRouts.Count() < 3) {
AConnection->SendStockReply(CHTTPReply::not_found);
return;
}
const auto &caService = slRouts[0].Lower();
const auto &caVersion = slRouts[1].Lower();
const auto &caCommand = slRouts[2].Lower();
const auto &caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : "";
if (caService != "api") {
AConnection->SendStockReply(CHTTPReply::not_found);
return;
}
if (caVersion != "v1") {
AConnection->SendStockReply(CHTTPReply::not_found);
return;
}
CString sRoute;
for (int i = 0; i < slRouts.Count(); ++i) {
sRoute.Append('/');
sRoute.Append(slRouts[i]);
}
try {
if (caCommand == "ping") {
AConnection->SendStockReply(CHTTPReply::ok);
} else if (caCommand == "time") {
pReply->Content << "{\"serverTime\": " << ToString(MsEpoch()) << "}";
AConnection->SendReply(CHTTPReply::ok);
} else if (caCommand == "help") {
pRequest->Content.Clear();
DoAccount(AConnection, "GET", sRoute);
} else if (caCommand == "account" && caAction == "status") {
pRequest->Content.Clear();
DoAccount(AConnection, "GET", sRoute);
} else if (caCommand == "deal" && caAction == "status") {
pRequest->Content.Clear();
DoDeal(AConnection, "GET", sRoute, caAction);
} else if (caCommand == "bc" && caAction == "history") {
const auto &caAccount = pRequest->Params["account"];
if (caAccount.IsEmpty()) {
AConnection->SendStockReply(CHTTPReply::bad_request);
return;
}
try {
const wallet::payment_address address(std::string(caAccount.c_str()));
CJSON history;
fetch_history(address, history);
pReply->Content = history.ToString();
} catch (Delphi::Exception::Exception &E) {
ExceptionToJson(CHTTPReply::bad_request, E, pReply->Content);
Log()->Error(APP_LOG_EMERG, 0, E.what());
}
AConnection->SendReply(CHTTPReply::ok, nullptr, true);
} else if (caCommand == "bc" && caAction == "header") {
const auto &caHeight = pRequest->Params["height"];
const auto &caHash = pRequest->Params["hash"];
if (caHeight.IsEmpty() && caHash.IsEmpty()) {
AConnection->SendStockReply(CHTTPReply::bad_request);
return;
}
try {
CJSON header;
if (!caHash.IsEmpty()) {
fetch_header(hash256(std::string(caHash.c_str())), header);
} else {
uint32_t height = StrToInt(caHeight.c_str());
fetch_header(height, header);
}
pReply->Content = header.ToString();
} catch (Delphi::Exception::Exception &E) {
ExceptionToJson(CHTTPReply::bad_request, E, pReply->Content);
Log()->Error(APP_LOG_EMERG, 0, E.what());
}
AConnection->SendReply(CHTTPReply::ok, nullptr, true);
} else {
AConnection->SendStockReply(CHTTPReply::not_found);
}
} catch (std::exception &e) {
ReplyError(AConnection, CHTTPReply::bad_request, e.what());
}
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::ModuleService(CContext &Context) {
Log()->Notice("[%s] [%s] Trying to fetch a OAuth2 configuration file.", Context.Name().c_str(), Context.URL().Origin().c_str());
auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) {
Context.SetStatus(Context::csPreparing);
ARequest->ContentType = CHTTPRequest::text;
Apostol::PGP::CleartextSignature(
m_pgpModuleKey,
m_pgpPassphrase,
BPS_PGP_HASH,
Context.Name(),
ARequest->Content);
CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/dm/service");
const auto &caModuleAddress = m_Module["address"];
if (!caModuleAddress.IsEmpty())
ARequest->AddHeader("Module-Address", caModuleAddress);
DebugRequest(ARequest);
};
auto OnExecute = [&Context](CTCPConnection *AConnection) {
Context.SetStatus(Context::csInitialization);
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
const CJSON OAuth2(pReply->Content);
UpdateOAuth2(Context, OAuth2.Object());
}
pConnection->CloseConnection(true);
}
return true;
};
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialization);
};
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
pClient->OnRequest(OnRequest);
pClient->OnExecute(OnExecute);
pClient->OnException(OnException);
pClient->AutoFree(true);
pClient->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::ModuleStatus(CContext &Context) {
Log()->Notice("[%s] [%s] Trying get module status.", Context.Name().c_str(), Context.URL().Origin().c_str());
auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) {
Context.SetStatus(Context::csInProgress);
const auto &caModuleAddress = m_Module["address"];
ARequest->ContentType = CHTTPRequest::text;
ARequest->Content.Format(R"({"address": "%s"})", caModuleAddress.c_str());
CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/dm/status");
ARequest->AddHeader("Authorization", "Bearer " + Context.Tokens()["access_token"]);
if (!caModuleAddress.IsEmpty())
ARequest->AddHeader("Module-Address", caModuleAddress);
DebugRequest(ARequest);
};
auto OnExecute = [this, &Context](CTCPConnection *AConnection) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
ModuleAuthorize(Context);
} else {
ModuleNew(Context);
}
pConnection->CloseConnection(true);
}
return true;
};
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialized);
};
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
pClient->OnRequest(OnRequest);
pClient->OnExecute(OnExecute);
pClient->OnException(OnException);
pClient->AutoFree(true);
pClient->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::ModuleNew(CContext &Context) {
Log()->Notice("[%s] [%s] Trying create new module.", Context.Name().c_str(), Context.URL().Origin().c_str());
auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) {
Context.SetStatus(Context::csInProgress);
ARequest->ContentType = CHTTPRequest::json;
const auto &caModuleAddress = m_Module["address"];
CJSON Json(jvtObject);
Json.Object().AddPair("address", caModuleAddress);
Json.Object().AddPair("bitmessage", Context.Name());
const OpenPGP::SecretKey pgpSecret(m_pgpModuleKey.c_str());
const auto &public_key = pgpSecret.get_public();
Json.Object().AddPair("pgp", public_key.write(OpenPGP::PGP::Armored::YES));
ARequest->Content = Json.ToString();
CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/client/new");
ARequest->AddHeader("Authorization", "Bearer " + Context.Tokens()["access_token"]);
if (!caModuleAddress.IsEmpty())
ARequest->AddHeader("Module-Address", caModuleAddress);
DebugRequest(ARequest);
};
auto OnExecute = [this, &Context](CTCPConnection *AConnection) {
Context.SetStatus(Context::csInitialized);
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
const CJSON Json(pReply->Content);
if (Json.HasOwnProperty("result")) {
const auto &caResult = Json["result"];
if (caResult.HasOwnProperty("success")) {
if (caResult["success"].AsBoolean()) {
ModuleAuthorize(Context);
} else {
if (caResult.HasOwnProperty("message")) {
Log()->Error(APP_LOG_EMERG, 0, "[%s] [%s] %s", Context.Name().c_str(),
Context.URL().Origin().c_str(), caResult["message"].AsString().c_str());
}
}
}
}
}
pConnection->CloseConnection(true);
}
return true;
};
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialized);
};
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
pClient->OnRequest(OnRequest);
pClient->OnExecute(OnExecute);
pClient->OnException(OnException);
pClient->AutoFree(true);
pClient->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void CCustomModule::ModuleAuthorize(CContext &Context) {
Log()->Notice("[%s] [%s] Trying authorize module.", Context.Name().c_str(), Context.URL().Origin().c_str());
auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) {
Context.SetStatus(Context::csAuthorization);
ARequest->ContentType = CHTTPRequest::text;
Apostol::PGP::CleartextSignature(
m_pgpModuleKey,
m_pgpPassphrase,
BPS_PGP_HASH,
Context.Name(),
ARequest->Content);
CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/dm/authorize");
ARequest->AddHeader("Authorization", "Bearer " + Context.Tokens()["access_token"]);
const auto &caModuleAddress = m_Module["address"];
if (!caModuleAddress.IsEmpty())
ARequest->AddHeader("Module-Address", caModuleAddress);
DebugRequest(ARequest);
};
auto OnExecute = [&Context](CTCPConnection *AConnection) {
Context.SetStatus(Context::csInitialized);
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
const CJSON Json(pReply->Content);
Context.Session() = Json["session"].AsString();
Context.Secret() = Json["secret"].AsString();
Context.Tokens().Values("access_token", Json["access_token"].AsString());
Context.SetFixedDate(0);
Context.SetCheckDate(Now() + (CDateTime) 55 / MinsPerDay); // 55 min
Context.SetStatus(csAuthorized);
} else {
Context.SetCheckDate(Now() + (CDateTime) 5 / MinsPerDay); // 5 min
}
pConnection->CloseConnection(true);
}
return true;
};
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialized);
};
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
pClient->OnRequest(OnRequest);
pClient->OnExecute(OnExecute);
pClient->OnException(OnException);
pClient->AutoFree(true);
pClient->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
} // namespace Common
} // namespace Apostol
#ifdef __cplusplus
}
#endif // __cplusplus

154
src/app/Common.hpp Normal file
View File

@@ -0,0 +1,154 @@
/*++
Module Name:
Common.hpp
Notices:
Dial Module common.
Author:
Copyright (c) Prepodobny Alen
mailto: alienufo@inbox.ru
mailto: ufocomp@gmail.com
--*/
#ifndef APOSTOL_DM_COMMON_HPP
#define APOSTOL_DM_COMMON_HPP
//----------------------------------------------------------------------------------------------------------------------
#define BPS_PGP_HASH "SHA512"
#define BM_PREFIX "BM-"
//----------------------------------------------------------------------------------------------------------------------
#ifdef __cplusplus
extern "C++" {
#endif // __cplusplus
namespace Apostol {
namespace Common {
//--------------------------------------------------------------------------------------------------------------
//-- CCustomModule ---------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------
class CCustomModule: public CApostolModule {
protected:
CStringList m_Module;
CJSON m_OAuth2;
CString m_pgpModuleKey;
CString m_pgpPublicKey;
CString m_pgpPassphrase;
CProcessStatus m_Status;
int m_SyncPeriod;
void InitMethods() override;
template<class ClassName>
void UpdateServerList(TPairs<ClassName> &ClassList, const CString &Key) {
CStringPairs ServerList;
CStringList Keys;
int index;
ParsePGPKey(Key, ServerList, Keys);
if (ServerList.Count() != 0) {
CStringPairs::ConstEnumerator em(ServerList);
while (em.MoveNext()) {
const auto &caCurrent = em.Current();
#ifndef _DEBUG
if (caCurrent.Name() == BPS_BM_DEBUG_ADDRESS)
continue;
#endif
index = ClassList.IndexOfName(caCurrent.Name());
if (index == -1) {
index = ClassList.AddPair(caCurrent.Name(), ClassName(caCurrent.Name(), CLocation(caCurrent.Value())));
auto &Context = ClassList[index].Value();
Context.PGP().Add(CKeyContext(CString("PUBLIC"), Key));
Context.PGP().Add(CKeyContext(caCurrent.Name(), CString()));
} else {
auto &Context = ClassList[index].Value();
Context.URL() = caCurrent.Value();
Context.PGP().First().Key = Key;
}
auto &Context = ClassList[index].Value();
Context.BTCKeys() = Keys;
if (Context.Status() == Context::csInitialization) {
UpdateOAuth2(Context, m_OAuth2.Object());
}
}
}
}
//----------------------------------------------------------------------------------------------------------
void FetchProviders(CDateTime Now, CContext &Context);
static void CheckProviders(CDateTime Now, CContext &Context);
void CreateAccessToken(const CProvider &Provider, const CString &Application, CContext &Context);
void ParsePGPKey(const CString &Key, CStringPairs &ServerList, CStringList &BTCKeys);
void ModuleService(CContext &Context);
void ModuleStatus(CContext &Context);
void ModuleNew(CContext &Context);
void ModuleAuthorize(CContext &Context);
void DoGet(CHTTPServerConnection *AConnection) override;
virtual void DoPost(CHTTPServerConnection *AConnection);
virtual void DoAPI(CHTTPServerConnection *AConnection);
virtual void DoAccount(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI) abstract;
virtual void DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action) abstract;
virtual void DoSignature(CHTTPServerConnection *AConnection) abstract;
public:
explicit CCustomModule(CModuleProcess *AProcess, const CString &ModuleName, const CString &SectionName);
~CCustomModule() override = default;
void Initialization(CModuleProcess *AProcess) override;
bool Enabled() override;
virtual void Reload() abstract;
static CString ToString(unsigned long Value);
static void JsonStringToKey(const CString &String, CString &Key);
static void CheckKeyForNull(LPCTSTR Key, LPCTSTR Value);
static void CheckDeal(const CDeal &Deal);
static int CheckFee(const CString &Fee);
static bool CheckVerifyPGPSignature(int VerifyPGPSignature, CString &Message);
static int VerifyPGPSignature(const CString &ClearText, const CString &Key, CString &Message);
static bool FindURLInLine(const CString &Line, CStringList &List);
static void UpdateOAuth2(CContext &Context, const CJSONObject &OAuth2);
static void UpdateProviders(CProviders &Providers, const CJSONObject &Data);
};
}
}
using namespace Apostol::Common;
#ifdef __cplusplus
}
#endif // __cplusplus
#endif //APOSTOL_DM_COMMON_HPP

View File

@@ -29,9 +29,6 @@ Author:
#include <execinfo.h>
//----------------------------------------------------------------------------------------------------------------------
//using namespace std;
//----------------------------------------------------------------------------------------------------------------------
#include "delphi.hpp"
//----------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,9 @@ Author:
#define DM_WEB_SERVICE_MODULE_HPP
//----------------------------------------------------------------------------------------------------------------------
#include "Common.hpp"
//----------------------------------------------------------------------------------------------------------------------
extern "C++" {
namespace Apostol {
@@ -37,26 +40,14 @@ namespace Apostol {
//--------------------------------------------------------------------------------------------------------------
class CWebService: public CApostolModule {
class CWebService: public CCustomModule {
private:
CStringList m_Module;
CJSON m_OAuth2;
CString m_pgpModuleKey;
CString m_pgpPublicKey;
CString m_pgpPassphrase;
CProcessStatus m_Status;
CContextPair m_DefaultServer { BPS_BM_SERVER_ADDRESS, CContext(BPS_BM_SERVER_ADDRESS, CLocation(BPS_SERVER_URL)) };
CContextList m_Servers {};
CContext m_CurrentServer {};
int m_SyncPeriod;
CDateTime m_NexServerTime;
CHTTPProxyManager m_ProxyManager;
@@ -64,39 +55,16 @@ namespace Apostol {
void InitMethods() override;
void InitServerList();
void UpdateServerList(const CString &Key);
void FetchProviders(CDateTime Now, CContext &Context);
void CheckProviders(CDateTime Now, CContext &Context);
void CreateAccessToken(const CProvider &Provider, const CString &Application, CContext &Context);
void ParsePGPKey(const CString& Key, CStringPairs& ServerList, CStringList& BTCKeys);
void FetchPGP(CContext &Context);
void ModuleService(CContext &Context);
void ModuleStatus(CContext &Context);
void ModuleNew(CContext &Context);
void ModuleAuthorize(CContext &Context);
protected:
CHTTPProxy *GetProxy(CHTTPServerConnection *AConnection);
const CContext &CurrentServer() const;
static int CheckFee(const CString& Fee);
static void CheckKeyForNull(LPCTSTR Key, LPCTSTR Value);
void DoAccount(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI);
void DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action);
void DoSignature(CHTTPServerConnection *AConnection);
void DoAPI(CHTTPServerConnection *AConnection);
void DoGet(CHTTPServerConnection *AConnection) override;
void DoPost(CHTTPServerConnection *AConnection);
void DoAccount(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI) override;
void DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action) override;
void DoSignature(CHTTPServerConnection *AConnection) override;
bool DoProxyExecute(CTCPConnection *AConnection);
void DoProxyException(CTCPConnection *AConnection, const Delphi::Exception::Exception &E);
@@ -107,33 +75,17 @@ namespace Apostol {
public:
explicit CWebService(CModuleProcess *AProcess);
explicit CWebService(CModuleProcess *AProcess, const CString &ModuleName, const CString &SectionName);
~CWebService() override = default;
static class CWebService *CreateModule(CModuleProcess *AProcess) {
return new CWebService(AProcess);
return new CWebService(AProcess, "web service", "module/WebService");
}
void Heartbeat(CDateTime Now) override;
void Initialization(CModuleProcess *AProcess) override;
bool Enabled() override;
void Reload();
static CString ToString(unsigned long Value);
static void JsonStringToKey(const CString& jsonString, CString& Key);
static void CheckDeal(const CDeal& Deal);
static bool CheckVerifyPGPSignature(int VerifyPGPSignature, CString &Message);
static int VerifyPGPSignature(const CString &ClearText, const CString &Key, CString &Message);
static bool FindURLInLine(const CString &Line, CStringList &List);
static void UpdateOAuth2(CContext &Context, const CJSONObject &OAuth2);
static void UpdateProviders(CProviders &Providers, const CJSONObject &Data);
void Reload() override;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,9 @@ Author:
#define DM_WEB_SOCKET_MODULE_HPP
//----------------------------------------------------------------------------------------------------------------------
#include "Common.hpp"
//----------------------------------------------------------------------------------------------------------------------
extern "C++" {
namespace Apostol {
@@ -64,42 +67,18 @@ namespace Apostol {
//--------------------------------------------------------------------------------------------------------------
class CWebSocketModule: public CApostolModule {
class CWebSocketModule: public CCustomModule {
private:
CStringList m_Module;
CJSON m_OAuth2;
CString m_pgpModuleKey;
CString m_pgpPublicKey;
CString m_pgpPassphrase;
int m_SyncPeriod;
CClientContextPair m_DefaultServer { BPS_BM_SERVER_ADDRESS, CClientContext(BPS_BM_SERVER_ADDRESS, CLocation(BPS_SERVER_URL)) };
CClientContextList m_Servers {};
void InitMethods() override;
void InitServerList();
void UpdateServerList(const CString &Key);
void FetchProviders(CDateTime Now, CClientContext &Context);
void CheckProviders(CDateTime Now, CClientContext &Context);
void CreateAccessToken(const CProvider &Provider, const CString &Application, CClientContext &Context);
void ParsePGPKey(const CString& Key, CStringPairs& ServerList, CStringList& BTCKeys);
CWebSocketClient *GetWebSocketClient(CClientContext &Context);
void CreateWebSocketClient(CClientContext &Context);
void ModuleService(CContext &Context);
void ModuleStatus(CContext &Context);
void ModuleNew(CContext &Context);
void ModuleAuthorize(CContext &Context);
static CWebSocketClient *GetConnectedClient(const CClientContext &Context);
int CurrentContextIndex(const CString &Params);
@@ -107,17 +86,9 @@ namespace Apostol {
protected:
void Heartbeat(CDateTime Now) override;
void DoAccount(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI);
void DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action);
void DoSignature(CHTTPServerConnection *AConnection);
void DoAPI(CHTTPServerConnection *AConnection);
void DoGet(CHTTPServerConnection *AConnection) override;
void DoPost(CHTTPServerConnection *AConnection);
void DoAccount(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI) override;
void DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action) override;
void DoSignature(CHTTPServerConnection *AConnection) override;
void DoWebSocketError(CTCPConnection *AConnection);
void DoClientConnected(CObject *Sender);
@@ -137,31 +108,18 @@ namespace Apostol {
public:
explicit CWebSocketModule(CModuleProcess *AProcess);
explicit CWebSocketModule(CModuleProcess *AProcess, const CString &ModuleName, const CString &SectionName);
~CWebSocketModule() override = default;
static class CWebSocketModule *CreateModule(CModuleProcess *AProcess) {
return new CWebSocketModule(AProcess);
return new CWebSocketModule(AProcess, "web socket", "module/WebSocket");
}
void Initialization(CModuleProcess *AProcess) override;
bool Enabled() override;
void Heartbeat(CDateTime Now) override;
void Reload();
void Reload() override;
static CString ToString(unsigned long Value);
static int CheckFee(const CString& Fee);
static void CheckDeal(const CDeal& Deal);
static void CheckKeyForNull(LPCTSTR key, const CString& Value);
static bool CheckVerifyPGPSignature(int VerifyPGPSignature, CString &Message);
static int VerifyPGPSignature(const CString &Text, const CString &Key, CString &Message);
static bool FindURLInLine(const CString &Line, CStringList &List);
static void UpdateOAuth2(CContext &Context, const CJSONObject &OAuth2);
static void UpdateProviders(CProviders &Providers, const CJSONObject &Data);
};
}
}