mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +07:00
feat: add async post in gateway controller (#1963)
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
#include <QPromise>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include "QBlockCipher.h"
|
#include "QBlockCipher.h"
|
||||||
@@ -50,24 +51,25 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
|
||||||
{
|
{
|
||||||
|
EncryptedRequestData encRequestData;
|
||||||
|
encRequestData.errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
IosController::Instance()->requestInetAccess();
|
IosController::Instance()->requestInetAccess();
|
||||||
QThread::msleep(10);
|
QThread::msleep(10);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QNetworkRequest request;
|
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||||
request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||||
|
|
||||||
request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
|
||||||
|
|
||||||
// bypass killSwitch exceptions for API-gateway
|
// bypass killSwitch exceptions for API-gateway
|
||||||
#ifdef AMNEZIA_DESKTOP
|
#ifdef AMNEZIA_DESKTOP
|
||||||
if (m_isStrictKillSwitchEnabled) {
|
if (m_isStrictKillSwitchEnabled) {
|
||||||
QString host = QUrl(request.url()).host();
|
QString host = QUrl(encRequestData.request.url()).host();
|
||||||
QString ip = NetworkUtilities::getIPAddress(host);
|
QString ip = NetworkUtilities::getIPAddress(host);
|
||||||
if (!ip.isEmpty()) {
|
if (!ip.isEmpty()) {
|
||||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||||
@@ -76,14 +78,14 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
encRequestData.key = blockCipher.generatePrivateSalt(32);
|
||||||
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
encRequestData.iv = blockCipher.generatePrivateSalt(32);
|
||||||
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
encRequestData.salt = blockCipher.generatePrivateSalt(8);
|
||||||
|
|
||||||
QJsonObject keyPayload;
|
QJsonObject keyPayload;
|
||||||
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
|
||||||
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
|
||||||
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
|
||||||
|
|
||||||
QByteArray encryptedKeyPayload;
|
QByteArray encryptedKeyPayload;
|
||||||
QByteArray encryptedApiPayload;
|
QByteArray encryptedApiPayload;
|
||||||
@@ -98,24 +100,37 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
} catch (...) {
|
} catch (...) {
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
qCritical() << "error loading public key from environment variables";
|
qCritical() << "error loading public key from environment variables";
|
||||||
return ErrorCode::ApiMissingAgwPublicKey;
|
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
|
||||||
|
return encRequestData;
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||||
EVP_PKEY_free(publicKey);
|
EVP_PKEY_free(publicKey);
|
||||||
|
|
||||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
} catch (...) {
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
qCritical() << "error when encrypting the request body";
|
qCritical() << "error when encrypting the request body";
|
||||||
return ErrorCode::ApiConfigDecryptionError;
|
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
|
||||||
|
return encRequestData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject requestBody;
|
QJsonObject requestBody;
|
||||||
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||||
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
|
||||||
|
return encRequestData;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||||
|
{
|
||||||
|
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||||
|
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||||
|
return encRequestData.errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||||
|
|
||||||
QEventLoop wait;
|
QEventLoop wait;
|
||||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
@@ -131,19 +146,19 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, encryptedResponseBody, true, key, iv, salt)) {
|
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
|
||||||
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
|
||||||
request.setUrl(url);
|
encRequestData.request.setUrl(url);
|
||||||
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &key, &iv,
|
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
|
||||||
&salt, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
||||||
encryptedResponseBody = reply->readAll();
|
encryptedResponseBody = reply->readAll();
|
||||||
replyErrorString = reply->errorString();
|
replyErrorString = reply->errorString();
|
||||||
replyError = reply->error();
|
replyError = reply->error();
|
||||||
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
if (!sslErrors.isEmpty() || shouldBypassProxy(replyError, encryptedResponseBody, true, key, iv, salt)) {
|
if (!sslErrors.isEmpty() || shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
|
||||||
sslErrors = nestedSslErrors;
|
sslErrors = nestedSslErrors;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -161,7 +176,8 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
@@ -170,6 +186,57 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
|
||||||
|
{
|
||||||
|
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QByteArray>>>::create();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||||
|
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||||
|
promise->addResult(qMakePair(encRequestData.errorCode, QByteArray()));
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||||
|
|
||||||
|
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) {
|
||||||
|
*sslErrors = errors;
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, [=]() {
|
||||||
|
QByteArray encryptedResponseBody = reply->readAll();
|
||||||
|
QString replyErrorString = reply->errorString();
|
||||||
|
auto replyError = reply->error();
|
||||||
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
auto errorCode = apiUtils::checkNetworkReplyErrors(*sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody);
|
||||||
|
if (errorCode) {
|
||||||
|
promise->addResult(qMakePair(errorCode, QByteArray()));
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
try {
|
||||||
|
QByteArray responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||||
|
promise->addResult(qMakePair(ErrorCode::NoError, responseBody));
|
||||||
|
promise->finish();
|
||||||
|
} catch (...) {
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error when decrypting the request body";
|
||||||
|
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
|
||||||
|
promise->finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
|
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
|
||||||
{
|
{
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#ifndef GATEWAYCONTROLLER_H
|
#ifndef GATEWAYCONTROLLER_H
|
||||||
#define GATEWAYCONTROLLER_H
|
#define GATEWAYCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QFuture>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPair>
|
||||||
|
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
|
||||||
@@ -19,8 +21,20 @@ public:
|
|||||||
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
||||||
|
|
||||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||||
|
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct EncryptedRequestData {
|
||||||
|
QNetworkRequest request;
|
||||||
|
QByteArray requestBody;
|
||||||
|
QByteArray key;
|
||||||
|
QByteArray iv;
|
||||||
|
QByteArray salt;
|
||||||
|
amnezia::ErrorCode errorCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
|
||||||
|
|
||||||
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
|
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
|
||||||
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
|
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
|
||||||
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
|
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ void ApiNewsController::fetchNews()
|
|||||||
}
|
}
|
||||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||||
m_settings->isStrictKillSwitchEnabled());
|
m_settings->isStrictKillSwitchEnabled());
|
||||||
QByteArray responseBody;
|
|
||||||
QJsonObject payload;
|
QJsonObject payload;
|
||||||
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
||||||
|
|
||||||
@@ -44,22 +43,26 @@ void ApiNewsController::fetchNews()
|
|||||||
payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType));
|
payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType));
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode errorCode = gatewayController.post(QString("%1v1/news"), payload, responseBody);
|
auto future = gatewayController.postAsync(QString("%1v1/news"), payload);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
future.then(this, [this](QPair<ErrorCode, QByteArray> result) {
|
||||||
emit errorOccurred(errorCode);
|
auto [errorCode, responseBody] = result;
|
||||||
return;
|
if (errorCode != ErrorCode::NoError) {
|
||||||
}
|
emit errorOccurred(errorCode);
|
||||||
|
return;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
|
|
||||||
QJsonArray newsArray;
|
|
||||||
if (doc.isArray()) {
|
|
||||||
newsArray = doc.array();
|
|
||||||
} else if (doc.isObject()) {
|
|
||||||
QJsonObject obj = doc.object();
|
|
||||||
if (obj.value("news").isArray()) {
|
|
||||||
newsArray = obj.value("news").toArray();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
m_newsModel->updateModel(newsArray);
|
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
|
||||||
|
QJsonArray newsArray;
|
||||||
|
if (doc.isArray()) {
|
||||||
|
newsArray = doc.array();
|
||||||
|
} else if (doc.isObject()) {
|
||||||
|
QJsonObject obj = doc.object();
|
||||||
|
if (obj.value("news").isArray()) {
|
||||||
|
newsArray = obj.value("news").toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_newsModel->updateModel(newsArray);
|
||||||
|
emit fetchNewsFinished();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(ErrorCode errorCode);
|
void errorOccurred(ErrorCode errorCode);
|
||||||
|
void fetchNewsFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<NewsModel> m_newsModel;
|
QSharedPointer<NewsModel> m_newsModel;
|
||||||
|
|||||||
@@ -14,6 +14,19 @@ import "../Config"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ApiNewsController
|
||||||
|
function onFetchNewsFinished() {
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onErrorOccurred(errorCode) {
|
||||||
|
PageController.showErrorMessage(errorCode)
|
||||||
|
PageController.closePage()
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
@@ -140,9 +153,8 @@ PageType {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
ApiNewsController.fetchNews();
|
ApiNewsController.fetchNews()
|
||||||
PageController.goToPage(PageEnum.PageSettingsNewsNotifications)
|
PageController.goToPage(PageEnum.PageSettingsNewsNotifications)
|
||||||
PageController.showBusyIndicator(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user