diff --git a/src/modules/Workers/WebService/WebService.cpp b/src/modules/Workers/WebService/WebService.cpp index 5113e52..9b816c4 100644 --- a/src/modules/Workers/WebService/WebService.cpp +++ b/src/modules/Workers/WebService/WebService.cpp @@ -50,7 +50,6 @@ namespace Apostol { CWebService::CWebService(CModuleProcess *AProcess): CApostolModule(AProcess, "web service", "module/WebService") { m_NexServerTime = 0; - m_ServerIndex = -1; m_SyncPeriod = BPS_DEFAULT_SYNC_PERIOD; m_Status = psStopped; @@ -122,125 +121,34 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebService::FetchOAuth2(CContext &Context) { - - Log()->Debug(APP_LOG_DEBUG_CORE, "Trying to fetch a OAuth2 configuration file for module \"%s\" from: %s", Context.Name().c_str(), Context.URL().Origin().c_str()); - - auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) { - - ARequest->ContentType = CHTTPRequest::text; - - Apostol::PGP::CleartextSignature( - m_pgpPrivateKey, - 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 = [this, &Context](CTCPConnection *AConnection) { - - auto pConnection = dynamic_cast (AConnection); - - if (pConnection != nullptr) { - auto pReply = pConnection->Reply(); - - DebugReply(pReply); - - if (Context.Status() == Context::csPreparing) { - if (pReply->Status == CHTTPReply::ok) { - const CJSON Json(pReply->Content); - Json.SaveToFile(m_OAuth2.c_str()); - UpdateOAuth2(m_OAuth2); - } else { - Context.SetStatus(Context::csInitialization); - } - } - - 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); - }; - - Context.SetStatus(Context::csPreparing); - - 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::UpdateOAuth2(const CString &FileName) { - for (int i = 0; i < m_Servers.Count(); i++) { - auto &Context = m_Servers[i].Value(); - if (Context.Status() == Context::csInitialization || Context.Status() == Context::csPreparing) { - if (LoadOAuth2(FileName, Context.Providers())) { - Context.SetStatus(Context::csInitialized); - Context.SetCheckDate(0); - } else { - if (Context.Status() != Context::csPreparing) { - FetchOAuth2(Context); - } - } - } - } - } - //-------------------------------------------------------------------------------------------------------------- - - bool CWebService::LoadOAuth2(const CString &FileName, CProviders &Providers) { - const auto &caProviderName = CString(SYSTEM_PROVIDER_NAME); - const auto &caApplicationName = CString(SERVICE_APPLICATION_NAME); - - if (FileExists(FileName.c_str())) { - CJSONObject Json; - Json.LoadFromFile(FileName.c_str()); - - int index = Providers.IndexOfName(caProviderName); - if (index == -1) - index = Providers.AddPair(caProviderName, CProvider(caProviderName)); - auto& Provider = Providers[index].Value(); - Provider.Applications().AddPair(caApplicationName, Json); - - return true; - } - - return false; - } - //-------------------------------------------------------------------------------------------------------------- - 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; @@ -726,7 +634,7 @@ namespace Apostol { sPayload = caClearText.Text(); } else { Apostol::PGP::CleartextSignature( - m_pgpPrivateKey, + m_pgpModuleKey, m_pgpPassphrase, BPS_PGP_HASH, caClearText.Text(), @@ -1174,7 +1082,7 @@ namespace Apostol { sPayload = caClearText; } else { Apostol::PGP::CleartextSignature( - m_pgpPrivateKey, + m_pgpModuleKey, m_pgpPassphrase, BPS_PGP_HASH, caClearText, @@ -1612,7 +1520,7 @@ namespace Apostol { void CWebService::FetchPGP(CContext &Context) { - Log()->Debug(APP_LOG_DEBUG_CORE, "Trying to fetch a PGP key \"%s\" from: %s", Context.Name().c_str(), Context.URL().Origin().c_str()); + Log()->Notice("[%s] [%s] Trying to fetch a PGP key.", Context.Name().c_str(), Context.URL().Origin().c_str()); auto OnRequest = [&Context](CHTTPClient *Sender, CHTTPRequest *ARequest) { @@ -1699,28 +1607,348 @@ 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); + } + + 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); - m_OAuth2 = Config()->IniFile().ReadString(CONFIG_SECTION_NAME, "oauth2", "oauth2/service.json"); - if (!path_separator(m_OAuth2.front())) { - m_OAuth2 = Config()->Prefix() + m_OAuth2; + CString FileName; + + FileName = Config()->IniFile().ReadString(CONFIG_SECTION_NAME, "oauth2", "oauth2/service.json"); + if (!path_separator(FileName.front())) { + FileName = Config()->Prefix() + FileName; } - const auto& caPrivateKey = Config()->IniFile().ReadString("pgp", "private", "module.sec"); - const auto& caPublicKey = Config()->IniFile().ReadString("pgp", "public", "dm.pub"); + if (FileExists(FileName.c_str())) { + m_OAuth2.LoadFromFile(FileName.c_str()); + } + + const auto &caPrivateKey = Config()->IniFile().ReadString("pgp", "private", "module.sec"); + const auto &caPublicKey = Config()->IniFile().ReadString("pgp", "public", "dm.pub"); m_pgpPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", ""); if (FileExists(caPrivateKey.c_str())) { - m_pgpPrivateKey.LoadFromFile(caPrivateKey.c_str()); + m_pgpModuleKey.LoadFromFile(caPrivateKey.c_str()); if (FileExists(caPublicKey.c_str())) { - CString Key; - Key.LoadFromFile(caPublicKey.c_str()); - - UpdateServerList(Key); - UpdateOAuth2(m_OAuth2); + m_pgpPublicKey.LoadFromFile(caPublicKey.c_str()); + UpdateServerList(m_pgpPublicKey); } else { Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, caPublicKey.c_str()); } @@ -1734,23 +1962,21 @@ namespace Apostol { for (int i = 0; i < m_Servers.Count(); i++) { auto &Context = m_Servers[i].Value(); - if ((Now >= Context.CheckDate()) && (Context.Status() == Context::csInitialization)) { - Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec - FetchOAuth2(Context); - } - - if ((Now >= Context.CheckDate()) && (Context.Status() >= Context::csInitialized)) { + if (Now >= Context.CheckDate()) { Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec - if (Context.Status() == Context::csInitialized) - Context.SetStatus(Context::csAuthorization); + if (Context.Status() == Context::csInitialization) { + ModuleService(Context); + } - CheckProviders(Now, Context); - FetchProviders(Now, Context); + if (Context.Status() >= Context::csInitialized) { + CheckProviders(Now, Context); + FetchProviders(Now, Context); + } } - if (Context.Status() == Context::csAuthorized) { - if (Now >= Context.FixedDate()) { + if (Now >= Context.FixedDate()) { + if (Context.Status() == Context::csAuthorized) { Context.SetFixedDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec Context.SetStatus(Context::csInProgress); diff --git a/src/modules/Workers/WebService/WebService.hpp b/src/modules/Workers/WebService/WebService.hpp index b219944..65edbcd 100644 --- a/src/modules/Workers/WebService/WebService.hpp +++ b/src/modules/Workers/WebService/WebService.hpp @@ -42,11 +42,11 @@ namespace Apostol { CStringList m_Module; - CString m_OAuth2; - CString m_pgpPrivateKey; - CString m_pgpPassphrase; + CJSON m_OAuth2; - int m_ServerIndex; + CString m_pgpModuleKey; + CString m_pgpPublicKey; + CString m_pgpPassphrase; CProcessStatus m_Status; @@ -65,17 +65,19 @@ namespace Apostol { void InitServerList(); void UpdateServerList(const CString &Key); - void UpdateOAuth2(const CString &FileName); 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 FetchOAuth2(CContext &Context); 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); @@ -130,6 +132,8 @@ namespace Apostol { 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); static bool LoadOAuth2(const CString &FileName, CProviders &Providers); }; } diff --git a/src/modules/Workers/WebSocket/WebSocket.cpp b/src/modules/Workers/WebSocket/WebSocket.cpp index 32ee82f..3fa27bf 100644 --- a/src/modules/Workers/WebSocket/WebSocket.cpp +++ b/src/modules/Workers/WebSocket/WebSocket.cpp @@ -125,29 +125,31 @@ namespace Apostol { Context.PGP().Name = "PUBLIC"; Context.PGP().Key = Key; Context.BTCKeys() = Keys; + + UpdateOAuth2(Context, m_OAuth2.Object()); } } } //-------------------------------------------------------------------------------------------------------------- - void CWebSocketModule::FetchOAuth2(CContext &Context) { + void CWebSocketModule::ModuleStatus(CContext &Context) { - Log()->Debug(APP_LOG_DEBUG_CORE, "Trying to fetch a OAuth2 configuration file for module \"%s\" from: %s", Context.Name().c_str(), Context.URL().Origin().c_str()); + 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) { - ARequest->ContentType = CHTTPRequest::text; - - Apostol::PGP::CleartextSignature( - m_pgpPrivateKey, - m_pgpPassphrase, - BPS_PGP_HASH, - Context.Name(), - ARequest->Content); - - CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/dm/service"); + 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); @@ -163,14 +165,82 @@ namespace Apostol { DebugReply(pReply); - if (Context.Status() == Context::csPreparing) { - if (pReply->Status == CHTTPReply::ok) { - const CJSON Json(pReply->Content); - Json.SaveToFile(m_OAuth2.c_str()); - UpdateOAuth2(m_OAuth2); - } else { - Context.SetStatus(Context::csInitialization); - } + 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); @@ -192,7 +262,98 @@ namespace Apostol { Context.SetStatus(Context::csInitialization); }; - Context.SetStatus(Context::csPreparing); + 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); @@ -205,41 +366,108 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebSocketModule::UpdateOAuth2(const CString &FileName) { - for (int i = 0; i < m_Servers.Count(); i++) { - auto &Context = m_Servers[i].Value(); - if (Context.Status() == Context::csInitialization || Context.Status() == Context::csPreparing) { - if (LoadOAuth2(FileName, Context.Providers())) { - Context.SetStatus(Context::csInitialized); - Context.SetCheckDate(0); - } else { - if (Context.Status() != Context::csPreparing) { - FetchOAuth2(Context); - } + 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); } + + 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); } } //-------------------------------------------------------------------------------------------------------------- - bool CWebSocketModule::LoadOAuth2(const CString &FileName, CProviders &Providers) { + void CWebSocketModule::UpdateProviders(CProviders &Providers, const CJSONObject &Data) { const auto &caProviderName = CString(SYSTEM_PROVIDER_NAME); const auto &caApplicationName = CString(SERVICE_APPLICATION_NAME); - if (FileExists(FileName.c_str())) { - CJSONObject Json; - Json.LoadFromFile(FileName.c_str()); - - int index = Providers.IndexOfName(caProviderName); - if (index == -1) - index = Providers.AddPair(caProviderName, CProvider(caProviderName)); - auto& Provider = Providers[index].Value(); - Provider.Applications().AddPair(caApplicationName, Json); - - return true; - } - - return false; + int index = Providers.IndexOfName(caProviderName); + if (index == -1) + index = Providers.AddPair(caProviderName, CProvider(caProviderName)); + auto& Provider = Providers[index].Value(); + Provider.Applications().AddPair(caApplicationName, Data); } //-------------------------------------------------------------------------------------------------------------- @@ -355,9 +583,7 @@ namespace Apostol { pClient->Active(true); Context.SetFixedDate(0); - - if (Context.Status() == Context::csInProgress) - Context.SetStatus(csRunning); + Context.SetStatus(csRunning); } catch (std::exception &e) { Log()->Error(APP_LOG_ERR, 0, e.what()); } @@ -425,7 +651,7 @@ namespace Apostol { void CWebSocketModule::CreateAccessToken(const CProvider &Provider, const CString &Application, CClientContext &Context) { - auto OnDone = [&Context](CTCPConnection *Sender) { + auto OnDone = [this, &Context](CTCPConnection *Sender) { auto pConnection = dynamic_cast (Sender); auto pReply = pConnection->Reply(); @@ -435,14 +661,12 @@ namespace Apostol { if (pReply->Status == CHTTPReply::ok) { const CJSON Json(pReply->Content); - if (Context.Status() == Context::csAuthorization) - Context.SetStatus(csAuthorized); - Context.Session() = Json["session"].AsString(); Context.Secret() = Json["secret"].AsString(); - Context.SetFixedDate(0); - Context.SetCheckDate(Now() + (CDateTime) 55 / MinsPerDay); // 55 min + Context.Tokens().Values("access_token", Json["access_token"].AsString()); + + ModuleStatus(Context); } return true; @@ -639,7 +863,6 @@ namespace Apostol { if (Response.Payload.HasOwnProperty("data")) { UpdateServerList(Response.Payload["data"].AsString()); - UpdateOAuth2(m_OAuth2); } } //-------------------------------------------------------------------------------------------------------------- @@ -863,7 +1086,7 @@ namespace Apostol { sPayload = caClearText.Text(); } else { Apostol::PGP::CleartextSignature( - m_pgpPrivateKey, + m_pgpModuleKey, m_pgpPassphrase, BPS_PGP_HASH, caClearText.Text(), @@ -1260,7 +1483,7 @@ namespace Apostol { sPayload = caClearText; } else { Apostol::PGP::CleartextSignature( - m_pgpPrivateKey, + m_pgpModuleKey, m_pgpPassphrase, BPS_PGP_HASH, caClearText, @@ -1517,34 +1740,27 @@ namespace Apostol { for (int i = 0; i < m_Servers.Count(); i++) { auto &Context = m_Servers[i].Value(); - if ((Now >= Context.CheckDate()) && (Context.Status() == Context::csInitialization)) { - Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec - FetchOAuth2(Context); - } - - if ((Now >= Context.CheckDate()) && (Context.Status() >= Context::csInitialized)) { + if (Now >= Context.CheckDate()) { Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec - if (Context.Status() == Context::csInitialized) - Context.SetStatus(Context::csAuthorization); + if (Context.Status() == Context::csInitialization) { + ModuleService(Context); + } - CheckProviders(Now, Context); - FetchProviders(Now, Context); - } - - if (Context.Status() == Context::csAuthorized) { - if (Now >= Context.FixedDate()) { - Context.SetFixedDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec - Context.SetStatus(Context::csInProgress); - - CreateWebSocketClient(Context); + if (Context.Status() >= Context::csInitialized) { + CheckProviders(Now, Context); + FetchProviders(Now, Context); } } - if (Context.Status() == Context::csRunning) { - if (Now >= Context.FixedDate()) { - Context.SetFixedDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec + if (Now >= Context.FixedDate()) { + Context.SetFixedDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec + if (Context.Status() == Context::csAuthorized) { + CreateWebSocketClient(Context); + } + + if (Context.Status() == Context::csRunning) { for (int j = 0; j < Context.ClientManager().Count(); ++j) { auto pClient = Context.ClientManager()[j]; @@ -1552,7 +1768,10 @@ namespace Apostol { pClient->Active(true); if (!pClient->Connected() && !pClient->Session().IsEmpty()) { - Log()->Notice(_T("[%s] Trying connect to %s."), pClient->Session().c_str(), pClient->URI().href().c_str()); + Log()->Notice(_T("[%s] [%s] [%s] Trying to establish a connection."), + Context.Name().c_str(), + Context.URL().Origin().c_str(), + pClient->Session().c_str()); pClient->ConnectStart(); } } @@ -1565,9 +1784,15 @@ namespace Apostol { void CWebSocketModule::Reload() { Config()->IniFile().ReadSectionValues("module", &m_Module); - m_OAuth2 = Config()->IniFile().ReadString(CONFIG_SECTION_NAME, "oauth2", "oauth2/service.json"); - if (!path_separator(m_OAuth2.front())) { - m_OAuth2 = Config()->Prefix() + m_OAuth2; + CString FileName; + + FileName = Config()->IniFile().ReadString(CONFIG_SECTION_NAME, "oauth2", "oauth2/service.json"); + if (!path_separator(FileName.front())) { + FileName = Config()->Prefix() + FileName; + } + + if (FileExists(FileName.c_str())) { + m_OAuth2.LoadFromFile(FileName.c_str()); } const auto &caPrivateKey = Config()->IniFile().ReadString("pgp", "private", "module.sec"); @@ -1576,14 +1801,11 @@ namespace Apostol { m_pgpPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", ""); if (FileExists(caPrivateKey.c_str())) { - m_pgpPrivateKey.LoadFromFile(caPrivateKey.c_str()); + m_pgpModuleKey.LoadFromFile(caPrivateKey.c_str()); if (FileExists(caPublicKey.c_str())) { - CString Key; - Key.LoadFromFile(caPublicKey.c_str()); - - UpdateServerList(Key); - UpdateOAuth2(m_OAuth2); + m_pgpPublicKey.LoadFromFile(caPublicKey.c_str()); + UpdateServerList(m_pgpPublicKey); } else { Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, caPublicKey.c_str()); } diff --git a/src/modules/Workers/WebSocket/WebSocket.hpp b/src/modules/Workers/WebSocket/WebSocket.hpp index d24fa90..999d04c 100644 --- a/src/modules/Workers/WebSocket/WebSocket.hpp +++ b/src/modules/Workers/WebSocket/WebSocket.hpp @@ -69,8 +69,10 @@ namespace Apostol { CStringList m_Module; - CString m_OAuth2; - CString m_pgpPrivateKey; + CJSON m_OAuth2; + + CString m_pgpModuleKey; + CString m_pgpPublicKey; CString m_pgpPassphrase; int m_SyncPeriod; @@ -83,7 +85,6 @@ namespace Apostol { void InitServerList(); void UpdateServerList(const CString &Key); - void UpdateOAuth2(const CString &FileName); void FetchProviders(CDateTime Now, CClientContext &Context); void CheckProviders(CDateTime Now, CClientContext &Context); @@ -94,7 +95,10 @@ namespace Apostol { CWebSocketClient *GetWebSocketClient(CClientContext &Context); void CreateWebSocketClient(CClientContext &Context); - void FetchOAuth2(CContext &Context); + void ModuleService(CContext &Context); + void ModuleStatus(CContext &Context); + void ModuleNew(CContext &Context); + void ModuleAuthorize(CContext &Context); static CWebSocketClient *GetConnectedClient(const CClientContext &Context); @@ -147,12 +151,14 @@ namespace Apostol { void Reload(); 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 FindURLInLine(const CString &Line, CStringList &List); - static bool LoadOAuth2(const CString &FileName, CProviders &Providers); + static void UpdateOAuth2(CContext &Context, const CJSONObject &OAuth2); + static void UpdateProviders(CProviders &Providers, const CJSONObject &Data); }; } }