From 7fd514a96154533b641cb5fd54c9d2641992e901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=BF=D0=BE=D0=B4=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=90=D0=BB=D0=B5=D0=BD?= Date: Sun, 18 Sep 2022 23:01:27 +0300 Subject: [PATCH] Code refactoring. --- src/app/Common.cpp | 990 ++++++++++++++ src/app/Common.hpp | 154 +++ src/app/Header.hpp | 3 - src/modules/Workers/WebService/WebService.cpp | 1173 ++--------------- src/modules/Workers/WebService/WebService.hpp | 68 +- src/modules/Workers/WebSocket/WebSocket.cpp | 986 +------------- src/modules/Workers/WebSocket/WebSocket.hpp | 64 +- 7 files changed, 1274 insertions(+), 2164 deletions(-) create mode 100644 src/app/Common.cpp create mode 100644 src/app/Common.hpp diff --git a/src/app/Common.cpp b/src/app/Common.cpp new file mode 100644 index 0000000..33ecbff --- /dev/null +++ b/src/app/Common.cpp @@ -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 (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 (Sender); + auto pClient = dynamic_cast (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 (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 (AConnection); + if (pConnection != nullptr) { + auto pClient = dynamic_cast (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 (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 (AConnection); + if (pConnection != nullptr) { + auto pClient = dynamic_cast (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 (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 (AConnection); + if (pConnection != nullptr) { + auto pClient = dynamic_cast (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 (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 (AConnection); + if (pConnection != nullptr) { + auto pClient = dynamic_cast (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 diff --git a/src/app/Common.hpp b/src/app/Common.hpp new file mode 100644 index 0000000..e6bb1a1 --- /dev/null +++ b/src/app/Common.hpp @@ -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 + void UpdateServerList(TPairs &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 diff --git a/src/app/Header.hpp b/src/app/Header.hpp index faa4801..62cc5ac 100644 --- a/src/app/Header.hpp +++ b/src/app/Header.hpp @@ -29,9 +29,6 @@ Author: #include //---------------------------------------------------------------------------------------------------------------------- -//using namespace std; -//---------------------------------------------------------------------------------------------------------------------- - #include "delphi.hpp" //---------------------------------------------------------------------------------------------------------------------- diff --git a/src/modules/Workers/WebService/WebService.cpp b/src/modules/Workers/WebService/WebService.cpp index 719e680..71ab453 100644 --- a/src/modules/Workers/WebService/WebService.cpp +++ b/src/modules/Workers/WebService/WebService.cpp @@ -48,11 +48,10 @@ namespace Apostol { //-------------------------------------------------------------------------------------------------------------- - CWebService::CWebService(CModuleProcess *AProcess): CApostolModule(AProcess, "web service", "module/WebService") { + CWebService::CWebService(CModuleProcess *AProcess, const CString& ModuleName, const CString& SectionName): + CCustomModule(AProcess, ModuleName, SectionName) { + m_NexServerTime = 0; - m_SyncPeriod = BPS_DEFAULT_SYNC_PERIOD; - m_Status = psStopped; - CWebService::InitMethods(); InitServerList(); @@ -60,27 +59,7 @@ namespace Apostol { //-------------------------------------------------------------------------------------------------------------- void CWebService::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(&CWebService::DoGet, this, _1))); - m_pMethods->AddObject(_T("POST"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoPost, this, _1))); - m_pMethods->AddObject(_T("HEAD"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoHead, this, _1))); - m_pMethods->AddObject(_T("OPTIONS"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoOptions, this, _1))); - m_pMethods->AddObject(_T("PUT"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); - m_pMethods->AddObject(_T("DELETE"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); - m_pMethods->AddObject(_T("TRACE"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); - m_pMethods->AddObject(_T("PATCH"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); - m_pMethods->AddObject(_T("CONNECT"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); -#endif + CCustomModule::InitMethods(); } //-------------------------------------------------------------------------------------------------------------- @@ -90,7 +69,7 @@ namespace Apostol { if (m_Servers.Count() == 0) { #ifdef _DEBUG - int index = m_Servers.AddPair(BPS_BM_DEBUG_ADDRESS, CClientContext(BPS_BM_DEBUG_ADDRESS, CLocation(BPS_SERVER_URL))); + int index = m_Servers.AddPair(BPS_BM_DEBUG_ADDRESS, CContext(BPS_BM_DEBUG_ADDRESS, CLocation(BPS_SERVER_URL))); auto &Keys = m_Servers[index].Value().PGP(); Keys.Add(CKeyContext(CString("PUBLIC"), CString())); Keys.Add(CKeyContext(CString(BPS_BM_DEBUG_ADDRESS), CString())); @@ -101,245 +80,6 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebService::UpdateServerList(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 = m_Servers.IndexOfName(caCurrent.Name()); - if (index == -1) { - index = m_Servers.AddPair(caCurrent.Name(), CClientContext(caCurrent.Name(), CLocation(caCurrent.Value()))); - auto &Context = m_Servers[index].Value(); - Context.PGP().Add(CKeyContext(CString("PUBLIC"), Key)); - Context.PGP().Add(CKeyContext(caCurrent.Name(), CString())); - } else { - auto &Context = m_Servers[index].Value(); - Context.URL() = caCurrent.Value(); - Context.PGP().First().Key = Key; - } - - auto &Context = m_Servers[index].Value(); - Context.BTCKeys() = Keys; - - if (Context.Status() == Context::csInitialization) { - UpdateOAuth2(Context, m_OAuth2.Object()); - } - } - } - } - //-------------------------------------------------------------------------------------------------------------- - - void CWebService::CheckKeyForNull(LPCTSTR Key, LPCTSTR Value) { - if (Value == nullptr) - throw ExceptionFrm("Invalid format: key \"%s\" cannot be empty.", Key); - } - //-------------------------------------------------------------------------------------------------------------- - - void CWebService::UpdateOAuth2(CContext &Context, const CJSONObject &OAuth2) { - if (OAuth2["type"].AsString() == "service_account") { - UpdateProviders(Context.Providers(), OAuth2); - - Context.SetCheckDate(0); - Context.SetStatus(Context::csInitialized); - } - } - //-------------------------------------------------------------------------------------------------------------- - - void CWebService::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); - } - //-------------------------------------------------------------------------------------------------------------- - - bool CWebService::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 CWebService::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; - } - //-------------------------------------------------------------------------------------------------------------- - - void CWebService::CreateAccessToken(const CProvider &Provider, const CString &Application, CContext &Context) { - - auto OnDone = [this, &Context](CTCPConnection *Sender) { - - auto pConnection = dynamic_cast (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 (Sender); - auto pClient = dynamic_cast (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 CWebService::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 CWebService::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); - } - } - } - //-------------------------------------------------------------------------------------------------------------- - bool CWebService::DoProxyExecute(CTCPConnection *AConnection) { auto pConnection = dynamic_cast (AConnection); @@ -474,7 +214,7 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebService::DoAccount(CHTTPServerConnection *AConnection, const CString& Method, const CString& URI) { + void CWebService::DoAccount(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI) { auto pProxy = GetProxy(AConnection); auto pServerRequest = AConnection->Request(); @@ -488,7 +228,7 @@ namespace Apostol { const auto &pgpValue = pServerRequest->Params["pgp"]; const auto &caServerParam = pServerRequest->Params["server"]; - const auto& caServer = caServerParam.IsEmpty() ? m_CurrentServer.URL().Origin() : caServerParam; + const auto &caServer = caServerParam.IsEmpty() ? m_CurrentServer.URL().Origin() : caServerParam; CLocation Location(caServer); @@ -500,20 +240,20 @@ namespace Apostol { if (!pServerRequest->Content.IsEmpty()) { - const auto& ContentType = pServerRequest->Headers.Values(_T("content-type")); + const auto &ContentType = pServerRequest->Headers.Values(_T("content-type")); if (ContentType.Find("application/x-www-form-urlencoded") == 0) { const CStringList &FormData = pServerRequest->FormData; - const auto& formDate = FormData["date"]; - const auto& formAddress = FormData["address"]; - const auto& formBitmessage = FormData["bitmessage"]; - const auto& formKey = FormData["key"]; - const auto& formPGP = FormData["pgp"]; - const auto& formURL = FormData["url"]; - const auto& formFlags = FormData["flags"]; - const auto& formSign = FormData["sign"]; + const auto &formDate = FormData["date"]; + const auto &formAddress = FormData["address"]; + const auto &formBitmessage = FormData["bitmessage"]; + const auto &formKey = FormData["key"]; + const auto &formPGP = FormData["pgp"]; + const auto &formURL = FormData["url"]; + const auto &formFlags = FormData["flags"]; + const auto &formSign = FormData["sign"]; if (!formDate.IsEmpty()) { caClearText << formDate; @@ -552,14 +292,14 @@ namespace Apostol { CFormData FormData; CHTTPRequestParser::ParseFormData(pServerRequest, FormData); - const auto& formDate = FormData.Data("date"); - const auto& formAddress = FormData.Data("address"); - const auto& formBitmessage = FormData.Data("bitmessage"); - const auto& formKey = FormData.Data("key"); - const auto& formPGP = FormData.Data("pgp"); - const auto& formURL = FormData.Data("url"); - const auto& formFlags = FormData.Data("flags"); - const auto& formSign = FormData.Data("sign"); + const auto &formDate = FormData.Data("date"); + const auto &formAddress = FormData.Data("address"); + const auto &formBitmessage = FormData.Data("bitmessage"); + const auto &formKey = FormData.Data("key"); + const auto &formPGP = FormData.Data("pgp"); + const auto &formURL = FormData.Data("url"); + const auto &formFlags = FormData.Data("flags"); + const auto &formSign = FormData.Data("sign"); if (!formDate.IsEmpty()) { caClearText << formDate; @@ -597,13 +337,13 @@ namespace Apostol { const CJSON contextJson(pServerRequest->Content); - const auto& jsonDate = contextJson["date"].AsString(); - const auto& jsonAddress = contextJson["address"].AsString(); - const auto& jsonBitmessage = contextJson["bitmessage"].AsString(); - const auto& jsonKey = contextJson["key"].AsString(); - const auto& jsonPGP = contextJson["pgp"].AsString(); - const auto& jsonFlags = contextJson["flags"].AsString(); - const auto& jsonSign = contextJson["sign"].AsString(); + const auto &jsonDate = contextJson["date"].AsString(); + const auto &jsonAddress = contextJson["address"].AsString(); + const auto &jsonBitmessage = contextJson["bitmessage"].AsString(); + const auto &jsonKey = contextJson["key"].AsString(); + const auto &jsonPGP = contextJson["pgp"].AsString(); + const auto &jsonFlags = contextJson["flags"].AsString(); + const auto &jsonSign = contextJson["sign"].AsString(); if (!jsonDate.IsEmpty()) { caClearText << jsonDate; @@ -700,68 +440,6 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebService::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 CWebService::DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action) { auto pProxy = GetProxy(AConnection); @@ -787,7 +465,7 @@ namespace Apostol { const auto &pgpValue = pServerRequest->Params["pgp"]; const auto &caServerParam = pServerRequest->Params["server"]; - const auto& caServer = caServerParam.IsEmpty() ? m_CurrentServer.URL().Origin() : caServerParam; + const auto &caServer = caServerParam.IsEmpty() ? m_CurrentServer.URL().Origin() : caServerParam; CLocation Location(caServer); @@ -800,27 +478,27 @@ namespace Apostol { if (!pServerRequest->Content.IsEmpty()) { - const auto& ContentType = pServerRequest->Headers.Values(_T("content-type")); + const auto &ContentType = pServerRequest->Headers.Values(_T("content-type")); if (ContentType.Find("application/x-www-form-urlencoded") == 0) { const CStringList &FormData = pServerRequest->FormData; - const auto& formType = FormData["type"]; - const auto& formAt = FormData["at"]; - const auto& formDate = FormData["date"]; - const auto& formSellerAddress = FormData["seller_address"]; - const auto& formSellerRating = FormData["seller_rating"]; - const auto& formSellerSignature = FormData["seller_signature"]; - const auto& formCustomerAddress = FormData["customer_address"]; - const auto& formCustomerRating = FormData["customer_rating"]; - const auto& formCustomerSignature = FormData["customer_signature"]; - const auto& formPaymentAddress = FormData["payment_address"]; - const auto& formPaymentUntil = FormData["payment_until"]; - const auto& formPaymentSum = FormData["payment_sum"]; - const auto& formFeedbackLeaveBefore = FormData["feedback_leave_before"]; - const auto& formFeedbackStatus = FormData["feedback_status"]; - const auto& formFeedbackComments = FormData["feedback_comments"]; + const auto &formType = FormData["type"]; + const auto &formAt = FormData["at"]; + const auto &formDate = FormData["date"]; + const auto &formSellerAddress = FormData["seller_address"]; + const auto &formSellerRating = FormData["seller_rating"]; + const auto &formSellerSignature = FormData["seller_signature"]; + const auto &formCustomerAddress = FormData["customer_address"]; + const auto &formCustomerRating = FormData["customer_rating"]; + const auto &formCustomerSignature = FormData["customer_signature"]; + const auto &formPaymentAddress = FormData["payment_address"]; + const auto &formPaymentUntil = FormData["payment_until"]; + const auto &formPaymentSum = FormData["payment_sum"]; + const auto &formFeedbackLeaveBefore = FormData["feedback_leave_before"]; + const auto &formFeedbackStatus = FormData["feedback_status"]; + const auto &formFeedbackComments = FormData["feedback_comments"]; CheckKeyForNull("order", Action.c_str()); CheckKeyForNull("type", formType.c_str()); @@ -893,21 +571,21 @@ namespace Apostol { CFormData FormData; CHTTPRequestParser::ParseFormData(pServerRequest, FormData); - const auto& formType = FormData.Data("type"); - const auto& formAt = FormData.Data("at"); - const auto& formDate = FormData.Data("date"); - const auto& formSellerAddress = FormData.Data("seller_address"); - const auto& formSellerRating = FormData.Data("seller_rating"); - const auto& formSellerSignature = FormData.Data("seller_signature"); - const auto& formCustomerAddress = FormData.Data("customer_address"); - const auto& formCustomerRating = FormData.Data("customer_rating"); - const auto& formCustomerSignature = FormData.Data("customer_signature"); - const auto& formPaymentAddress = FormData.Data("payment_address"); - const auto& formPaymentUntil = FormData.Data("payment_until"); - const auto& formPaymentSum = FormData.Data("payment_sum"); - const auto& formFeedbackLeaveBefore = FormData.Data("feedback_leave_before"); - const auto& formFeedbackStatus = FormData.Data("feedback_status"); - const auto& formFeedbackComments = FormData.Data("feedback_comments"); + const auto &formType = FormData.Data("type"); + const auto &formAt = FormData.Data("at"); + const auto &formDate = FormData.Data("date"); + const auto &formSellerAddress = FormData.Data("seller_address"); + const auto &formSellerRating = FormData.Data("seller_rating"); + const auto &formSellerSignature = FormData.Data("seller_signature"); + const auto &formCustomerAddress = FormData.Data("customer_address"); + const auto &formCustomerRating = FormData.Data("customer_rating"); + const auto &formCustomerSignature = FormData.Data("customer_signature"); + const auto &formPaymentAddress = FormData.Data("payment_address"); + const auto &formPaymentUntil = FormData.Data("payment_until"); + const auto &formPaymentSum = FormData.Data("payment_sum"); + const auto &formFeedbackLeaveBefore = FormData.Data("feedback_leave_before"); + const auto &formFeedbackStatus = FormData.Data("feedback_status"); + const auto &formFeedbackComments = FormData.Data("feedback_comments"); CheckKeyForNull("order", Action.c_str()); CheckKeyForNull("type", formType.c_str()); @@ -979,35 +657,35 @@ namespace Apostol { const CJSON jsonData(pServerRequest->Content); - const auto& formOrder = jsonData["order"].AsString().Lower(); - const auto& formType = jsonData["type"].AsString(); + const auto &formOrder = jsonData["order"].AsString().Lower(); + const auto &formType = jsonData["type"].AsString(); - const auto& formAt = jsonData["at"].AsString(); - const auto& formDate = jsonData["date"].AsString(); + const auto &formAt = jsonData["at"].AsString(); + const auto &formDate = jsonData["date"].AsString(); const CJSONValue &jsonSeller = jsonData["seller"]; - const auto& formSellerAddress = jsonSeller["address"].AsString(); - const auto& formSellerRating = jsonSeller["rating"].AsString(); - const auto& formSellerSignature = jsonSeller["signature"].AsString(); + const auto &formSellerAddress = jsonSeller["address"].AsString(); + const auto &formSellerRating = jsonSeller["rating"].AsString(); + const auto &formSellerSignature = jsonSeller["signature"].AsString(); const CJSONValue &jsonCustomer = jsonData["customer"]; - const auto& formCustomerAddress = jsonCustomer["address"].AsString(); - const auto& formCustomerRating = jsonCustomer["rating"].AsString(); - const auto& formCustomerSignature = jsonCustomer["signature"].AsString(); + const auto &formCustomerAddress = jsonCustomer["address"].AsString(); + const auto &formCustomerRating = jsonCustomer["rating"].AsString(); + const auto &formCustomerSignature = jsonCustomer["signature"].AsString(); const CJSONValue &jsonPayment = jsonData["payment"]; - const auto& formPaymentAddress = jsonPayment["address"].AsString(); - const auto& formPaymentUntil = jsonPayment["until"].AsString(); - const auto& formPaymentSum = jsonPayment["sum"].AsString(); + const auto &formPaymentAddress = jsonPayment["address"].AsString(); + const auto &formPaymentUntil = jsonPayment["until"].AsString(); + const auto &formPaymentSum = jsonPayment["sum"].AsString(); const CJSONValue &jsonFeedback = jsonData["feedback"]; - const auto& formFeedbackLeaveBefore = jsonFeedback["leave-before"].AsString(); - const auto& formFeedbackStatus = jsonFeedback["status"].AsString(); - const auto& formFeedbackComments = jsonFeedback["comments"].AsString(); + const auto &formFeedbackLeaveBefore = jsonFeedback["leave-before"].AsString(); + const auto &formFeedbackStatus = jsonFeedback["status"].AsString(); + const auto &formFeedbackComments = jsonFeedback["comments"].AsString(); const auto &action = Action.IsEmpty() ? formOrder : Action; @@ -1194,7 +872,7 @@ namespace Apostol { if (index == Keys.Count()) throw ExceptionFrm("PGP key not found."); - const auto& caServerKey = Keys[index].Key; + const auto &caServerKey = Keys[index].Key; if (caServerKey.IsEmpty()) throw ExceptionFrm("PGP key not added."); @@ -1202,12 +880,12 @@ namespace Apostol { CString message; CJSON Json(jvtObject); - const auto& caContentType = pRequest->Headers["content-type"]; + const auto &caContentType = pRequest->Headers["content-type"]; if (caContentType.Find("application/x-www-form-urlencoded") == 0) { const CStringList &FormData = pRequest->FormData; - const auto& caClearText = FormData["message"]; + const auto &caClearText = FormData["message"]; CheckKeyForNull("message", caClearText.c_str()); const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); @@ -1216,7 +894,7 @@ namespace Apostol { CFormData FormData; CHTTPRequestParser::ParseFormData(pRequest, FormData); - const auto& caClearText = FormData.Data("message"); + const auto &caClearText = FormData.Data("message"); CheckKeyForNull("message", caClearText.c_str()); const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); @@ -1224,13 +902,13 @@ namespace Apostol { } else if (caContentType.Find("application/json") == 0) { const CJSON jsonData(pRequest->Content); - const auto& caClearText = jsonData["message"].AsString(); + const auto &caClearText = jsonData["message"].AsString(); CheckKeyForNull("message", caClearText.c_str()); const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); Json.Object().AddPair("verified", bVerified); } else { - const auto& caClearText = pRequest->Content; + const auto &caClearText = pRequest->Content; const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); Json.Object().AddPair("verified", bVerified); } @@ -1244,336 +922,6 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebService::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) { - CHTTPReply::CStatusType status = CHTTPReply::internal_server_error; - - ExceptionToJson(0, e, pReply->Content); - - AConnection->SendReply(status); - Log()->Error(APP_LOG_EMERG, 0, e.what()); - } - } - //-------------------------------------------------------------------------------------------------------------- - - void CWebService::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 CWebService::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) { - ExceptionToJson(0, e, pReply->Content); - - AConnection->SendReply(CHTTPReply::internal_server_error); - Log()->Error(APP_LOG_EMERG, 0, e.what()); - } - } - //-------------------------------------------------------------------------------------------------------------- - - const CContext &CWebService::CurrentServer() const { - return m_CurrentServer; - } - //-------------------------------------------------------------------------------------------------------------- - - bool CWebService::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 CWebService::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 CWebService::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 CWebService::JsonStringToKey(const CString &jsonString, CString& Key) { - CJSON Json; - Json << jsonString; - - 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 CWebService::FetchPGP(CContext &Context) { Log()->Notice("[%s] [%s] Trying to fetch a PGP key.", Context.Name().c_str(), Context.URL().Origin().c_str()); @@ -1642,7 +990,7 @@ namespace Apostol { Key.StatusTime = Now(); Key.Key = Content; - UpdateServerList(Content); + UpdateServerList(m_Servers, Content); } Context.SetStatus(csRunning); @@ -1706,325 +1054,6 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebService::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 (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 (AConnection); - if (pConnection != nullptr) { - auto pClient = dynamic_cast (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 CWebService::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 (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 (AConnection); - if (pConnection != nullptr) { - auto pClient = dynamic_cast (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 CWebService::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 (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 (AConnection); - if (pConnection != nullptr) { - auto pClient = dynamic_cast (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 CWebService::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 (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 (AConnection); - if (pConnection != nullptr) { - auto pClient = dynamic_cast (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 CWebService::Reload() { Config()->IniFile().ReadSectionValues("module", &m_Module); @@ -2049,7 +1078,7 @@ namespace Apostol { if (FileExists(caPublicKey.c_str())) { m_pgpPublicKey.LoadFromFile(caPublicKey.c_str()); - UpdateServerList(m_pgpPublicKey); + UpdateServerList(m_Servers, m_pgpPublicKey); } else { Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, caPublicKey.c_str()); } @@ -2062,7 +1091,10 @@ namespace Apostol { void CWebService::Heartbeat(CDateTime Now) { for (int i = 0; i < m_Servers.Count(); i++) { auto &Context = m_Servers[i].Value(); - +#ifndef _DEBUG + if (Context.Name() == BPS_BM_DEBUG_ADDRESS) + continue; +#endif if (Now >= Context.CheckDate()) { Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec @@ -2095,29 +1127,6 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebService::Initialization(CModuleProcess *AProcess) { - CApostolModule::Initialization(AProcess); - - if (Enabled()) { - Reload(); - } - } - //-------------------------------------------------------------------------------------------------------------- - - bool CWebService::Enabled() { - if (m_ModuleStatus == msUnknown) - m_ModuleStatus = Config()->IniFile().ReadBool(SectionName().c_str(), "enable", true) ? msEnabled : msDisabled; - return m_ModuleStatus == msEnabled; - } - //-------------------------------------------------------------------------------------------------------------- - - CString CWebService::ToString(unsigned long Value) { - TCHAR szString[_INT_T_LEN + 1] = {0}; - sprintf(szString, "%lu", Value); - return { szString }; - } - //-------------------------------------------------------------------------------------------------------------- - } } } \ No newline at end of file diff --git a/src/modules/Workers/WebService/WebService.hpp b/src/modules/Workers/WebService/WebService.hpp index d8110f8..8f80678 100644 --- a/src/modules/Workers/WebService/WebService.hpp +++ b/src/modules/Workers/WebService/WebService.hpp @@ -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; }; } diff --git a/src/modules/Workers/WebSocket/WebSocket.cpp b/src/modules/Workers/WebSocket/WebSocket.cpp index b85094a..63101e4 100644 --- a/src/modules/Workers/WebSocket/WebSocket.cpp +++ b/src/modules/Workers/WebSocket/WebSocket.cpp @@ -30,10 +30,6 @@ Author: #include //---------------------------------------------------------------------------------------------------------------------- -#define BPS_PGP_HASH "SHA512" -#define BM_PREFIX "BM-" -//---------------------------------------------------------------------------------------------------------------------- - #define SYSTEM_PROVIDER_NAME "system" #define SERVICE_APPLICATION_NAME "service" #define CONFIG_SECTION_NAME "module" @@ -54,7 +50,9 @@ namespace Apostol { //-------------------------------------------------------------------------------------------------------------- - CWebSocketModule::CWebSocketModule(CModuleProcess *AProcess) : CApostolModule(AProcess, "web socket", "module/WebSocket") { + CWebSocketModule::CWebSocketModule(CModuleProcess *AProcess, const CString &ModuleName, const CString &SectionName): + CCustomModule(AProcess, ModuleName, SectionName) { + m_SyncPeriod = BPS_DEFAULT_SYNC_PERIOD; m_Headers.Add("Authorization"); @@ -64,31 +62,6 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebSocketModule::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(&CWebSocketModule::DoGet, this, _1))); - m_pMethods->AddObject(_T("POST") , (CObject *) new CMethodHandler(true, std::bind(&CWebSocketModule::DoPost, this, _1))); - m_pMethods->AddObject(_T("HEAD") , (CObject *) new CMethodHandler(true, std::bind(&CWebSocketModule::DoHead, this, _1))); - m_pMethods->AddObject(_T("OPTIONS"), (CObject *) new CMethodHandler(true, std::bind(&CWebSocketModule::DoOptions, this, _1))); - m_pMethods->AddObject(_T("PUT") , (CObject *) new CMethodHandler(false, std::bind(&CWebSocketModule::MethodNotAllowed, this, _1))); - m_pMethods->AddObject(_T("DELETE") , (CObject *) new CMethodHandler(false, std::bind(&CWebSocketModule::MethodNotAllowed, this, _1))); - m_pMethods->AddObject(_T("TRACE") , (CObject *) new CMethodHandler(false, std::bind(&CWebSocketModule::MethodNotAllowed, this, _1))); - m_pMethods->AddObject(_T("PATCH") , (CObject *) new CMethodHandler(false, std::bind(&CWebSocketModule::MethodNotAllowed, this, _1))); - m_pMethods->AddObject(_T("CONNECT"), (CObject *) new CMethodHandler(false, std::bind(&CWebSocketModule::MethodNotAllowed, this, _1))); -#endif - } - //-------------------------------------------------------------------------------------------------------------- - void CWebSocketModule::InitServerList() { m_DefaultServer.Value().PGP().Add(CKeyContext(CString("PUBLIC"), CString())); m_DefaultServer.Value().PGP().Add(CKeyContext(m_DefaultServer.Name(), CString())); @@ -106,476 +79,6 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebSocketModule::UpdateServerList(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 = m_Servers.IndexOfName(caCurrent.Name()); - if (index == -1) { - index = m_Servers.AddPair(caCurrent.Name(), CClientContext(caCurrent.Name(), CLocation(caCurrent.Value()))); - auto &Context = m_Servers[index].Value(); - Context.PGP().Add(CKeyContext(CString("PUBLIC"), Key)); - Context.PGP().Add(CKeyContext(caCurrent.Name(), CString())); - } else { - auto &Context = m_Servers[index].Value(); - Context.URL() = caCurrent.Value(); - Context.PGP().First().Key = Key; - } - - auto &Context = m_Servers[index].Value(); - Context.BTCKeys() = Keys; - - if (Context.Status() == Context::csInitialization) { - UpdateOAuth2(Context, m_OAuth2.Object()); - } - } - } - } - //-------------------------------------------------------------------------------------------------------------- - - void CWebSocketModule::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 (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 (AConnection); - if (pConnection != nullptr) { - auto pClient = dynamic_cast (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 CWebSocketModule::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 (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 (AConnection); - if (pConnection != nullptr) { - auto pClient = dynamic_cast (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 CWebSocketModule::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 (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 (AConnection); - if (pConnection != nullptr) { - auto pClient = dynamic_cast (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 CWebSocketModule::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 (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 (AConnection); - if (pConnection != nullptr) { - auto pClient = dynamic_cast (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 CWebSocketModule::UpdateOAuth2(CContext &Context, const CJSONObject &OAuth2) { - if (OAuth2["type"].AsString() == "service_account") { - UpdateProviders(Context.Providers(), OAuth2); - - Context.SetCheckDate(0); - Context.SetStatus(Context::csInitialized); - } - } - //-------------------------------------------------------------------------------------------------------------- - - void CWebSocketModule::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); - } - //-------------------------------------------------------------------------------------------------------------- - - bool CWebSocketModule::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 CWebSocketModule::VerifyPGPSignature(const CString &Text, const CString &Key, CString &Message) { - const OpenPGP::Key signer(Key.c_str()); - const OpenPGP::CleartextSignature cleartext(Text.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; - } - //-------------------------------------------------------------------------------------------------------------- - - bool CWebSocketModule::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; - } - //-------------------------------------------------------------------------------------------------------------- - CWebSocketClient *CWebSocketModule::GetWebSocketClient(CClientContext &Context) { auto pClient = Context.ClientManager().Add(&Context, CLocation(Context.URL().Origin() + "/session/" + Context.Session())); @@ -635,133 +138,6 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebSocketModule::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 CWebSocketModule::CreateAccessToken(const CProvider &Provider, const CString &Application, CClientContext &Context) { - - auto OnDone = [this, &Context](CTCPConnection *Sender) { - - auto pConnection = dynamic_cast (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 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); - } - } - //-------------------------------------------------------------------------------------------------------------- - - void CWebSocketModule::FetchProviders(CDateTime Now, CClientContext &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 CWebSocketModule::CheckProviders(CDateTime Now, CClientContext &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); - } - } - } - //-------------------------------------------------------------------------------------------------------------- - CWebSocketClient *CWebSocketModule::GetConnectedClient(const CClientContext &Context) { for (int i = 0; i < Context.ClientManager().Count(); i++) { auto pClient = Context.ClientManager()[i]; @@ -915,7 +291,7 @@ namespace Apostol { if (Response.Payload.HasOwnProperty("code")) { if (Response.Payload["code"].AsString() == Key.Name) { if (Key.Name == "PUBLIC") { - UpdateServerList(Response.Payload["data"].AsString()); + UpdateServerList(m_Servers, Response.Payload["data"].AsString()); } else { Key.Key = Response.Payload["data"].AsString(); } @@ -1440,20 +816,20 @@ namespace Apostol { const auto &action = Action.IsEmpty() ? formOrder : Action; - CheckKeyForNull("order", action); - CheckKeyForNull("type", formType); - CheckKeyForNull("at", formAt); - CheckKeyForNull("date", formDate); - CheckKeyForNull("seller.address", formSellerAddress); - CheckKeyForNull("customer.address", formCustomerAddress); - CheckKeyForNull("payment.sum", formPaymentSum); + CheckKeyForNull("order", action.c_str()); + CheckKeyForNull("type", formType.c_str()); + CheckKeyForNull("at", formAt.c_str()); + CheckKeyForNull("date", formDate.c_str()); + CheckKeyForNull("seller.address", formSellerAddress.c_str()); + CheckKeyForNull("customer.address", formCustomerAddress.c_str()); + CheckKeyForNull("payment.sum", formPaymentSum.c_str()); if (action == "cancel") { - CheckKeyForNull("seller.signature", formSellerSignature); + CheckKeyForNull("seller.signature", formSellerSignature.c_str()); } if (action == "feedback") { - CheckKeyForNull("customer.signature", formCustomerSignature); + CheckKeyForNull("customer.signature", formCustomerSignature.c_str()); } YAML::Node Deal = Node["deal"]; @@ -1662,215 +1038,13 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebSocketModule::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) { - CHTTPReply::CStatusType status = CHTTPReply::internal_server_error; - - ExceptionToJson(0, e, pReply->Content); - - AConnection->SendReply(status); - Log()->Error(APP_LOG_EMERG, 0, e.what()); - } - } - //-------------------------------------------------------------------------------------------------------------- - - void CWebSocketModule::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 CWebSocketModule::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 CWebSocketModule::Heartbeat(CDateTime Now) { for (int i = 0; i < m_Servers.Count(); i++) { auto &Context = m_Servers[i].Value(); - +#ifndef _DEBUG + if (Context.Name() == BPS_BM_DEBUG_ADDRESS) + continue; +#endif if (Now >= Context.CheckDate()) { Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec @@ -1936,7 +1110,7 @@ namespace Apostol { if (FileExists(caPublicKey.c_str())) { m_pgpPublicKey.LoadFromFile(caPublicKey.c_str()); - UpdateServerList(m_pgpPublicKey); + UpdateServerList(m_Servers, m_pgpPublicKey); } else { Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, caPublicKey.c_str()); } @@ -1946,130 +1120,6 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebSocketModule::Initialization(CModuleProcess *AProcess) { - CApostolModule::Initialization(AProcess); - - if (Enabled()) { - Reload(); - } - } - //-------------------------------------------------------------------------------------------------------------- - - bool CWebSocketModule::Enabled() { - if (m_ModuleStatus == msUnknown) - m_ModuleStatus = Config()->IniFile().ReadBool(SectionName().c_str(), "enable", false) ? msEnabled: msDisabled; - return m_ModuleStatus == msEnabled; - } - //-------------------------------------------------------------------------------------------------------------- - - int CWebSocketModule::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; - } - //-------------------------------------------------------------------------------------------------------------- - - void CWebSocketModule::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 CWebSocketModule::CheckKeyForNull(LPCTSTR key, const CString &Value) { - if (Value.IsEmpty()) - throw ExceptionFrm("Invalid format: key \"%s\" cannot be empty.", key); - } - //-------------------------------------------------------------------------------------------------------------- - - CString CWebSocketModule::ToString(unsigned long Value) { - TCHAR szString[_INT_T_LEN + 1] = {0}; - sprintf(szString, "%lu", Value); - return { szString }; - } - //-------------------------------------------------------------------------------------------------------------- - } } } \ No newline at end of file diff --git a/src/modules/Workers/WebSocket/WebSocket.hpp b/src/modules/Workers/WebSocket/WebSocket.hpp index b667ac0..838ab9b 100644 --- a/src/modules/Workers/WebSocket/WebSocket.hpp +++ b/src/modules/Workers/WebSocket/WebSocket.hpp @@ -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); }; } }