Подписанная PGP ключом ДС Created сделка сначала должна отправляться по bitmessage, затем должна быть отдана МС.

This commit is contained in:
Преподобный Ален
2022-09-17 15:57:29 +03:00
parent 3b69fc0889
commit 16c1abefe5
4 changed files with 700 additions and 242 deletions

View File

@@ -50,7 +50,6 @@ namespace Apostol {
CWebService::CWebService(CModuleProcess *AProcess): CApostolModule(AProcess, "web service", "module/WebService") { CWebService::CWebService(CModuleProcess *AProcess): CApostolModule(AProcess, "web service", "module/WebService") {
m_NexServerTime = 0; m_NexServerTime = 0;
m_ServerIndex = -1;
m_SyncPeriod = BPS_DEFAULT_SYNC_PERIOD; m_SyncPeriod = BPS_DEFAULT_SYNC_PERIOD;
m_Status = psStopped; m_Status = psStopped;
@@ -122,122 +121,31 @@ namespace Apostol {
} }
//-------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------
void CWebService::FetchOAuth2(CContext &Context) { void CWebService::CheckKeyForNull(LPCTSTR Key, LPCTSTR Value) {
if (Value == nullptr)
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()); throw ExceptionFrm("Invalid format: key \"%s\" cannot be empty.", Key);
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<CHTTPClientConnection *> (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<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialization);
};
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) { void CWebService::UpdateOAuth2(CContext &Context, const CJSONObject &OAuth2) {
for (int i = 0; i < m_Servers.Count(); i++) { if (OAuth2["type"].AsString() == "service_account") {
auto &Context = m_Servers[i].Value(); UpdateProviders(Context.Providers(), OAuth2);
if (Context.Status() == Context::csInitialization || Context.Status() == Context::csPreparing) {
if (LoadOAuth2(FileName, Context.Providers())) {
Context.SetStatus(Context::csInitialized);
Context.SetCheckDate(0); Context.SetCheckDate(0);
} else { Context.SetStatus(Context::csInitialized);
if (Context.Status() != Context::csPreparing) {
FetchOAuth2(Context);
}
}
}
} }
} }
//-------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------
bool CWebService::LoadOAuth2(const CString &FileName, CProviders &Providers) { void CWebService::UpdateProviders(CProviders &Providers, const CJSONObject &Data) {
const auto &caProviderName = CString(SYSTEM_PROVIDER_NAME); const auto &caProviderName = CString(SYSTEM_PROVIDER_NAME);
const auto &caApplicationName = CString(SERVICE_APPLICATION_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); int index = Providers.IndexOfName(caProviderName);
if (index == -1) if (index == -1)
index = Providers.AddPair(caProviderName, CProvider(caProviderName)); index = Providers.AddPair(caProviderName, CProvider(caProviderName));
auto& Provider = Providers[index].Value(); auto& Provider = Providers[index].Value();
Provider.Applications().AddPair(caApplicationName, Json); Provider.Applications().AddPair(caApplicationName, Data);
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
void CWebService::CheckKeyForNull(LPCTSTR Key, LPCTSTR Value) {
if (Value == nullptr)
throw ExceptionFrm("Invalid format: key \"%s\" cannot be empty.", Key);
} }
//-------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------
@@ -726,7 +634,7 @@ namespace Apostol {
sPayload = caClearText.Text(); sPayload = caClearText.Text();
} else { } else {
Apostol::PGP::CleartextSignature( Apostol::PGP::CleartextSignature(
m_pgpPrivateKey, m_pgpModuleKey,
m_pgpPassphrase, m_pgpPassphrase,
BPS_PGP_HASH, BPS_PGP_HASH,
caClearText.Text(), caClearText.Text(),
@@ -1174,7 +1082,7 @@ namespace Apostol {
sPayload = caClearText; sPayload = caClearText;
} else { } else {
Apostol::PGP::CleartextSignature( Apostol::PGP::CleartextSignature(
m_pgpPrivateKey, m_pgpModuleKey,
m_pgpPassphrase, m_pgpPassphrase,
BPS_PGP_HASH, BPS_PGP_HASH,
caClearText, caClearText,
@@ -1612,7 +1520,7 @@ namespace Apostol {
void CWebService::FetchPGP(CContext &Context) { 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) { 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<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
const CJSON OAuth2(pReply->Content);
UpdateOAuth2(Context, OAuth2.Object());
}
pConnection->CloseConnection(true);
}
return true;
};
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialization);
};
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
pClient->OnRequest(OnRequest);
pClient->OnExecute(OnExecute);
pClient->OnException(OnException);
pClient->AutoFree(true);
pClient->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void 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<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
ModuleAuthorize(Context);
} else {
ModuleNew(Context);
}
pConnection->CloseConnection(true);
}
return true;
};
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialized);
};
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
pClient->OnRequest(OnRequest);
pClient->OnExecute(OnExecute);
pClient->OnException(OnException);
pClient->AutoFree(true);
pClient->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void 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<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
const CJSON Json(pReply->Content);
if (Json.HasOwnProperty("result")) {
const auto &caResult = Json["result"];
if (caResult.HasOwnProperty("success")) {
if (caResult["success"].AsBoolean()) {
ModuleAuthorize(Context);
} else {
if (caResult.HasOwnProperty("message")) {
Log()->Error(APP_LOG_EMERG, 0, "[%s] [%s] %s", Context.Name().c_str(),
Context.URL().Origin().c_str(), caResult["message"].AsString().c_str());
}
}
}
}
}
pConnection->CloseConnection(true);
}
return true;
};
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialized);
};
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
pClient->OnRequest(OnRequest);
pClient->OnExecute(OnExecute);
pClient->OnException(OnException);
pClient->AutoFree(true);
pClient->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void 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<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
const CJSON Json(pReply->Content);
Context.Session() = Json["session"].AsString();
Context.Secret() = Json["secret"].AsString();
Context.Tokens().Values("access_token", Json["access_token"].AsString());
Context.SetFixedDate(0);
Context.SetCheckDate(Now() + (CDateTime) 55 / MinsPerDay); // 55 min
Context.SetStatus(csAuthorized);
}
pConnection->CloseConnection(true);
}
return true;
};
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialized);
};
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
pClient->OnRequest(OnRequest);
pClient->OnExecute(OnExecute);
pClient->OnException(OnException);
pClient->AutoFree(true);
pClient->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void CWebService::Reload() { void CWebService::Reload() {
Config()->IniFile().ReadSectionValues("module", &m_Module); Config()->IniFile().ReadSectionValues("module", &m_Module);
m_OAuth2 = Config()->IniFile().ReadString(CONFIG_SECTION_NAME, "oauth2", "oauth2/service.json"); CString FileName;
if (!path_separator(m_OAuth2.front())) {
m_OAuth2 = Config()->Prefix() + m_OAuth2; 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"); if (FileExists(FileName.c_str())) {
const auto& caPublicKey = Config()->IniFile().ReadString("pgp", "public", "dm.pub"); 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", ""); m_pgpPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", "");
if (FileExists(caPrivateKey.c_str())) { if (FileExists(caPrivateKey.c_str())) {
m_pgpPrivateKey.LoadFromFile(caPrivateKey.c_str()); m_pgpModuleKey.LoadFromFile(caPrivateKey.c_str());
if (FileExists(caPublicKey.c_str())) { if (FileExists(caPublicKey.c_str())) {
CString Key; m_pgpPublicKey.LoadFromFile(caPublicKey.c_str());
Key.LoadFromFile(caPublicKey.c_str()); UpdateServerList(m_pgpPublicKey);
UpdateServerList(Key);
UpdateOAuth2(m_OAuth2);
} else { } else {
Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, caPublicKey.c_str()); 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++) { for (int i = 0; i < m_Servers.Count(); i++) {
auto &Context = m_Servers[i].Value(); auto &Context = m_Servers[i].Value();
if ((Now >= Context.CheckDate()) && (Context.Status() == Context::csInitialization)) { if (Now >= Context.CheckDate()) {
Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec
FetchOAuth2(Context);
if (Context.Status() == Context::csInitialization) {
ModuleService(Context);
} }
if ((Now >= Context.CheckDate()) && (Context.Status() >= Context::csInitialized)) { if (Context.Status() >= Context::csInitialized) {
Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec
if (Context.Status() == Context::csInitialized)
Context.SetStatus(Context::csAuthorization);
CheckProviders(Now, Context); CheckProviders(Now, Context);
FetchProviders(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.SetFixedDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec
Context.SetStatus(Context::csInProgress); Context.SetStatus(Context::csInProgress);

View File

@@ -42,11 +42,11 @@ namespace Apostol {
CStringList m_Module; CStringList m_Module;
CString m_OAuth2; CJSON m_OAuth2;
CString m_pgpPrivateKey;
CString m_pgpPassphrase;
int m_ServerIndex; CString m_pgpModuleKey;
CString m_pgpPublicKey;
CString m_pgpPassphrase;
CProcessStatus m_Status; CProcessStatus m_Status;
@@ -65,17 +65,19 @@ namespace Apostol {
void InitServerList(); void InitServerList();
void UpdateServerList(const CString &Key); void UpdateServerList(const CString &Key);
void UpdateOAuth2(const CString &FileName);
void FetchProviders(CDateTime Now, CContext &Context); void FetchProviders(CDateTime Now, CContext &Context);
void CheckProviders(CDateTime Now, CContext &Context); void CheckProviders(CDateTime Now, CContext &Context);
void CreateAccessToken(const CProvider &Provider, const CString &Application, CContext &Context); void CreateAccessToken(const CProvider &Provider, const CString &Application, CContext &Context);
void ParsePGPKey(const CString& Key, CStringPairs& ServerList, CStringList& BTCKeys); void ParsePGPKey(const CString& Key, CStringPairs& ServerList, CStringList& BTCKeys);
void FetchOAuth2(CContext &Context);
void FetchPGP(CContext &Context); void FetchPGP(CContext &Context);
void ModuleService(CContext &Context);
void ModuleStatus(CContext &Context);
void ModuleNew(CContext &Context);
void ModuleAuthorize(CContext &Context);
protected: protected:
CHTTPProxy *GetProxy(CHTTPServerConnection *AConnection); CHTTPProxy *GetProxy(CHTTPServerConnection *AConnection);
@@ -130,6 +132,8 @@ namespace Apostol {
static int VerifyPGPSignature(const CString &ClearText, const CString &Key, CString &Message); static int VerifyPGPSignature(const CString &ClearText, const CString &Key, CString &Message);
static bool FindURLInLine(const CString &Line, CStringList &List); 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); static bool LoadOAuth2(const CString &FileName, CProviders &Providers);
}; };
} }

View File

@@ -125,29 +125,31 @@ namespace Apostol {
Context.PGP().Name = "PUBLIC"; Context.PGP().Name = "PUBLIC";
Context.PGP().Key = Key; Context.PGP().Key = Key;
Context.BTCKeys() = Keys; 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) { auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) {
ARequest->ContentType = CHTTPRequest::text; Context.SetStatus(Context::csInProgress);
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"]; 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()) if (!caModuleAddress.IsEmpty())
ARequest->AddHeader("Module-Address", caModuleAddress); ARequest->AddHeader("Module-Address", caModuleAddress);
@@ -163,14 +165,82 @@ namespace Apostol {
DebugReply(pReply); DebugReply(pReply);
if (Context.Status() == Context::csPreparing) {
if (pReply->Status == CHTTPReply::ok) { if (pReply->Status == CHTTPReply::ok) {
const CJSON Json(pReply->Content); ModuleAuthorize(Context);
Json.SaveToFile(m_OAuth2.c_str());
UpdateOAuth2(m_OAuth2);
} else { } else {
Context.SetStatus(Context::csInitialization); ModuleNew(Context);
} }
pConnection->CloseConnection(true);
}
return true;
};
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialized);
};
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
pClient->OnRequest(OnRequest);
pClient->OnExecute(OnExecute);
pClient->OnException(OnException);
pClient->AutoFree(true);
pClient->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void 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<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
const CJSON OAuth2(pReply->Content);
UpdateOAuth2(Context, OAuth2.Object());
} }
pConnection->CloseConnection(true); pConnection->CloseConnection(true);
@@ -192,7 +262,98 @@ namespace Apostol {
Context.SetStatus(Context::csInitialization); 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<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
const CJSON Json(pReply->Content);
if (Json.HasOwnProperty("result")) {
const auto &caResult = Json["result"];
if (caResult.HasOwnProperty("success")) {
if (caResult["success"].AsBoolean()) {
ModuleAuthorize(Context);
} else {
if (caResult.HasOwnProperty("message")) {
Log()->Error(APP_LOG_EMERG, 0, "[%s] [%s] %s", Context.Name().c_str(),
Context.URL().Origin().c_str(), caResult["message"].AsString().c_str());
}
}
}
}
}
pConnection->CloseConnection(true);
}
return true;
};
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialized);
};
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port); 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) { void CWebSocketModule::ModuleAuthorize(CContext &Context) {
for (int i = 0; i < m_Servers.Count(); i++) {
auto &Context = m_Servers[i].Value(); Log()->Notice("[%s] [%s] Trying authorize module.", Context.Name().c_str(), Context.URL().Origin().c_str());
if (Context.Status() == Context::csInitialization || Context.Status() == Context::csPreparing) {
if (LoadOAuth2(FileName, Context.Providers())) { 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); Context.SetStatus(Context::csInitialized);
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pReply = pConnection->Reply();
DebugReply(pReply);
if (pReply->Status == CHTTPReply::ok) {
const CJSON Json(pReply->Content);
Context.Session() = Json["session"].AsString();
Context.Secret() = Json["secret"].AsString();
Context.Tokens().Values("access_token", Json["access_token"].AsString());
Context.SetFixedDate(0);
Context.SetCheckDate(Now() + (CDateTime) 55 / MinsPerDay); // 55 min
Context.SetStatus(csAuthorized);
}
pConnection->CloseConnection(true);
}
return true;
};
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
if (pClient != nullptr) {
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
}
DebugReply(pConnection->Reply());
}
Context.SetStatus(Context::csInitialized);
};
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
pClient->OnRequest(OnRequest);
pClient->OnExecute(OnExecute);
pClient->OnException(OnException);
pClient->AutoFree(true);
pClient->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void CWebSocketModule::UpdateOAuth2(CContext &Context, const CJSONObject &OAuth2) {
if (OAuth2["type"].AsString() == "service_account") {
UpdateProviders(Context.Providers(), OAuth2);
Context.SetCheckDate(0); Context.SetCheckDate(0);
} else { Context.SetStatus(Context::csInitialized);
if (Context.Status() != Context::csPreparing) {
FetchOAuth2(Context);
}
}
}
} }
} }
//-------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------
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 &caProviderName = CString(SYSTEM_PROVIDER_NAME);
const auto &caApplicationName = CString(SERVICE_APPLICATION_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); int index = Providers.IndexOfName(caProviderName);
if (index == -1) if (index == -1)
index = Providers.AddPair(caProviderName, CProvider(caProviderName)); index = Providers.AddPair(caProviderName, CProvider(caProviderName));
auto& Provider = Providers[index].Value(); auto& Provider = Providers[index].Value();
Provider.Applications().AddPair(caApplicationName, Json); Provider.Applications().AddPair(caApplicationName, Data);
return true;
}
return false;
} }
//-------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------
@@ -355,8 +583,6 @@ namespace Apostol {
pClient->Active(true); pClient->Active(true);
Context.SetFixedDate(0); Context.SetFixedDate(0);
if (Context.Status() == Context::csInProgress)
Context.SetStatus(csRunning); Context.SetStatus(csRunning);
} catch (std::exception &e) { } catch (std::exception &e) {
Log()->Error(APP_LOG_ERR, 0, e.what()); 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) { 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<CHTTPClientConnection *> (Sender); auto pConnection = dynamic_cast<CHTTPClientConnection *> (Sender);
auto pReply = pConnection->Reply(); auto pReply = pConnection->Reply();
@@ -435,14 +661,12 @@ namespace Apostol {
if (pReply->Status == CHTTPReply::ok) { if (pReply->Status == CHTTPReply::ok) {
const CJSON Json(pReply->Content); const CJSON Json(pReply->Content);
if (Context.Status() == Context::csAuthorization)
Context.SetStatus(csAuthorized);
Context.Session() = Json["session"].AsString(); Context.Session() = Json["session"].AsString();
Context.Secret() = Json["secret"].AsString(); Context.Secret() = Json["secret"].AsString();
Context.SetFixedDate(0); Context.Tokens().Values("access_token", Json["access_token"].AsString());
Context.SetCheckDate(Now() + (CDateTime) 55 / MinsPerDay); // 55 min
ModuleStatus(Context);
} }
return true; return true;
@@ -639,7 +863,6 @@ namespace Apostol {
if (Response.Payload.HasOwnProperty("data")) { if (Response.Payload.HasOwnProperty("data")) {
UpdateServerList(Response.Payload["data"].AsString()); UpdateServerList(Response.Payload["data"].AsString());
UpdateOAuth2(m_OAuth2);
} }
} }
//-------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------
@@ -863,7 +1086,7 @@ namespace Apostol {
sPayload = caClearText.Text(); sPayload = caClearText.Text();
} else { } else {
Apostol::PGP::CleartextSignature( Apostol::PGP::CleartextSignature(
m_pgpPrivateKey, m_pgpModuleKey,
m_pgpPassphrase, m_pgpPassphrase,
BPS_PGP_HASH, BPS_PGP_HASH,
caClearText.Text(), caClearText.Text(),
@@ -1260,7 +1483,7 @@ namespace Apostol {
sPayload = caClearText; sPayload = caClearText;
} else { } else {
Apostol::PGP::CleartextSignature( Apostol::PGP::CleartextSignature(
m_pgpPrivateKey, m_pgpModuleKey,
m_pgpPassphrase, m_pgpPassphrase,
BPS_PGP_HASH, BPS_PGP_HASH,
caClearText, caClearText,
@@ -1517,34 +1740,27 @@ namespace Apostol {
for (int i = 0; i < m_Servers.Count(); i++) { for (int i = 0; i < m_Servers.Count(); i++) {
auto &Context = m_Servers[i].Value(); auto &Context = m_Servers[i].Value();
if ((Now >= Context.CheckDate()) && (Context.Status() == Context::csInitialization)) { if (Now >= Context.CheckDate()) {
Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec
FetchOAuth2(Context);
if (Context.Status() == Context::csInitialization) {
ModuleService(Context);
} }
if ((Now >= Context.CheckDate()) && (Context.Status() >= Context::csInitialized)) { if (Context.Status() >= Context::csInitialized) {
Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec
if (Context.Status() == Context::csInitialized)
Context.SetStatus(Context::csAuthorization);
CheckProviders(Now, Context); CheckProviders(Now, Context);
FetchProviders(Now, Context); FetchProviders(Now, Context);
} }
}
if (Context.Status() == Context::csAuthorized) {
if (Now >= Context.FixedDate()) { if (Now >= Context.FixedDate()) {
Context.SetFixedDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec Context.SetFixedDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec
Context.SetStatus(Context::csInProgress);
if (Context.Status() == Context::csAuthorized) {
CreateWebSocketClient(Context); CreateWebSocketClient(Context);
} }
}
if (Context.Status() == Context::csRunning) { if (Context.Status() == Context::csRunning) {
if (Now >= Context.FixedDate()) {
Context.SetFixedDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec
for (int j = 0; j < Context.ClientManager().Count(); ++j) { for (int j = 0; j < Context.ClientManager().Count(); ++j) {
auto pClient = Context.ClientManager()[j]; auto pClient = Context.ClientManager()[j];
@@ -1552,7 +1768,10 @@ namespace Apostol {
pClient->Active(true); pClient->Active(true);
if (!pClient->Connected() && !pClient->Session().IsEmpty()) { 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(); pClient->ConnectStart();
} }
} }
@@ -1565,9 +1784,15 @@ namespace Apostol {
void CWebSocketModule::Reload() { void CWebSocketModule::Reload() {
Config()->IniFile().ReadSectionValues("module", &m_Module); Config()->IniFile().ReadSectionValues("module", &m_Module);
m_OAuth2 = Config()->IniFile().ReadString(CONFIG_SECTION_NAME, "oauth2", "oauth2/service.json"); CString FileName;
if (!path_separator(m_OAuth2.front())) {
m_OAuth2 = Config()->Prefix() + m_OAuth2; 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"); const auto &caPrivateKey = Config()->IniFile().ReadString("pgp", "private", "module.sec");
@@ -1576,14 +1801,11 @@ namespace Apostol {
m_pgpPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", ""); m_pgpPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", "");
if (FileExists(caPrivateKey.c_str())) { if (FileExists(caPrivateKey.c_str())) {
m_pgpPrivateKey.LoadFromFile(caPrivateKey.c_str()); m_pgpModuleKey.LoadFromFile(caPrivateKey.c_str());
if (FileExists(caPublicKey.c_str())) { if (FileExists(caPublicKey.c_str())) {
CString Key; m_pgpPublicKey.LoadFromFile(caPublicKey.c_str());
Key.LoadFromFile(caPublicKey.c_str()); UpdateServerList(m_pgpPublicKey);
UpdateServerList(Key);
UpdateOAuth2(m_OAuth2);
} else { } else {
Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, caPublicKey.c_str()); Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, caPublicKey.c_str());
} }

View File

@@ -69,8 +69,10 @@ namespace Apostol {
CStringList m_Module; CStringList m_Module;
CString m_OAuth2; CJSON m_OAuth2;
CString m_pgpPrivateKey;
CString m_pgpModuleKey;
CString m_pgpPublicKey;
CString m_pgpPassphrase; CString m_pgpPassphrase;
int m_SyncPeriod; int m_SyncPeriod;
@@ -83,7 +85,6 @@ namespace Apostol {
void InitServerList(); void InitServerList();
void UpdateServerList(const CString &Key); void UpdateServerList(const CString &Key);
void UpdateOAuth2(const CString &FileName);
void FetchProviders(CDateTime Now, CClientContext &Context); void FetchProviders(CDateTime Now, CClientContext &Context);
void CheckProviders(CDateTime Now, CClientContext &Context); void CheckProviders(CDateTime Now, CClientContext &Context);
@@ -94,7 +95,10 @@ namespace Apostol {
CWebSocketClient *GetWebSocketClient(CClientContext &Context); CWebSocketClient *GetWebSocketClient(CClientContext &Context);
void CreateWebSocketClient(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); static CWebSocketClient *GetConnectedClient(const CClientContext &Context);
@@ -147,12 +151,14 @@ namespace Apostol {
void Reload(); void Reload();
static CString ToString(unsigned long Value); static CString ToString(unsigned long Value);
static int CheckFee(const CString& Fee); static int CheckFee(const CString& Fee);
static void CheckDeal(const CDeal& Deal); static void CheckDeal(const CDeal& Deal);
static void CheckKeyForNull(LPCTSTR key, const CString& Value); static void CheckKeyForNull(LPCTSTR key, const CString& Value);
static bool FindURLInLine(const CString &Line, CStringList &List); 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);
}; };
} }
} }