From 7c8ae9c3114e4fc597f8dc81ea1d704dba8ed157 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 31 Jan 2025 10:06:35 +0700 Subject: [PATCH 01/23] refactoring: moved api info pages from ServerInfo --- client/resources.qrc | 3 +- client/ui/controllers/pageController.h | 2 + .../ui/qml/Components/RenameServerDrawer.qml | 55 ++++++++++++ client/ui/qml/Components/ServersListView.qml | 13 ++- client/ui/qml/Pages2/PageHome.qml | 12 ++- ... => PageSettingsApiAvailableCountries.qml} | 72 +++++++++++++++- .../qml/Pages2/PageSettingsApiServerInfo.qml | 79 ++++++++++++++++- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 85 ++----------------- .../ui/qml/Pages2/PageSettingsServersList.qml | 12 ++- 9 files changed, 245 insertions(+), 88 deletions(-) create mode 100644 client/ui/qml/Components/RenameServerDrawer.qml rename client/ui/qml/Pages2/{PageSettingsApiLanguageList.qml => PageSettingsApiAvailableCountries.qml} (63%) diff --git a/client/resources.qrc b/client/resources.qrc index ff03a6e7d..c55636186 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -192,7 +192,7 @@ ui/qml/Pages2/PageServiceTorWebsiteSettings.qml ui/qml/Pages2/PageSettings.qml ui/qml/Pages2/PageSettingsAbout.qml - ui/qml/Pages2/PageSettingsApiLanguageList.qml + ui/qml/Pages2/PageSettingsApiAvailableCountries.qml ui/qml/Pages2/PageSettingsApiServerInfo.qml ui/qml/Pages2/PageSettingsApplication.qml ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -224,6 +224,7 @@ ui/qml/Pages2/PageShare.qml ui/qml/Pages2/PageShareFullAccess.qml ui/qml/Pages2/PageStart.qml + ui/qml/Components/RenameServerDrawer.qml images/flagKit/ZW.svg diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index ffbdd3a1c..428cf4f8b 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -31,6 +31,8 @@ namespace PageLoader PageSettingsLogging, PageSettingsSplitTunneling, PageSettingsAppSplitTunneling, + PageSettingsApiServerInfo, + PageSettingsApiAvailableCountries, PageServiceSftpSettings, PageServiceTorWebsiteSettings, diff --git a/client/ui/qml/Components/RenameServerDrawer.qml b/client/ui/qml/Components/RenameServerDrawer.qml new file mode 100644 index 000000000..d65b9bbaa --- /dev/null +++ b/client/ui/qml/Components/RenameServerDrawer.qml @@ -0,0 +1,55 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Style 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + +import "../Config" + +DrawerType2 { + property string serverNameText + + id: root + objectName: "serverNameEditDrawer" + + expandedStateContent: ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 32 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + TextFieldWithHeaderType { + id: serverName + + Layout.fillWidth: true + headerText: qsTr("Server name") + textField.text: root.serverNameText + textField.maximumLength: 30 + checkEmptyText: true + } + + BasicButtonType { + id: saveButton + + Layout.fillWidth: true + + text: qsTr("Save") + + clickedFunc: function() { + if (serverName.textField.text === "") { + return + } + + if (serverName.textField.text !== root.serverNameText) { + ServersModel.setProcessedServerData("name", serverName.textField.text); + } + root.closeTriggered() + } + } + } +} diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index dc5c5a33a..8d591d86a 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -110,7 +110,18 @@ ListView { onClicked: function() { ServersModel.processedIndex = index - PageController.goToPage(PageEnum.PageSettingsServerInfo) + + if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { + if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { + PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) + + } else { + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) + } + } else { + PageController.goToPage(PageEnum.PageSettingsServerInfo) + } + drawer.closeTriggered() } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index ae29b80cd..fc5f53266 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -297,7 +297,17 @@ PageType { onClicked: { ServersModel.processedIndex = ServersModel.defaultIndex - PageController.goToPage(PageEnum.PageSettingsServerInfo) + + if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { + if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { + PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) + + } else { + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) + } + } else { + PageController.goToPage(PageEnum.PageSettingsServerInfo) + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml similarity index 63% rename from client/ui/qml/Pages2/PageSettingsApiLanguageList.qml rename to client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index 30968b38b..853a44a38 100644 --- a/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -3,6 +3,8 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs +import SortFilterProxyModel 0.2 + import PageEnum 1.0 import Style 1.0 @@ -15,22 +17,86 @@ import "../Components" PageType { id: root + property var processedServer + + Connections { + target: ServersModel + + function onProcessedServerChanged() { + root.processedServer = proxyServersModel.get(0) + } + } + + SortFilterProxyModel { + id: proxyServersModel + objectName: "proxyServersModel" + + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + + Component.onCompleted: { + root.processedServer = proxyServersModel.get(0) + } + } + ListView { id: menuContent property bool isFocusable: true - width: parent.width - height: parent.height + anchors.fill: parent + + ScrollBar.vertical: ScrollBarType {} clip: true - interactive: true + reuseItems: true + snapMode: ListView.SnapToItem + model: ApiCountryModel + currentIndex: 0 + ButtonGroup { id: containersRadioButtonGroup } + header: ColumnLayout { + width: menuContent.width + + spacing: 4 + + BackButtonType { + id: backButton + objectName: "backButton" + + Layout.topMargin: 20 + } + + HeaderType { + id: headerContent + objectName: "headerContent" + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 10 + + actionButtonImage: "qrc:/images/controls/settings.svg" + + headerText: root.processedServer.name + descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") + + actionButtonFunction: function() { + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) + } + } + } + delegate: ColumnLayout { id: content diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 1fc172180..4346e6176 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -3,6 +3,8 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs +import SortFilterProxyModel 0.2 + import PageEnum 1.0 import Style 1.0 @@ -54,12 +56,40 @@ PageType { readonly property string objectImageSource: "qrc:/images/controls/gauge.svg" } + property var processedServer + + Connections { + target: ServersModel + + function onProcessedServerChanged() { + root.processedServer = proxyServersModel.get(0) + } + } + + SortFilterProxyModel { + id: proxyServersModel + objectName: "proxyServersModel" + + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + + Component.onCompleted: { + root.processedServer = proxyServersModel.get(0) + } + } + ListView { id: listView - anchors.fill: parent property bool isFocusable: true + anchors.fill: parent + Keys.onTabPressed: { FocusController.nextKeyTabItem() } @@ -86,9 +116,54 @@ PageType { ScrollBar.vertical: ScrollBarType {} - model: labelsModel clip: true reuseItems: true + snapMode: ListView.SnapToItem + + model: labelsModel + + header: ColumnLayout { + width: listView.width + + spacing: 4 + + BackButtonType { + id: backButton + objectName: "backButton" + + Layout.topMargin: 20 + } + + HeaderType { + id: headerContent + objectName: "headerContent" + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 10 + + actionButtonImage: "qrc:/images/controls/edit-3.svg" + + headerText: root.processedServer.name + descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") + + actionButtonFunction: function() { + serverNameEditDrawer.openTriggered() + } + } + + RenameServerDrawer { + id: serverNameEditDrawer + + parent: root + + anchors.fill: parent + expandedHeight: root.height * 0.35 + + serverNameText: root.processedServer.name + } + } delegate: ColumnLayout { width: listView.width diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 9abb6ae2c..d350ebef3 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -22,8 +22,6 @@ PageType { readonly property int pageSettingsServerProtocols: 0 readonly property int pageSettingsServerServices: 1 readonly property int pageSettingsServerData: 2 - readonly property int pageSettingsApiServerInfo: 3 - readonly property int pageSettingsApiLanguageList: 4 property var processedServer @@ -71,15 +69,6 @@ PageType { BackButtonType { id: backButton objectName: "backButton" - - backButtonFunction: function() { - if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo && - root.processedServer.isCountrySelectionAvailable) { - nestedStackView.currentIndex = root.pageSettingsApiLanguageList - } else { - PageController.closePage() - } - } } HeaderType { @@ -91,18 +80,11 @@ PageType { Layout.rightMargin: 16 Layout.bottomMargin: 10 - actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg" - : "qrc:/images/controls/edit-3.svg" + actionButtonImage: "qrc:/images/controls/edit-3.svg" headerText: root.processedServer.name descriptionText: { - if (root.processedServer.isServerFromGatewayApi) { - if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) { - return qsTr("Subscription is valid until ") + ApiServicesModel.getSelectedServiceData("endDate") - } else { - return ApiServicesModel.getSelectedServiceData("serviceDescription") - } - } else if (root.processedServer.isServerFromTelegramApi) { + if (root.processedServer.isServerFromTelegramApi) { return root.processedServer.serverDescription } else if (root.processedServer.hasWriteAccess) { return root.processedServer.credentialsLogin + " · " + root.processedServer.hostName @@ -112,60 +94,19 @@ PageType { } actionButtonFunction: function() { - if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) { - nestedStackView.currentIndex = root.pageSettingsApiServerInfo - } else { - serverNameEditDrawer.openTriggered() - } + serverNameEditDrawer.openTriggered() } } - DrawerType2 { + RenameServerDrawer { id: serverNameEditDrawer - objectName: "serverNameEditDrawer" parent: root anchors.fill: parent expandedHeight: root.height * 0.35 - expandedStateContent: ColumnLayout { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 32 - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - TextFieldWithHeaderType { - id: serverName - - Layout.fillWidth: true - headerText: qsTr("Server name") - textField.text: root.processedServer.name - textField.maximumLength: 30 - checkEmptyText: true - } - - BasicButtonType { - id: saveButton - - Layout.fillWidth: true - - text: qsTr("Save") - - clickedFunc: function() { - if (serverName.textField.text === "") { - return - } - - if (serverName.textField.text !== root.processedServer.name) { - ServersModel.setProcessedServerData("name", serverName.textField.text); - } - serverNameEditDrawer.closeTriggered() - } - } - } + serverNameText: root.processedServer.name } TabBar { @@ -181,8 +122,6 @@ PageType { color: AmneziaStyle.color.transparent } - visible: !ServersModel.getProcessedServerData("isServerFromGatewayApi") - TabButtonType { id: protocolsTab @@ -221,9 +160,7 @@ PageType { Layout.fillWidth: true - currentIndex: ServersModel.getProcessedServerData("isServerFromGatewayApi") ? - (ServersModel.getProcessedServerData("isCountrySelectionAvailable") ? - root.pageSettingsApiLanguageList : root.pageSettingsApiServerInfo) : tabBar.currentIndex + currentIndex: tabBar.currentIndex PageSettingsServerProtocols { id: protocolsPage @@ -239,16 +176,6 @@ PageType { id: dataPage stackView: root.stackView } - - PageSettingsApiServerInfo { - id: apiInfoPage - stackView: root.stackView - } - - PageSettingsApiLanguageList { - id: apiLanguageListPage - stackView: root.stackView - } } } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 17337a487..b13f4947a 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -93,7 +93,17 @@ PageType { clickedFunction: function() { ServersModel.processedIndex = index - PageController.goToPage(PageEnum.PageSettingsServerInfo) + + if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { + if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { + PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) + + } else { + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) + } + } else { + PageController.goToPage(PageEnum.PageSettingsServerInfo) + } } } From 3f55f6a629bcd67fb47966fabe09f17c5c493174 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 31 Jan 2025 14:33:12 +0700 Subject: [PATCH 02/23] refactoring: moved gateway interaction functions to a separate class --- client/CMakeLists.txt | 3 +- client/core/controllers/apiController.cpp | 267 +--------------- client/core/controllers/gatewayController.cpp | 300 ++++++++++++++++++ client/core/controllers/gatewayController.h | 35 ++ client/core/networkUtilities.cpp | 20 ++ client/core/networkUtilities.h | 5 + 6 files changed, 369 insertions(+), 261 deletions(-) create mode 100644 client/core/controllers/gatewayController.cpp create mode 100644 client/core/controllers/gatewayController.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 3ef923854..4b7540f03 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -57,7 +57,8 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) endif() qt_standard_project_setup() -qt_add_executable(${PROJECT} MANUAL_FINALIZATION) +qt_add_executable(${PROJECT} MANUAL_FINALIZATION + core/controllers/gatewayController.h core/controllers/gatewayController.cpp) if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index 6562632a9..52ec86c2b 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -1,21 +1,15 @@ #include "apiController.h" -#include -#include - #include #include #include #include -#include "QBlockCipher.h" -#include "QRsa.h" - #include "amnezia_application.h" #include "configurators/wireguard_configurator.h" #include "core/enums/apiEnums.h" -#include "utilities.h" #include "version.h" +#include "gatewayController.h" namespace { @@ -51,48 +45,6 @@ namespace } const int requestTimeoutMsecs = 12 * 1000; // 12 secs - - ErrorCode checkErrors(const QList &sslErrors, QNetworkReply *reply) - { - if (!sslErrors.empty()) { - qDebug().noquote() << sslErrors; - return ErrorCode::ApiConfigSslError; - } else if (reply->error() == QNetworkReply::NoError) { - return ErrorCode::NoError; - } else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError - || reply->error() == QNetworkReply::NetworkError::TimeoutError) { - return ErrorCode::ApiConfigTimeoutError; - } else { - QString err = reply->errorString(); - qDebug() << QString::fromUtf8(reply->readAll()); - qDebug() << reply->error(); - qDebug() << err; - qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - return ErrorCode::ApiConfigDownloadError; - } - } - - bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "", - const QByteArray &iv = "", const QByteArray &salt = "") - { - if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError - || reply->error() == QNetworkReply::NetworkError::TimeoutError) { - qDebug() << "Timeout occurred"; - return true; - } else if (responseBody.contains("html")) { - qDebug() << "The response contains an html tag"; - return true; - } else if (checkEncryption) { - try { - QSimpleCrypto::QBlockCipher blockCipher; - static_cast(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt)); - } catch (...) { - qDebug() << "Failed to decrypt the data"; - return true; - } - } - return false; - } } ApiController::ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent) @@ -176,75 +128,6 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle return; } -QStringList ApiController::getProxyUrls() -{ - QNetworkRequest request; - request.setTransferTimeout(requestTimeoutMsecs); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - QEventLoop wait; - QList sslErrors; - QNetworkReply *reply; - - QStringList proxyStorageUrl; - if (m_isDevEnvironment) { - proxyStorageUrl = QStringList { DEV_S3_ENDPOINT }; - } else { - proxyStorageUrl = QStringList { PROD_S3_ENDPOINT }; - } - - QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; - - for (const auto &proxyStorageUrl : proxyStorageUrl) { - request.setUrl(proxyStorageUrl); - reply = amnApp->manager()->get(request); - - connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); - connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); - wait.exec(); - - if (reply->error() == QNetworkReply::NetworkError::NoError) { - break; - } - reply->deleteLater(); - } - - auto encryptedResponseBody = reply->readAll(); - reply->deleteLater(); - - EVP_PKEY *privateKey = nullptr; - QByteArray responseBody; - try { - if (!m_isDevEnvironment) { - QCryptographicHash hash(QCryptographicHash::Sha512); - hash.addData(key); - QByteArray hashResult = hash.result().toHex(); - - QByteArray key = QByteArray::fromHex(hashResult.left(64)); - QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32)); - - QByteArray ba = QByteArray::fromBase64(encryptedResponseBody); - - QSimpleCrypto::QBlockCipher blockCipher; - responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv); - } else { - responseBody = encryptedResponseBody; - } - } catch (...) { - Utils::logException(); - qCritical() << "error loading private key from environment variables or decrypting payload"; - return {}; - } - - auto endpointsArray = QJsonDocument::fromJson(responseBody).array(); - - QStringList endpoints; - for (const auto &endpoint : endpointsArray) { - endpoints.push_back(endpoint.toString()); - } - return endpoints; -} - ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol) { ApiController::ApiPayloadData apiPayload; @@ -332,54 +215,8 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c ErrorCode ApiController::getServicesList(QByteArray &responseBody) { -#ifdef Q_OS_IOS - IosController::Instance()->requestInetAccess(); - QThread::msleep(10); -#endif - - QNetworkRequest request; - request.setTransferTimeout(requestTimeoutMsecs); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - request.setUrl(QString("%1v1/services").arg(m_gatewayEndpoint)); - - QNetworkReply *reply; - reply = amnApp->manager()->get(request); - - QEventLoop wait; - QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); - - QList sslErrors; - connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); - wait.exec(); - - responseBody = reply->readAll(); - - if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) { - m_proxyUrls = getProxyUrls(); - std::random_device randomDevice; - std::mt19937 generator(randomDevice()); - std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator); - for (const QString &proxyUrl : m_proxyUrls) { - qDebug() << "Go to the next endpoint"; - request.setUrl(QString("%1v1/services").arg(proxyUrl)); - reply->deleteLater(); // delete the previous reply - reply = amnApp->manager()->get(request); - - QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); - connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); - wait.exec(); - - responseBody = reply->readAll(); - if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, responseBody, false)) { - break; - } - } - } - - auto errorCode = checkErrors(sslErrors, reply); - reply->deleteLater(); - + GatewayController gatewayController(m_gatewayEndpoint, m_isDevEnvironment, requestTimeoutMsecs); + ErrorCode errorCode = gatewayController.get("%1v1/services", responseBody); if (errorCode == ErrorCode::NoError) { if (!responseBody.contains("services")) { return ErrorCode::ApiServicesMissingError; @@ -393,16 +230,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, QJsonObject &serverConfig) { -#ifdef Q_OS_IOS - IosController::Instance()->requestInetAccess(); - QThread::msleep(10); -#endif - - QNetworkRequest request; - request.setTransferTimeout(requestTimeoutMsecs); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - request.setUrl(QString("%1v1/config").arg(m_gatewayEndpoint)); + GatewayController gatewayController(m_gatewayEndpoint, m_isDevEnvironment, requestTimeoutMsecs); ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); @@ -417,92 +245,11 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co apiPayload[configKey::authData] = authData; } - QSimpleCrypto::QBlockCipher blockCipher; - QByteArray key = blockCipher.generatePrivateSalt(32); - QByteArray iv = blockCipher.generatePrivateSalt(32); - QByteArray salt = blockCipher.generatePrivateSalt(8); - - QJsonObject keyPayload; - keyPayload[configKey::aesKey] = QString(key.toBase64()); - keyPayload[configKey::aesIv] = QString(iv.toBase64()); - keyPayload[configKey::aesSalt] = QString(salt.toBase64()); - - QByteArray encryptedKeyPayload; - QByteArray encryptedApiPayload; - try { - QSimpleCrypto::QRsa rsa; - - EVP_PKEY *publicKey = nullptr; - try { - QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; - QSimpleCrypto::QRsa rsa; - publicKey = rsa.getPublicKeyFromByteArray(rsaKey); - } catch (...) { - Utils::logException(); - qCritical() << "error loading public key from environment variables"; - return ErrorCode::ApiMissingAgwPublicKey; - } - - encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING); - EVP_PKEY_free(publicKey); - - encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt); - } catch (...) { // todo change error handling in QSimpleCrypto? - Utils::logException(); - qCritical() << "error when encrypting the request body"; - return ErrorCode::ApiConfigDecryptionError; - } - - QJsonObject requestBody; - requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64()); - requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64()); - - QNetworkReply *reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson()); - - QEventLoop wait; - connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); - - QList sslErrors; - connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); - wait.exec(); - - auto encryptedResponseBody = reply->readAll(); - - if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) { - m_proxyUrls = getProxyUrls(); - std::random_device randomDevice; - std::mt19937 generator(randomDevice()); - std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator); - for (const QString &proxyUrl : m_proxyUrls) { - qDebug() << "Go to the next endpoint"; - request.setUrl(QString("%1v1/config").arg(proxyUrl)); - reply->deleteLater(); // delete the previous reply - reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson()); - - QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); - connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); - wait.exec(); - - encryptedResponseBody = reply->readAll(); - if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) { - break; - } - } - } - - auto errorCode = checkErrors(sslErrors, reply); - reply->deleteLater(); - if (errorCode) { - return errorCode; - } + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody); - try { - auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt); + if (errorCode == ErrorCode::NoError) { fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig); - } catch (...) { // todo change error handling in QSimpleCrypto? - Utils::logException(); - qCritical() << "error when decrypting the request body"; - return ErrorCode::ApiConfigDecryptionError; } return errorCode; diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp new file mode 100644 index 000000000..44a3d5d1e --- /dev/null +++ b/client/core/controllers/gatewayController.cpp @@ -0,0 +1,300 @@ +#include "gatewayController.h" + +#include +#include +#include +#include + +#include "QBlockCipher.h" +#include "QRsa.h" + +#include "amnezia_application.h" +#include "core/networkUtilities.h" +#include "utilities.h" + +namespace +{ + namespace configKey + { + constexpr char aesKey[] = "aes_key"; + constexpr char aesIv[] = "aes_iv"; + constexpr char aesSalt[] = "aes_salt"; + + constexpr char apiPayload[] = "api_payload"; + constexpr char keyPayload[] = "key_payload"; + } +} + +GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent) + : QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment), m_requestTimeoutMsecs(requestTimeoutMsecs) +{ +} + +ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody) +{ +#ifdef Q_OS_IOS + IosController::Instance()->requestInetAccess(); + QThread::msleep(10); +#endif + + QNetworkRequest request; + request.setTransferTimeout(m_requestTimeoutMsecs); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + request.setUrl(QString(endpoint).arg(m_gatewayEndpoint)); + + QNetworkReply *reply; + reply = amnApp->manager()->get(request); + + QEventLoop wait; + QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); + + QList sslErrors; + connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); + wait.exec(); + + responseBody = reply->readAll(); + + if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) { + auto requestFunction = [&request, &responseBody](const QString &url) { + request.setUrl(url); + return amnApp->manager()->get(request); + }; + + auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply, + const QList &nestedSslErrors) { + responseBody = nestedReply->readAll(); + if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) { + sslErrors = nestedSslErrors; + reply = nestedReply; + return true; + } + return false; + }; + + bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction); + } + + auto errorCode = NetworkUtilities::checkNetworkReplyErrors(sslErrors, reply); + reply->deleteLater(); + + return errorCode; +} + +ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody) +{ +#ifdef Q_OS_IOS + IosController::Instance()->requestInetAccess(); + QThread::msleep(10); +#endif + + QNetworkRequest request; + request.setTransferTimeout(m_requestTimeoutMsecs); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + request.setUrl(endpoint.arg(m_gatewayEndpoint)); + + QSimpleCrypto::QBlockCipher blockCipher; + QByteArray key = blockCipher.generatePrivateSalt(32); + QByteArray iv = blockCipher.generatePrivateSalt(32); + QByteArray salt = blockCipher.generatePrivateSalt(8); + + QJsonObject keyPayload; + keyPayload[configKey::aesKey] = QString(key.toBase64()); + keyPayload[configKey::aesIv] = QString(iv.toBase64()); + keyPayload[configKey::aesSalt] = QString(salt.toBase64()); + + QByteArray encryptedKeyPayload; + QByteArray encryptedApiPayload; + try { + QSimpleCrypto::QRsa rsa; + + EVP_PKEY *publicKey = nullptr; + try { + QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; + QSimpleCrypto::QRsa rsa; + publicKey = rsa.getPublicKeyFromByteArray(rsaKey); + } catch (...) { + Utils::logException(); + qCritical() << "error loading public key from environment variables"; + return ErrorCode::ApiMissingAgwPublicKey; + } + + encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING); + EVP_PKEY_free(publicKey); + + encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt); + } catch (...) { // todo change error handling in QSimpleCrypto? + Utils::logException(); + qCritical() << "error when encrypting the request body"; + return ErrorCode::ApiConfigDecryptionError; + } + + QJsonObject requestBody; + requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64()); + requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64()); + + QNetworkReply *reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson()); + + QEventLoop wait; + connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); + + QList sslErrors; + connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); + wait.exec(); + + QByteArray encryptedResponseBody = reply->readAll(); + + if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, false)) { + auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) { + request.setUrl(url); + return amnApp->manager()->post(request, QJsonDocument(requestBody).toJson()); + }; + + auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt, + this](QNetworkReply *nestedReply, const QList &nestedSslErrors) { + encryptedResponseBody = nestedReply->readAll(); + if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) { + sslErrors = nestedSslErrors; + reply = nestedReply; + return true; + } + return false; + }; + + bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction); + } + + auto errorCode = NetworkUtilities::checkNetworkReplyErrors(sslErrors, reply); + reply->deleteLater(); + if (errorCode) { + return errorCode; + } + + try { + responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt); + return ErrorCode::NoError; + } catch (...) { // todo change error handling in QSimpleCrypto? + Utils::logException(); + qCritical() << "error when decrypting the request body"; + return ErrorCode::ApiConfigDecryptionError; + } +} + +QStringList GatewayController::getProxyUrls() +{ + QNetworkRequest request; + request.setTransferTimeout(m_requestTimeoutMsecs); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QEventLoop wait; + QList sslErrors; + QNetworkReply *reply; + + QStringList proxyStorageUrl; + if (m_isDevEnvironment) { + proxyStorageUrl = QStringList { DEV_S3_ENDPOINT }; + } else { + proxyStorageUrl = QStringList { PROD_S3_ENDPOINT }; + } + + QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; + + for (const auto &proxyStorageUrl : proxyStorageUrl) { + request.setUrl(proxyStorageUrl); + reply = amnApp->manager()->get(request); + + connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); + connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); + wait.exec(); + + if (reply->error() == QNetworkReply::NetworkError::NoError) { + break; + } + reply->deleteLater(); + } + + auto encryptedResponseBody = reply->readAll(); + reply->deleteLater(); + + EVP_PKEY *privateKey = nullptr; + QByteArray responseBody; + try { + if (!m_isDevEnvironment) { + QCryptographicHash hash(QCryptographicHash::Sha512); + hash.addData(key); + QByteArray hashResult = hash.result().toHex(); + + QByteArray key = QByteArray::fromHex(hashResult.left(64)); + QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32)); + + QByteArray ba = QByteArray::fromBase64(encryptedResponseBody); + + QSimpleCrypto::QBlockCipher blockCipher; + responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv); + } else { + responseBody = encryptedResponseBody; + } + } catch (...) { + Utils::logException(); + qCritical() << "error loading private key from environment variables or decrypting payload"; + return {}; + } + + auto endpointsArray = QJsonDocument::fromJson(responseBody).array(); + + QStringList endpoints; + for (const auto &endpoint : endpointsArray) { + endpoints.push_back(endpoint.toString()); + } + return endpoints; +} + +bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key, + const QByteArray &iv, const QByteArray &salt) +{ + if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) { + qDebug() << "Timeout occurred"; + return true; + } else if (responseBody.contains("html")) { + qDebug() << "The response contains an html tag"; + return true; + } else if (checkEncryption) { + try { + QSimpleCrypto::QBlockCipher blockCipher; + static_cast(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt)); + } catch (...) { + qDebug() << "Failed to decrypt the data"; + return true; + } + } + return false; +} + +void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply, + std::function requestFunction, + std::function &sslErrors)> replyProcessingFunction) +{ + QStringList proxyUrls = getProxyUrls(); + std::random_device randomDevice; + std::mt19937 generator(randomDevice()); + std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator); + + QEventLoop wait; + QList sslErrors; + QByteArray responseBody; + + for (const QString &proxyUrl : proxyUrls) { + qDebug() << "Go to the next endpoint"; + reply->deleteLater(); // delete the previous reply + reply = requestFunction(endpoint.arg(proxyUrl)); + + QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); + connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); + wait.exec(); + + if (!replyProcessingFunction(reply, sslErrors)) { + break; + } + } +} diff --git a/client/core/controllers/gatewayController.h b/client/core/controllers/gatewayController.h new file mode 100644 index 000000000..45d989f06 --- /dev/null +++ b/client/core/controllers/gatewayController.h @@ -0,0 +1,35 @@ +#ifndef GATEWAYCONTROLLER_H +#define GATEWAYCONTROLLER_H + +#include +#include + +#include "core/defs.h" + +#ifdef Q_OS_IOS + #include "platforms/ios/ios_controller.h" +#endif + +class GatewayController : public QObject +{ + Q_OBJECT + +public: + explicit GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent = nullptr); + + amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody); + amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody); + +private: + QStringList getProxyUrls(); + bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "", + const QByteArray &iv = "", const QByteArray &salt = ""); + void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function requestFunction, + std::function &sslErrors)> replyProcessingFunction); + + int m_requestTimeoutMsecs; + QString m_gatewayEndpoint; + bool m_isDevEnvironment = false; +}; + +#endif // GATEWAYCONTROLLER_H diff --git a/client/core/networkUtilities.cpp b/client/core/networkUtilities.cpp index a5825f0d4..7d98e6a19 100644 --- a/client/core/networkUtilities.cpp +++ b/client/core/networkUtilities.cpp @@ -107,6 +107,26 @@ QStringList NetworkUtilities::summarizeRoutes(const QStringList &ips, const QStr return QStringList(); } +amnezia::ErrorCode NetworkUtilities::checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply) +{ + if (!sslErrors.empty()) { + qDebug().noquote() << sslErrors; + return amnezia::ErrorCode::ApiConfigSslError; + } else if (reply->error() == QNetworkReply::NoError) { + return amnezia::ErrorCode::NoError; + } else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError + || reply->error() == QNetworkReply::NetworkError::TimeoutError) { + return amnezia::ErrorCode::ApiConfigTimeoutError; + } else { + QString err = reply->errorString(); + qDebug() << QString::fromUtf8(reply->readAll()); + qDebug() << reply->error(); + qDebug() << err; + qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + return amnezia::ErrorCode::ApiConfigDownloadError; + } +} + QString NetworkUtilities::getIPAddress(const QString &host) { QHostAddress address(host); diff --git a/client/core/networkUtilities.h b/client/core/networkUtilities.h index 3057b8528..805ce9e50 100644 --- a/client/core/networkUtilities.h +++ b/client/core/networkUtilities.h @@ -5,6 +5,9 @@ #include #include #include +#include + +#include "core/defs.h" class NetworkUtilities : public QObject @@ -31,6 +34,8 @@ class NetworkUtilities : public QObject static QStringList summarizeRoutes(const QStringList &ips, const QString cidr); + static amnezia::ErrorCode checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply); + }; #endif // NETWORKUTILITIES_H From b183a3b232c2c5161b5d838ce51230b42f256c74 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 6 Feb 2025 15:26:47 +0700 Subject: [PATCH 03/23] feature: added pages for subscription settings feature --- client/CMakeLists.txt | 176 +--------------- client/amnezia_application.cpp | 6 + client/amnezia_application.h | 7 +- client/cmake/sources.cmake | 189 ++++++++++++++++++ client/core/api/apiUtils.cpp | 10 + client/core/api/apiUtils.h | 11 + client/core/controllers/apiController.cpp | 44 +++- client/core/controllers/apiController.h | 7 +- client/resources.qrc | 3 + .../controllers/api/apiSettingsController.cpp | 57 ++++++ .../controllers/api/apiSettingsController.h | 27 +++ .../ui/controllers/api/importController.cpp | 2 + client/ui/controllers/api/importController.h | 24 +++ client/ui/controllers/installController.cpp | 1 + client/ui/controllers/pageController.h | 6 +- client/ui/models/api/apiAccountInfoModel.cpp | 105 ++++++++++ client/ui/models/api/apiAccountInfoModel.h | 55 +++++ client/ui/qml/Components/ServersListView.qml | 3 +- client/ui/qml/Controls2/ListViewType.qml | 38 ++++ client/ui/qml/Pages2/PageHome.qml | 3 +- client/ui/qml/Pages2/PageSettingsAbout.qml | 3 +- .../Pages2/PageSettingsApiInstructions.qml | 87 ++++++++ .../qml/Pages2/PageSettingsApiServerInfo.qml | 127 +++++------- .../ui/qml/Pages2/PageSettingsApiSupport.qml | 107 ++++++++++ .../ui/qml/Pages2/PageSettingsServerData.qml | 10 - .../ui/qml/Pages2/PageSettingsServersList.qml | 7 +- client/ui/qml/Pages2/PageStart.qml | 10 + 27 files changed, 847 insertions(+), 278 deletions(-) create mode 100644 client/cmake/sources.cmake create mode 100644 client/core/api/apiUtils.cpp create mode 100644 client/core/api/apiUtils.h create mode 100644 client/ui/controllers/api/apiSettingsController.cpp create mode 100644 client/ui/controllers/api/apiSettingsController.h create mode 100644 client/ui/controllers/api/importController.cpp create mode 100644 client/ui/controllers/api/importController.h create mode 100644 client/ui/models/api/apiAccountInfoModel.cpp create mode 100644 client/ui/models/api/apiAccountInfoModel.h create mode 100644 client/ui/qml/Controls2/ListViewType.qml create mode 100644 client/ui/qml/Pages2/PageSettingsApiInstructions.qml create mode 100644 client/ui/qml/Pages2/PageSettingsApiSupport.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 4b7540f03..294b9646c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -57,8 +57,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) endif() qt_standard_project_setup() -qt_add_executable(${PROJECT} MANUAL_FINALIZATION - core/controllers/gatewayController.h core/controllers/gatewayController.cpp) +qt_add_executable(${PROJECT} MANUAL_FINALIZATION) if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) @@ -111,8 +110,8 @@ if(IS_CI) endif() endif() - include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake) include_directories( ${CMAKE_CURRENT_LIST_DIR}/../ipc @@ -121,167 +120,22 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) -configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) - -set(HEADERS ${HEADERS} - ${CMAKE_CURRENT_LIST_DIR}/migrations.h - ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h - ${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h - ${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h - ${CMAKE_CURRENT_LIST_DIR}/core/defs.h - ${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h - ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h - ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h - ${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h - ${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h - ${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h - ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h - ${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h - ${CMAKE_CURRENT_LIST_DIR}/ui/pages.h - ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h - ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h - ${CMAKE_CURRENT_BINARY_DIR}/version.h - ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h - ${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h - ${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h - ${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h - ${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h - ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h - ${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.h -) - -# Mozilla headres -set(HEADERS ${HEADERS} - ${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.h - ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.h - ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.h - ${CMAKE_CURRENT_LIST_DIR}/mozilla/controllerimpl.h - ${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.h -) - include_directories(mozilla) include_directories(mozilla/shared) include_directories(mozilla/models) -if(NOT IOS) - set(HEADERS ${HEADERS} - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h - ) -endif() - -if(NOT ANDROID) - set(HEADERS ${HEADERS} - ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h - ) -endif() - -set(SOURCES ${SOURCES} - ${CMAKE_CURRENT_LIST_DIR}/migrations.cpp - ${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp - ${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp - ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp - ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp - ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/serialization/outbound.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/serialization/inbound.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/serialization/ss.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/serialization/ssd.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vless.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp - ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp - ${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.cpp -) - -# Mozilla sources -set(SOURCES ${SOURCES} - ${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.cpp - ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.cpp - ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.cpp - ${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.cpp -) +configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) if(CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG") endif() -if(NOT IOS) - set(SOURCES ${SOURCES} - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp - ) -endif() - -if(NOT ANDROID) - set(SOURCES ${SOURCES} - ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp - ) -endif() - -file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h) -file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp) - -file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.h) -file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.cpp) - -file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h) -file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp) - -file(GLOB UI_MODELS_H CONFIGURE_DEPENDS - ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h - ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h - ${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h -) -file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS - ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp - ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp - ${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp -) - -file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h) -file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp) - -set(HEADERS ${HEADERS} - ${COMMON_FILES_H} - ${PAGE_LOGIC_H} - ${CONFIGURATORS_H} - ${UI_MODELS_H} - ${UI_CONTROLLERS_H} -) -set(SOURCES ${SOURCES} - ${COMMON_FILES_CPP} - ${PAGE_LOGIC_CPP} - ${CONFIGURATORS_CPP} - ${UI_MODELS_CPP} - ${UI_CONTROLLERS_CPP} -) - if(WIN32) configure_file( ${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in ${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc ) - set(HEADERS ${HEADERS} - ${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.h - ) - - set(SOURCES ${SOURCES} - ${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.cpp - ) - - set(RESOURCES ${RESOURCES} - ${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc - ) - set(LIBS ${LIBS} user32 rasapi32 @@ -325,30 +179,6 @@ endif() if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) message("Client desktop build") add_compile_definitions(AMNEZIA_DESKTOP) - - set(HEADERS ${HEADERS} - ${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.h - ${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.h - ${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.h - ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.h - ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h - ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h - ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h - ${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h - ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h - ) - - set(SOURCES ${SOURCES} - ${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.cpp - ${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.cpp - ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.cpp - ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp - ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp - ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp - ${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp - ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp - ) endif() if(ANDROID) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index aeed439b6..2bd0b9911 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -378,6 +378,9 @@ void AmneziaApplication::initModels() }); connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this, [this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); }); + + m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this)); + m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get()); } void AmneziaApplication::initControllers() @@ -463,4 +466,7 @@ void AmneziaApplication::initControllers() m_systemController.reset(new SystemController(m_settings)); m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); + + m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_settings)); + m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get()); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index cfeac0d1f..dcadb77b8 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -25,6 +25,8 @@ #include "ui/controllers/sitesController.h" #include "ui/controllers/systemController.h" #include "ui/controllers/appSplitTunnelingController.h" +// #include "ui/controllers/api/importController.h" +#include "ui/controllers/api/apiSettingsController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" #include "ui/models/protocols/cloakConfigModel.h" @@ -48,6 +50,7 @@ #include "ui/models/appSplitTunnelingModel.h" #include "ui/models/apiServicesModel.h" #include "ui/models/apiCountryModel.h" +#include "ui/models/api/apiAccountInfoModel.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -104,6 +107,7 @@ class AmneziaApplication : public AMNEZIA_BASE_CLASS QSharedPointer m_clientManagementModel; QSharedPointer m_apiServicesModel; QSharedPointer m_apiCountryModel; + QSharedPointer m_apiAccountInfoModel; QScopedPointer m_openVpnConfigModel; QScopedPointer m_shadowSocksConfigModel; @@ -114,7 +118,6 @@ class AmneziaApplication : public AMNEZIA_BASE_CLASS #ifdef Q_OS_WINDOWS QScopedPointer m_ikev2ConfigModel; #endif - QScopedPointer m_sftpConfigModel; QScopedPointer m_socks5ConfigModel; @@ -135,6 +138,8 @@ class AmneziaApplication : public AMNEZIA_BASE_CLASS QScopedPointer m_systemController; QScopedPointer m_appSplitTunnelingController; + QScopedPointer m_apiSettingsController; + QNetworkAccessManager *m_nam; QMetaObject::Connection m_reloadConfigErrorOccurredConnection; diff --git a/client/cmake/sources.cmake b/client/cmake/sources.cmake new file mode 100644 index 000000000..ace83685c --- /dev/null +++ b/client/cmake/sources.cmake @@ -0,0 +1,189 @@ +set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..) + +set(HEADERS ${HEADERS} + ${CLIENT_ROOT_DIR}/migrations.h + ${CLIENT_ROOT_DIR}/../ipc/ipc.h + ${CLIENT_ROOT_DIR}/amnezia_application.h + ${CLIENT_ROOT_DIR}/containers/containers_defs.h + ${CLIENT_ROOT_DIR}/core/defs.h + ${CLIENT_ROOT_DIR}/core/errorstrings.h + ${CLIENT_ROOT_DIR}/core/scripts_registry.h + ${CLIENT_ROOT_DIR}/core/server_defs.h + ${CLIENT_ROOT_DIR}/core/controllers/apiController.h + ${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h + ${CLIENT_ROOT_DIR}/core/controllers/serverController.h + ${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.h + ${CLIENT_ROOT_DIR}/protocols/protocols_defs.h + ${CLIENT_ROOT_DIR}/protocols/qml_register_protocols.h + ${CLIENT_ROOT_DIR}/ui/pages.h + ${CLIENT_ROOT_DIR}/ui/qautostart.h + ${CLIENT_ROOT_DIR}/protocols/vpnprotocol.h + ${CMAKE_CURRENT_BINARY_DIR}/version.h + ${CLIENT_ROOT_DIR}/core/sshclient.h + ${CLIENT_ROOT_DIR}/core/networkUtilities.h + ${CLIENT_ROOT_DIR}/core/serialization/serialization.h + ${CLIENT_ROOT_DIR}/core/serialization/transfer.h + ${CLIENT_ROOT_DIR}/core/enums/apiEnums.h + ${CLIENT_ROOT_DIR}/../common/logger/logger.h + ${CLIENT_ROOT_DIR}/utils/qmlUtils.h + ${CLIENT_ROOT_DIR}/core/api/apiUtils.h +) + +# Mozilla headres +set(HEADERS ${HEADERS} + ${CLIENT_ROOT_DIR}/mozilla/models/server.h + ${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h + ${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h + ${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h + ${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h +) + +if(NOT IOS) + set(HEADERS ${HEADERS} + ${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h + ) +endif() + +if(NOT ANDROID) + set(HEADERS ${HEADERS} + ${CLIENT_ROOT_DIR}/ui/notificationhandler.h + ) +endif() + +set(SOURCES ${SOURCES} + ${CLIENT_ROOT_DIR}/migrations.cpp + ${CLIENT_ROOT_DIR}/amnezia_application.cpp + ${CLIENT_ROOT_DIR}/containers/containers_defs.cpp + ${CLIENT_ROOT_DIR}/core/errorstrings.cpp + ${CLIENT_ROOT_DIR}/core/scripts_registry.cpp + ${CLIENT_ROOT_DIR}/core/server_defs.cpp + ${CLIENT_ROOT_DIR}/core/controllers/apiController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/serverController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.cpp + ${CLIENT_ROOT_DIR}/protocols/protocols_defs.cpp + ${CLIENT_ROOT_DIR}/ui/qautostart.cpp + ${CLIENT_ROOT_DIR}/protocols/vpnprotocol.cpp + ${CLIENT_ROOT_DIR}/core/sshclient.cpp + ${CLIENT_ROOT_DIR}/core/networkUtilities.cpp + ${CLIENT_ROOT_DIR}/core/serialization/outbound.cpp + ${CLIENT_ROOT_DIR}/core/serialization/inbound.cpp + ${CLIENT_ROOT_DIR}/core/serialization/ss.cpp + ${CLIENT_ROOT_DIR}/core/serialization/ssd.cpp + ${CLIENT_ROOT_DIR}/core/serialization/vless.cpp + ${CLIENT_ROOT_DIR}/core/serialization/trojan.cpp + ${CLIENT_ROOT_DIR}/core/serialization/vmess.cpp + ${CLIENT_ROOT_DIR}/core/serialization/vmess_new.cpp + ${CLIENT_ROOT_DIR}/../common/logger/logger.cpp + ${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp + ${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp +) + +# Mozilla sources +set(SOURCES ${SOURCES} + ${CLIENT_ROOT_DIR}/mozilla/models/server.cpp + ${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp + ${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp + ${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp +) + +if(NOT IOS) + set(SOURCES ${SOURCES} + ${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp + ) +endif() + +if(NOT ANDROID) + set(SOURCES ${SOURCES} + ${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp + ) +endif() + +file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.h) +file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.cpp) + +file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.h) +file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.cpp) + +file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.h) +file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.cpp) + +file(GLOB UI_MODELS_H CONFIGURE_DEPENDS + ${CLIENT_ROOT_DIR}/ui/models/*.h + ${CLIENT_ROOT_DIR}/ui/models/protocols/*.h + ${CLIENT_ROOT_DIR}/ui/models/services/*.h + ${CLIENT_ROOT_DIR}/ui/models/api/*.h +) +file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS + ${CLIENT_ROOT_DIR}/ui/models/*.cpp + ${CLIENT_ROOT_DIR}/ui/models/protocols/*.cpp + ${CLIENT_ROOT_DIR}/ui/models/services/*.cpp + ${CLIENT_ROOT_DIR}/ui/models/api/*.cpp +) + +file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS + ${CLIENT_ROOT_DIR}/ui/controllers/*.h + ${CLIENT_ROOT_DIR}/ui/controllers/api/*.h +) +file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS + ${CLIENT_ROOT_DIR}/ui/controllers/*.cpp + ${CLIENT_ROOT_DIR}/ui/controllers/api/*.cpp +) + +set(HEADERS ${HEADERS} + ${COMMON_FILES_H} + ${PAGE_LOGIC_H} + ${CONFIGURATORS_H} + ${UI_MODELS_H} + ${UI_CONTROLLERS_H} +) +set(SOURCES ${SOURCES} + ${COMMON_FILES_CPP} + ${PAGE_LOGIC_CPP} + ${CONFIGURATORS_CPP} + ${UI_MODELS_CPP} + ${UI_CONTROLLERS_CPP} +) + +if(WIN32) + set(HEADERS ${HEADERS} + ${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.h + ) + + set(SOURCES ${SOURCES} + ${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.cpp + ) + + set(RESOURCES ${RESOURCES} + ${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc + ) +endif() + +if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) + message("Client desktop build") + add_compile_definitions(AMNEZIA_DESKTOP) + + set(HEADERS ${HEADERS} + ${CLIENT_ROOT_DIR}/core/ipcclient.h + ${CLIENT_ROOT_DIR}/core/privileged_process.h + ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h + ${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h + ${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h + ${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.h + ${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h + ${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h + ${CLIENT_ROOT_DIR}/protocols/awgprotocol.h + ) + + set(SOURCES ${SOURCES} + ${CLIENT_ROOT_DIR}/core/ipcclient.cpp + ${CLIENT_ROOT_DIR}/core/privileged_process.cpp + ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp + ${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp + ${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp + ${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.cpp + ${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.cpp + ${CLIENT_ROOT_DIR}/protocols/xrayprotocol.cpp + ${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp + ) +endif() diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp new file mode 100644 index 000000000..eed34f65b --- /dev/null +++ b/client/core/api/apiUtils.cpp @@ -0,0 +1,10 @@ +#include "apiUtils.h" + +#include + +bool ApiUtils::isSubscriptionExpired(const QString &subscriptionEndDate) +{ + QDateTime now = QDateTime::currentDateTime(); + QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs); + return endDate < now; +} diff --git a/client/core/api/apiUtils.h b/client/core/api/apiUtils.h new file mode 100644 index 000000000..dc863c224 --- /dev/null +++ b/client/core/api/apiUtils.h @@ -0,0 +1,11 @@ +#ifndef APIUTILS_H +#define APIUTILS_H + +#include + +namespace ApiUtils +{ + bool isSubscriptionExpired(const QString &subscriptionEndDate); +} + +#endif // APIUTILS_H diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index 52ec86c2b..4e43f1483 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -8,8 +8,8 @@ #include "amnezia_application.h" #include "configurators/wireguard_configurator.h" #include "core/enums/apiEnums.h" -#include "version.h" #include "gatewayController.h" +#include "version.h" namespace { @@ -213,10 +213,29 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c } } +ErrorCode ApiController::getAccountInfo(const QString &userCountryCode, const QString &serviceType, const QJsonObject &authData, + QByteArray &responseBody) +{ + GatewayController gatewayController(m_gatewayEndpoint, m_isDevEnvironment, requestTimeoutMsecs); + + QJsonObject apiPayload; + apiPayload[configKey::userCountryCode] = userCountryCode; + apiPayload[configKey::serviceType] = serviceType; + apiPayload[configKey::authData] = authData; + + ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody); + + return errorCode; +} + ErrorCode ApiController::getServicesList(QByteArray &responseBody) { GatewayController gatewayController(m_gatewayEndpoint, m_isDevEnvironment, requestTimeoutMsecs); - ErrorCode errorCode = gatewayController.get("%1v1/services", responseBody); + + QJsonObject apiPayload; + apiPayload[configKey::osVersion] = QSysInfo::productType(); + + ErrorCode errorCode = gatewayController.post(QString("%1v1/services"), apiPayload, responseBody); if (errorCode == ErrorCode::NoError) { if (!responseBody.contains("services")) { return ErrorCode::ApiServicesMissingError; @@ -254,3 +273,24 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co return errorCode; } + +ErrorCode ApiController::getNativeConfig(const QString &userCountryCode, const QString &serviceType, const QString &protocol, + const QString &serverCountryCode, const QJsonObject &authData, QString &nativeConfig) +{ + GatewayController gatewayController(m_gatewayEndpoint, m_isDevEnvironment, requestTimeoutMsecs); + + ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + + QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); + apiPayload[configKey::userCountryCode] = userCountryCode; + apiPayload[configKey::serverCountryCode] = serverCountryCode; + apiPayload[configKey::serviceType] = serviceType; + apiPayload[configKey::authData] = authData; + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/country_config"), apiPayload, responseBody); + + nativeConfig = responseBody; + + return errorCode; +} diff --git a/client/core/controllers/apiController.h b/client/core/controllers/apiController.h index bcb25f96f..9fb1f49de 100644 --- a/client/core/controllers/apiController.h +++ b/client/core/controllers/apiController.h @@ -19,9 +19,14 @@ class ApiController : public QObject public slots: void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig); + ErrorCode getAccountInfo(const QString &userCountryCode, const QString &serviceType, const QJsonObject &authData, + QByteArray &responseBody); ErrorCode getServicesList(QByteArray &responseBody); ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, - const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, QJsonObject &serverConfig); + const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, + QJsonObject &serverConfig); + ErrorCode getNativeConfig(const QString &userCountryCode, const QString &serviceType, const QString &protocol, + const QString &serverCountryCode, const QJsonObject &authData, QString &nativeConfig); signals: void errorOccurred(ErrorCode errorCode); diff --git a/client/resources.qrc b/client/resources.qrc index c55636186..308accdad 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -225,6 +225,9 @@ ui/qml/Pages2/PageShareFullAccess.qml ui/qml/Pages2/PageStart.qml ui/qml/Components/RenameServerDrawer.qml + ui/qml/Controls2/ListViewType.qml + ui/qml/Pages2/PageSettingsApiSupport.qml + ui/qml/Pages2/PageSettingsApiInstructions.qml images/flagKit/ZW.svg diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp new file mode 100644 index 000000000..d10254323 --- /dev/null +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -0,0 +1,57 @@ +#include "apiSettingsController.h" + +#include "core/controllers/gatewayController.h" + +namespace +{ + namespace configKey + { + constexpr char userCountryCode[] = "user_country_code"; + constexpr char serverCountryCode[] = "server_country_code"; + constexpr char serviceType[] = "service_type"; + constexpr char serviceInfo[] = "service_info"; + + constexpr char apiConfig[] = "api_config"; + constexpr char authData[] = "auth_data"; + } + + const int requestTimeoutMsecs = 12 * 1000; // 12 secs +} + +ApiSettingsController::ApiSettingsController(const QSharedPointer &serversModel, + const QSharedPointer &apiAccountInfoModel, const std::shared_ptr &settings, + QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_apiAccountInfoModel(apiAccountInfoModel), m_settings(settings) +{ +} + +ApiSettingsController::~ApiSettingsController() +{ +} + +bool ApiSettingsController::getAccountInfo() +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs); + + auto processedIndex = m_serversModel->getProcessedServerIndex(); + auto serverConfig = m_serversModel->getServerConfig(processedIndex); + auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); + auto authData = serverConfig.value(configKey::authData).toObject(); + + QJsonObject apiPayload; + apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString(); + apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString(); + apiPayload[configKey::authData] = authData; + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + // emit errorOccured(errorCode); + return false; + } + + QJsonObject accountInfo = QJsonDocument::fromJson(responseBody).object(); + m_apiAccountInfoModel->updateModel(accountInfo, serverConfig); + + return true; +} diff --git a/client/ui/controllers/api/apiSettingsController.h b/client/ui/controllers/api/apiSettingsController.h new file mode 100644 index 000000000..cad0e9a15 --- /dev/null +++ b/client/ui/controllers/api/apiSettingsController.h @@ -0,0 +1,27 @@ +#ifndef APISETTINGSCONTROLLER_H +#define APISETTINGSCONTROLLER_H + +#include + +#include "ui/models/api/apiAccountInfoModel.h" +#include "ui/models/servers_model.h" + +class ApiSettingsController : public QObject +{ + Q_OBJECT +public: + ApiSettingsController(const QSharedPointer &serversModel, const QSharedPointer &apiAccountInfoModel, + const std::shared_ptr &settings, QObject *parent = nullptr); + ~ApiSettingsController(); + +public slots: + bool getAccountInfo(); + +private: + QSharedPointer m_serversModel; + QSharedPointer m_apiAccountInfoModel; + + std::shared_ptr m_settings; +}; + +#endif // APISETTINGSCONTROLLER_H diff --git a/client/ui/controllers/api/importController.cpp b/client/ui/controllers/api/importController.cpp new file mode 100644 index 000000000..e352326f8 --- /dev/null +++ b/client/ui/controllers/api/importController.cpp @@ -0,0 +1,2 @@ +#include "importController.h" + diff --git a/client/ui/controllers/api/importController.h b/client/ui/controllers/api/importController.h new file mode 100644 index 000000000..f4580360a --- /dev/null +++ b/client/ui/controllers/api/importController.h @@ -0,0 +1,24 @@ +#ifndef IMPORTCONTROLLER_H +#define IMPORTCONTROLLER_H + +#include + +#include "ui/models/api/apiAccountInfoModel.h" +#include "ui/models/servers_model.h" + +// namespace api +// { +// class ImportController : public QObject +// { +// Q_OBJECT +// public: +// ImportController(const QSharedPointer &serversModel, QSharedPointer &accountInfoModel); +// ~ImportController(); + +// private: +// QSharedPointer m_serversModel; +// QSharedPointer m_accountInfoModel; +// }; +// } + +#endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index ae0804cb7..26e433e02 100755 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -832,6 +832,7 @@ bool InstallController::updateServiceFromApi(const int serverIndex, const QStrin auto authData = serverConfig.value(configKey::authData).toObject(); QJsonObject newServerConfig; + ErrorCode errorCode = apiController.getConfigForService( m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(), apiConfig.value(configKey::serviceType).toString(), apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode, diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 428cf4f8b..6e1efda58 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -33,6 +33,8 @@ namespace PageLoader PageSettingsAppSplitTunneling, PageSettingsApiServerInfo, PageSettingsApiAvailableCountries, + PageSettingsApiSupport, + PageSettingsApiInstructions, PageServiceSftpSettings, PageServiceTorWebsiteSettings, @@ -55,7 +57,7 @@ namespace PageLoader PageProtocolOpenVpnSettings, PageProtocolShadowSocksSettings, PageProtocolCloakSettings, - PageProtocolXraySettings, + PageProtocolXraySettings, PageProtocolWireGuardSettings, PageProtocolAwgSettings, PageProtocolIKev2Settings, @@ -106,7 +108,7 @@ public slots: int incrementDrawerDepth(); int decrementDrawerDepth(); - private slots: +private slots: void onShowErrorMessage(amnezia::ErrorCode errorCode); signals: diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp new file mode 100644 index 000000000..7679f0ff1 --- /dev/null +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -0,0 +1,105 @@ +#include "apiAccountInfoModel.h" + +#include + +#include "core/api/apiUtils.h" +#include "logger.h" + +namespace +{ + Logger logger("AccountInfoModel"); + + namespace configKey + { + constexpr char availableCountries[] = "available_countries"; + constexpr char serverCountryCode[] = "server_country_code"; + constexpr char serverCountryName[] = "server_country_name"; + constexpr char lastUpdated[] = "last_updated"; + constexpr char activeDeviceCount[] = "active_device_count"; + constexpr char maxDeviceCount[] = "max_device_count"; + constexpr char subscriptionEndDate[] = "subscription_end_date"; + } +} + +ApiAccountInfoModel::ApiAccountInfoModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int ApiAccountInfoModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return 1; +} + +QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) + return QVariant(); + + switch (role) { + case SubscriptionStatusRole: { + return ApiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active"); + } + case EndDateRole: { + return QDateTime::fromString(m_accountInfoData.subscriptionEndDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); + } + case ConnectedDevicesRole: { + return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount); + } + // case ServiceDescriptionRole: { + // return apiServiceData.serviceInfo.name; + // } + } + + return QVariant(); +} + +void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig) +{ + beginResetModel(); + + AccountInfoData accountInfoData; + + auto availableCountries = accountInfoObject.value(configKey::availableCountries).toArray(); + for (const auto &country : availableCountries) { + auto countryObject = country.toObject(); + CountryInfo countryInfo; + countryInfo.serverCountryCode = countryObject.value(configKey::serverCountryCode).toString(); + countryInfo.serverCountryName = countryObject.value(configKey::serverCountryName).toString(); + countryInfo.lastUpdated = countryObject.value(configKey::lastUpdated).toString(); + + accountInfoData.AvailableCountries.push_back(countryInfo); + } + + accountInfoData.activeDeviceCount = accountInfoObject.value(configKey::activeDeviceCount).toInt(); + accountInfoData.maxDeviceCount = accountInfoObject.value(configKey::maxDeviceCount).toInt(); + accountInfoData.subscriptionEndDate = accountInfoObject.value(configKey::subscriptionEndDate).toString(); + + m_accountInfoData = accountInfoData; + + endResetModel(); +} + +QVariant ApiAccountInfoModel::data(const QString &roleString) +{ + QModelIndex modelIndex = index(0); + auto roles = roleNames(); + for (auto it = roles.begin(); it != roles.end(); it++) { + if (QString(it.value()) == roleString) { + return data(modelIndex, it.key()); + } + } + + return {}; +} + +QHash ApiAccountInfoModel::roleNames() const +{ + QHash roles; + roles[SubscriptionStatusRole] = "subscriptionStatus"; + roles[EndDateRole] = "endDate"; + roles[ConnectedDevicesRole] = "connectedDevices"; + roles[ServiceDescriptionRole] = "serviceDescription"; + + return roles; +} diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h new file mode 100644 index 000000000..f7cabb696 --- /dev/null +++ b/client/ui/models/api/apiAccountInfoModel.h @@ -0,0 +1,55 @@ +#ifndef APIACCOUNTINFOMODEL_H +#define APIACCOUNTINFOMODEL_H + +#include +#include +#include + +class ApiAccountInfoModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + SubscriptionStatusRole = Qt::UserRole + 1, + ConnectedDevicesRole, + ServiceDescriptionRole, + EndDateRole + }; + + explicit ApiAccountInfoModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig); + QVariant data(const QString &roleString); + +protected: + QHash roleNames() const override; + +private: + struct CountryInfo + { + QString serverCountryCode; + QString serverCountryName; + QString lastUpdated; + }; + + struct AccountInfoData + { + QString subscriptionEndDate; + int activeDeviceCount; + int maxDeviceCount; + + QString vpnKey; + + QVector AvailableCountries; + }; + + AccountInfoData m_accountInfoData; +}; + +#endif // APIACCOUNTINFOMODEL_H diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 8d591d86a..870b83b71 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -112,9 +112,10 @@ ListView { ServersModel.processedIndex = index if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { + ApiSettingsController.getAccountInfo() + if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) - } else { PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } diff --git a/client/ui/qml/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewType.qml new file mode 100644 index 000000000..0de43d775 --- /dev/null +++ b/client/ui/qml/Controls2/ListViewType.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls + +ListView { + id: root + + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + ScrollBar.vertical: ScrollBarType {} + + clip: true + reuseItems: true + snapMode: ListView.SnapToItem +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index fc5f53266..38a87e727 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -299,9 +299,10 @@ PageType { ServersModel.processedIndex = ServersModel.defaultIndex if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { + ApiSettingsController.getAccountInfo() + if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) - } else { PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 2160692b5..37327313a 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -47,8 +47,7 @@ PageType { readonly property string description: qsTr("For reviews and bug reports") readonly property string imageSource: "qrc:/images/controls/mail.svg" readonly property var handler: function() { - GC.copyToClipBoard(title) - PageController.showNotificationMessage(qsTr("Copied")) + Qt.openUrlExternally(qsTr("mailto:support@amnezia.org")) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml new file mode 100644 index 000000000..9106808ac --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -0,0 +1,87 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + QtObject { + id: windows + + readonly property string title: qsTr("Windows") + readonly property string imageSource: "qrc:/images/controls/external-link.svg" + readonly property var handler: function() { + Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) + } + } + + QtObject { + id: linux + + readonly property string title: qsTr("Windows") + readonly property string imageSource: "qrc:/images/controls/external-link.svg" + readonly property var handler: function() { + Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) + } + } + + property list instructionsModel: [ + windows, + linux + ] + + ListViewType { + id: listView + + anchors.fill: parent + + model: instructionsModel + + header: ColumnLayout { + width: listView.width + + BackButtonType { + id: backButton + } + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: "Support" + descriptionText: qsTr("Our technical support specialists are ready to help you at any time") + } + } + + delegate: ColumnLayout { + width: listView.width + + LabelWithButtonType { + id: telegramButton + Layout.fillWidth: true + Layout.topMargin: 6 + + text: title + leftImageSource: imageSource + + clickedFunction: handler + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 4346e6176..350f2ea20 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -18,28 +18,19 @@ PageType { id: root property list labelsModel: [ - regionObject, - priceObject, + statusObject, endDateObject, - speedObject + deviceCountObject ] QtObject { - id: regionObject + id: statusObject - readonly property string title: qsTr("For the region") - readonly property string contentKey: "region" + readonly property string title: qsTr("Subscription status") + readonly property string contentKey: "subscriptionStatus" readonly property string objectImageSource: "qrc:/images/controls/map-pin.svg" } - QtObject { - id: priceObject - - readonly property string title: qsTr("Price") - readonly property string contentKey: "price" - readonly property string objectImageSource: "qrc:/images/controls/tag.svg" - } - QtObject { id: endDateObject @@ -49,10 +40,10 @@ PageType { } QtObject { - id: speedObject + id: deviceCountObject - readonly property string title: qsTr("Speed") - readonly property string contentKey: "speed" + readonly property string title: qsTr("Connected devices") + readonly property string contentKey: "connectedDevices" readonly property string objectImageSource: "qrc:/images/controls/gauge.svg" } @@ -83,43 +74,11 @@ PageType { } } - ListView { + ListViewType { id: listView - property bool isFocusable: true - anchors.fill: parent - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } - - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } - - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } - - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } - - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } - - ScrollBar.vertical: ScrollBarType {} - - clip: true - reuseItems: true - snapMode: ListView.SnapToItem - model: labelsModel header: ColumnLayout { @@ -175,7 +134,7 @@ PageType { imageSource: objectImageSource leftText: title - rightText: ApiServicesModel.getSelectedServiceData(contentKey) + rightText: ApiAccountInfoModel.data(contentKey) visible: rightText !== "" } @@ -185,53 +144,61 @@ PageType { width: listView.width spacing: 0 - ParagraphTextType { + LabelWithButtonType { Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 + Layout.topMargin: 32 - onLinkActivated: function(link) { - Qt.openUrlExternally(link) - } - textFormat: Text.RichText - text: { - var text = ApiServicesModel.getSelectedServiceData("features") - if (text === undefined) { - return "" - } - return text.replace("%1", LanguageModel.getCurrentSiteUrl()) + visible: false + + text: qsTr("Subscription key") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Configuration files") - visible: text !== "" + descriptionText: qsTr("To connect the router") + rightImageSource: "qrc:/images/controls/chevron-right.svg" - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + clickedFunction: function() { } } + DividerType {} + LabelWithButtonType { - id: supportUuid Layout.fillWidth: true - text: qsTr("Support tag") - descriptionText: SettingsController.getInstallationUuid() + text: qsTr("Support") + rightImageSource: "qrc:/images/controls/chevron-right.svg" - descriptionOnTop: true + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsApiSupport) + } + } - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("How to connect on another devicey") + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() - } + PageController.goToPage(PageEnum.PageSettingsApiInstructions) } } + DividerType {} + BasicButtonType { id: resetButton Layout.alignment: Qt.AlignHCenter diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml new file mode 100644 index 000000000..fafd1c02c --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -0,0 +1,107 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: backButtonLayout + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + id: backButton + } + + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: "Support" + descriptionText: qsTr("Our technical support specialists are ready to help you at any time") + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Telegram") + descriptionText: qsTr("@amnezia_premium_support_bot") + rightImageSource: "qrc:/images/controls/external-link.svg" + + clickedFunction: function() { + Qt.openUrlExternally(qsTr("https://t.me/amnezia_premium_support_bot")) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Mail") + descriptionText: qsTr("support@amnezia.org") + rightImageSource: "qrc:/images/controls/external-link.svg" + + clickedFunction: function() { + Qt.openUrlExternally(qsTr("mailto:support@amnezia.org")) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Site") + descriptionText: qsTr("amnezia.org") + rightImageSource: "qrc:/images/controls/external-link.svg" + + clickedFunction: function() { + Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) + } + } + + DividerType {} + + LabelWithButtonType { + id: supportUuid + Layout.fillWidth: true + + text: qsTr("Support tag") + descriptionText: SettingsController.getInstallationUuid() + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + if (!GC.isMobile()) { + this.rightButton.forceActiveFocus() + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index cd736d39e..977e669e4 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -36,16 +36,6 @@ PageType { PageController.showErrorMessage(message) } - function onRemoveProcessedServerFinished(finishedMessage) { - if (!ServersModel.getServersCount()) { - PageController.goToPageHome() - } else { - PageController.goToStartPage() - PageController.goToPage(PageEnum.PageSettingsServersList) - } - PageController.showNotificationMessage(finishedMessage) - } - function onRebootProcessedServerFinished(finishedMessage) { PageController.showNotificationMessage(finishedMessage) } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index b13f4947a..31d9f72a5 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -95,12 +95,9 @@ PageType { ServersModel.processedIndex = index if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { - if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { - PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) + ApiSettingsController.getAccountInfo() - } else { - PageController.goToPage(PageEnum.PageSettingsApiServerInfo) - } + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } else { PageController.goToPage(PageEnum.PageSettingsServerInfo) } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 71009e283..b5a61e83c 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -154,6 +154,16 @@ PageType { PageController.goToPageHome() PageController.showNotificationMessage(message) } + + function onRemoveProcessedServerFinished(finishedMessage) { + if (!ServersModel.getServersCount()) { + PageController.goToPageHome() + } else { + PageController.goToStartPage() + PageController.goToPage(PageEnum.PageSettingsServersList) + } + PageController.showNotificationMessage(finishedMessage) + } } Connections { From 42d3d9b98a8f633093f3e27c9ebab1cd6af2316d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 7 Feb 2025 22:22:14 +0700 Subject: [PATCH 04/23] feature: added page for export api native configs --- client/CMakeLists.txt | 4 +- client/amnezia_application.cpp | 16 ++- client/amnezia_application.h | 3 +- client/cmake/sources.cmake | 1 - client/core/api/apiDefs.h | 32 ++++++ client/core/api/apiUtils.cpp | 38 ++++++- client/core/api/apiUtils.h | 8 +- client/core/controllers/apiController.cpp | 25 +---- client/core/controllers/apiController.h | 2 - client/core/enums/apiEnums.h | 9 -- client/resources.qrc | 1 + .../controllers/api/apiConfigsController.cpp | 106 ++++++++++++++++++ .../ui/controllers/api/apiConfigsController.h | 35 ++++++ .../controllers/api/apiSettingsController.cpp | 16 ++- .../controllers/api/apiSettingsController.h | 6 +- .../ui/controllers/api/importController.cpp | 2 - client/ui/controllers/api/importController.h | 24 ---- .../ui/controllers/connectionController.cpp | 8 +- client/ui/controllers/pageController.h | 1 + client/ui/models/api/apiAccountInfoModel.cpp | 45 +++++--- client/ui/models/api/apiAccountInfoModel.h | 16 +-- client/ui/models/servers_model.cpp | 6 +- client/ui/qml/Components/ServersListView.qml | 6 +- client/ui/qml/Pages2/PageHome.qml | 6 +- .../PageSettingsApiAvailableCountries.qml | 4 + .../Pages2/PageSettingsApiInstructions.qml | 66 ++++++++--- .../Pages2/PageSettingsApiNativeConfigs.qml | 87 ++++++++++++++ .../qml/Pages2/PageSettingsApiServerInfo.qml | 13 ++- .../ui/qml/Pages2/PageSettingsApiSupport.qml | 2 +- .../ui/qml/Pages2/PageSettingsServersList.qml | 2 + 30 files changed, 461 insertions(+), 129 deletions(-) create mode 100644 client/core/api/apiDefs.h delete mode 100644 client/core/enums/apiEnums.h create mode 100644 client/ui/controllers/api/apiConfigsController.cpp create mode 100644 client/ui/controllers/api/apiConfigsController.h delete mode 100644 client/ui/controllers/api/importController.cpp delete mode 100644 client/ui/controllers/api/importController.h create mode 100644 client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 294b9646c..1ed3df08a 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -57,7 +57,9 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) endif() qt_standard_project_setup() -qt_add_executable(${PROJECT} MANUAL_FINALIZATION) +qt_add_executable(${PROJECT} MANUAL_FINALIZATION + core/api/apiDefs.h +) if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 2bd0b9911..7e72defe4 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -10,8 +12,6 @@ #include #include #include -#include -#include #include "logger.h" #include "ui/models/installedAppsModel.h" @@ -282,16 +282,17 @@ bool AmneziaApplication::parseCommands() } #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) -void AmneziaApplication::startLocalServer() { +void AmneziaApplication::startLocalServer() +{ const QString serverName("AmneziaVPNInstance"); QLocalServer::removeServer(serverName); - QLocalServer* server = new QLocalServer(this); + QLocalServer *server = new QLocalServer(this); server->listen(serverName); QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() { if (server) { - QLocalSocket* clientConnection = server->nextPendingConnection(); + QLocalSocket *clientConnection = server->nextPendingConnection(); clientConnection->deleteLater(); } emit m_pageController->raiseMainWindow(); @@ -467,6 +468,9 @@ void AmneziaApplication::initControllers() m_systemController.reset(new SystemController(m_settings)); m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); - m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_settings)); + m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_settings)); m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get()); + + m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_settings)); + m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get()); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index dcadb77b8..3b9d06dd3 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -25,7 +25,7 @@ #include "ui/controllers/sitesController.h" #include "ui/controllers/systemController.h" #include "ui/controllers/appSplitTunnelingController.h" -// #include "ui/controllers/api/importController.h" +#include "ui/controllers/api/apiConfigsController.h" #include "ui/controllers/api/apiSettingsController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" @@ -139,6 +139,7 @@ class AmneziaApplication : public AMNEZIA_BASE_CLASS QScopedPointer m_appSplitTunnelingController; QScopedPointer m_apiSettingsController; + QScopedPointer m_apiConfigsController; QNetworkAccessManager *m_nam; diff --git a/client/cmake/sources.cmake b/client/cmake/sources.cmake index ace83685c..1d7c40803 100644 --- a/client/cmake/sources.cmake +++ b/client/cmake/sources.cmake @@ -23,7 +23,6 @@ set(HEADERS ${HEADERS} ${CLIENT_ROOT_DIR}/core/networkUtilities.h ${CLIENT_ROOT_DIR}/core/serialization/serialization.h ${CLIENT_ROOT_DIR}/core/serialization/transfer.h - ${CLIENT_ROOT_DIR}/core/enums/apiEnums.h ${CLIENT_ROOT_DIR}/../common/logger/logger.h ${CLIENT_ROOT_DIR}/utils/qmlUtils.h ${CLIENT_ROOT_DIR}/core/api/apiUtils.h diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h new file mode 100644 index 000000000..0431839c6 --- /dev/null +++ b/client/core/api/apiDefs.h @@ -0,0 +1,32 @@ +#ifndef APIDEFS_H +#define APIDEFS_H + +#include + +namespace apiDefs +{ + enum ConfigType { + AmneziaFreeV2 = 1, + AmneziaFreeV3, + AmneziaPremiumV1, + AmneziaPremiumV2, + SelfHosted + }; + + enum ConfigSource { + Telegram = 1, + AmneziaGateway + }; + + namespace key + { + constexpr QLatin1String configVersion("config_version"); + + constexpr QLatin1String apiConfig("api_config"); + constexpr QLatin1String stackType("stack_type"); + } + + const int requestTimeoutMsecs = 12 * 1000; // 12 secs +} + +#endif // APIDEFS_H diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp index eed34f65b..d90ac1dc8 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/api/apiUtils.cpp @@ -1,10 +1,46 @@ #include "apiUtils.h" #include +#include -bool ApiUtils::isSubscriptionExpired(const QString &subscriptionEndDate) +bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate) { QDateTime now = QDateTime::currentDateTime(); QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs); return endDate < now; } + +bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject) +{ + auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt(); + switch (configVersion) { + case apiDefs::ConfigSource::Telegram: return true; + case apiDefs::ConfigSource::AmneziaGateway: return true; + default: return false; + } +} + +apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject) +{ + auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt(); + switch (configVersion) { + case apiDefs::ConfigSource::Telegram: { + }; + case apiDefs::ConfigSource::AmneziaGateway: { + constexpr QLatin1String premium("prem"); + constexpr QLatin1String free("free"); + + auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject(); + auto stackType = apiConfigObject.value(apiDefs::key::stackType).toString(); + + if (stackType == premium) { + return apiDefs::ConfigType::AmneziaPremiumV2; + } else if (stackType == free) { + return apiDefs::ConfigType::AmneziaFreeV3; + } + } + default: { + return apiDefs::ConfigType::SelfHosted; + } + }; +} diff --git a/client/core/api/apiUtils.h b/client/core/api/apiUtils.h index dc863c224..2c9496bd2 100644 --- a/client/core/api/apiUtils.h +++ b/client/core/api/apiUtils.h @@ -3,9 +3,15 @@ #include -namespace ApiUtils +#include "apiDefs.h" + +namespace apiUtils { + bool isServerFromApi(const QJsonObject &serverConfigObject); + bool isSubscriptionExpired(const QString &subscriptionEndDate); + + apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject); } #endif // APIUTILS_H diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index 4e43f1483..34fb30a0d 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -7,7 +7,7 @@ #include "amnezia_application.h" #include "configurators/wireguard_configurator.h" -#include "core/enums/apiEnums.h" +#include "core/api/apiDefs.h" #include "gatewayController.h" #include "version.h" @@ -106,7 +106,7 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle serverConfig[config_key::containers] = newServerConfig.value(config_key::containers); serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); - if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) { + if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion); serverConfig[config_key::description] = newServerConfig.value(config_key::description); serverConfig[config_key::name] = newServerConfig.value(config_key::name); @@ -119,7 +119,7 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap()); auto apiConfig = QJsonObject::fromVariantMap(map); - if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) { + if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject()); } @@ -274,23 +274,4 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co return errorCode; } -ErrorCode ApiController::getNativeConfig(const QString &userCountryCode, const QString &serviceType, const QString &protocol, - const QString &serverCountryCode, const QJsonObject &authData, QString &nativeConfig) -{ - GatewayController gatewayController(m_gatewayEndpoint, m_isDevEnvironment, requestTimeoutMsecs); - - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); - - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = userCountryCode; - apiPayload[configKey::serverCountryCode] = serverCountryCode; - apiPayload[configKey::serviceType] = serviceType; - apiPayload[configKey::authData] = authData; - - QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/country_config"), apiPayload, responseBody); - nativeConfig = responseBody; - - return errorCode; -} diff --git a/client/core/controllers/apiController.h b/client/core/controllers/apiController.h index 9fb1f49de..ab34bde40 100644 --- a/client/core/controllers/apiController.h +++ b/client/core/controllers/apiController.h @@ -25,8 +25,6 @@ public slots: ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, QJsonObject &serverConfig); - ErrorCode getNativeConfig(const QString &userCountryCode, const QString &serviceType, const QString &protocol, - const QString &serverCountryCode, const QJsonObject &authData, QString &nativeConfig); signals: void errorOccurred(ErrorCode errorCode); diff --git a/client/core/enums/apiEnums.h b/client/core/enums/apiEnums.h deleted file mode 100644 index 1f050007b..000000000 --- a/client/core/enums/apiEnums.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef APIENUMS_H -#define APIENUMS_H - -enum ApiConfigSources { - Telegram = 1, - AmneziaGateway -}; - -#endif // APIENUMS_H diff --git a/client/resources.qrc b/client/resources.qrc index 308accdad..cd44ca60f 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -228,6 +228,7 @@ ui/qml/Controls2/ListViewType.qml ui/qml/Pages2/PageSettingsApiSupport.qml ui/qml/Pages2/PageSettingsApiInstructions.qml + ui/qml/Pages2/PageSettingsApiNativeConfigs.qml images/flagKit/ZW.svg diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp new file mode 100644 index 000000000..62ccebb89 --- /dev/null +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -0,0 +1,106 @@ +#include "ApiConfigsController.h" + +#include "configurators/wireguard_configurator.h" +#include "core/api/apiDefs.h" +#include "core/controllers/gatewayController.h" +#include "version.h" +#include "ui/controllers/systemController.h" + +namespace +{ + namespace configKey + { + constexpr char cloak[] = "cloak"; + constexpr char awg[] = "awg"; + + constexpr char apiEdnpoint[] = "api_endpoint"; + constexpr char accessToken[] = "api_key"; + constexpr char certificate[] = "certificate"; + constexpr char publicKey[] = "public_key"; + + constexpr char uuid[] = "installation_uuid"; + constexpr char osVersion[] = "os_version"; + constexpr char appVersion[] = "app_version"; + + constexpr char userCountryCode[] = "user_country_code"; + constexpr char serverCountryCode[] = "server_country_code"; + constexpr char serviceType[] = "service_type"; + constexpr char serviceInfo[] = "service_info"; + constexpr char serviceProtocol[] = "service_protocol"; + + constexpr char aesKey[] = "aes_key"; + constexpr char aesIv[] = "aes_iv"; + constexpr char aesSalt[] = "aes_salt"; + + constexpr char apiPayload[] = "api_payload"; + constexpr char keyPayload[] = "key_payload"; + + constexpr char apiConfig[] = "api_config"; + constexpr char authData[] = "auth_data"; + + constexpr char config[] = "config"; + } +} + +ApiConfigsController::ApiConfigsController(const QSharedPointer &serversModel, const std::shared_ptr &settings, + QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_settings(settings) +{ +} + +void ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName) +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + + auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); + auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + + QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); + ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + + QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); + apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); + apiPayload[configKey::serverCountryCode] = apiConfigObject.value(configKey::serverCountryCode); + apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); + apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); + apiPayload[configKey::uuid] = m_settings->getInstallationUuid(true); + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/native_config"), apiPayload, responseBody); + // // if (errorCode != ErrorCode::NoError) { + + // // } + + QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object(); + QString nativeConfig = jsonConfig.value(configKey::config).toString(); + + SystemController::saveFile(fileName, nativeConfig); +} + +ApiConfigsController::ApiPayloadData ApiConfigsController::generateApiPayloadData(const QString &protocol) +{ + ApiConfigsController::ApiPayloadData apiPayload; + if (protocol == configKey::cloak) { + apiPayload.certRequest = OpenVpnConfigurator::createCertRequest(); + } else if (protocol == configKey::awg) { + auto connData = WireguardConfigurator::genClientKeys(); + apiPayload.wireGuardClientPubKey = connData.clientPubKey; + apiPayload.wireGuardClientPrivKey = connData.clientPrivKey; + } + return apiPayload; +} + +QJsonObject ApiConfigsController::fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData) +{ + QJsonObject obj; + if (protocol == configKey::cloak) { + obj[configKey::certificate] = apiPayloadData.certRequest.request; + } else if (protocol == configKey::awg) { + obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey; + } + + obj[configKey::osVersion] = QSysInfo::productType(); + obj[configKey::appVersion] = QString(APP_VERSION); + + return obj; +} diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h new file mode 100644 index 000000000..8f3249a7b --- /dev/null +++ b/client/ui/controllers/api/apiConfigsController.h @@ -0,0 +1,35 @@ +#ifndef APICONFIGSCONTROLLER_H +#define APICONFIGSCONTROLLER_H + +#include + +#include "configurators/openvpn_configurator.h" +#include "ui/models/servers_model.h" + +class ApiConfigsController : public QObject +{ + Q_OBJECT +public: + ApiConfigsController(const QSharedPointer &serversModel, const std::shared_ptr &settings, + QObject *parent = nullptr); + +public slots: + void exportNativeConfig(const QString &serverCountryCode, const QString &fileName); + +private: + struct ApiPayloadData + { + OpenVpnConfigurator::ConnectionData certRequest; + + QString wireGuardClientPrivKey; + QString wireGuardClientPubKey; + }; + + ApiPayloadData generateApiPayloadData(const QString &protocol); + QJsonObject fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData); + + QSharedPointer m_serversModel; + std::shared_ptr m_settings; +}; + +#endif // APICONFIGSCONTROLLER_H diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp index d10254323..00620d07d 100644 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -19,9 +19,14 @@ namespace } ApiSettingsController::ApiSettingsController(const QSharedPointer &serversModel, - const QSharedPointer &apiAccountInfoModel, const std::shared_ptr &settings, - QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_apiAccountInfoModel(apiAccountInfoModel), m_settings(settings) + const QSharedPointer &apiAccountInfoModel, + const QSharedPointer &apiCountryModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), + m_serversModel(serversModel), + m_apiAccountInfoModel(apiAccountInfoModel), + m_apiCountryModel(apiCountryModel), + m_settings(settings) { } @@ -55,3 +60,8 @@ bool ApiSettingsController::getAccountInfo() return true; } + +void ApiSettingsController::updateApiCountryModel() +{ + m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), ""); +} diff --git a/client/ui/controllers/api/apiSettingsController.h b/client/ui/controllers/api/apiSettingsController.h index cad0e9a15..32aa16731 100644 --- a/client/ui/controllers/api/apiSettingsController.h +++ b/client/ui/controllers/api/apiSettingsController.h @@ -4,6 +4,7 @@ #include #include "ui/models/api/apiAccountInfoModel.h" +#include "ui/models/apiCountryModel.h" #include "ui/models/servers_model.h" class ApiSettingsController : public QObject @@ -11,15 +12,18 @@ class ApiSettingsController : public QObject Q_OBJECT public: ApiSettingsController(const QSharedPointer &serversModel, const QSharedPointer &apiAccountInfoModel, - const std::shared_ptr &settings, QObject *parent = nullptr); + const QSharedPointer &apiCountryModel, const std::shared_ptr &settings, + QObject *parent = nullptr); ~ApiSettingsController(); public slots: bool getAccountInfo(); + void updateApiCountryModel(); private: QSharedPointer m_serversModel; QSharedPointer m_apiAccountInfoModel; + QSharedPointer m_apiCountryModel; std::shared_ptr m_settings; }; diff --git a/client/ui/controllers/api/importController.cpp b/client/ui/controllers/api/importController.cpp deleted file mode 100644 index e352326f8..000000000 --- a/client/ui/controllers/api/importController.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "importController.h" - diff --git a/client/ui/controllers/api/importController.h b/client/ui/controllers/api/importController.h deleted file mode 100644 index f4580360a..000000000 --- a/client/ui/controllers/api/importController.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef IMPORTCONTROLLER_H -#define IMPORTCONTROLLER_H - -#include - -#include "ui/models/api/apiAccountInfoModel.h" -#include "ui/models/servers_model.h" - -// namespace api -// { -// class ImportController : public QObject -// { -// Q_OBJECT -// public: -// ImportController(const QSharedPointer &serversModel, QSharedPointer &accountInfoModel); -// ~ImportController(); - -// private: -// QSharedPointer m_serversModel; -// QSharedPointer m_accountInfoModel; -// }; -// } - -#endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index f9491d4e6..9cd386c5d 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -8,7 +8,7 @@ #include #include "core/controllers/vpnConfigurationController.h" -#include "core/enums/apiEnums.h" +#include "core/api/apiDefs.h" #include "version.h" ConnectionController::ConnectionController(const QSharedPointer &serversModel, @@ -48,15 +48,15 @@ void ConnectionController::openConnection() emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing); - if (configVersion == ApiConfigSources::Telegram + if (configVersion == apiDefs::ConfigSource::Telegram && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { emit updateApiConfigFromTelegram(); - } else if (configVersion == ApiConfigSources::AmneziaGateway + } else if (configVersion == apiDefs::ConfigSource::AmneziaGateway && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { emit updateApiConfigFromGateway(); } else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) { qDebug() << "attempt to update api config by expires_at event"; - if (configVersion == ApiConfigSources::Telegram) { + if (configVersion == apiDefs::ConfigSource::Telegram) { emit updateApiConfigFromTelegram(); } else { emit updateApiConfigFromGateway(); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 6e1efda58..9ec93cc01 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -35,6 +35,7 @@ namespace PageLoader PageSettingsApiAvailableCountries, PageSettingsApiSupport, PageSettingsApiInstructions, + PageSettingsApiNativeConfigs, PageServiceSftpSettings, PageServiceTorWebsiteSettings, diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index 7679f0ff1..369234916 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -12,9 +12,9 @@ namespace namespace configKey { constexpr char availableCountries[] = "available_countries"; - constexpr char serverCountryCode[] = "server_country_code"; - constexpr char serverCountryName[] = "server_country_name"; - constexpr char lastUpdated[] = "last_updated"; + // constexpr char serverCountryCode[] = "server_country_code"; + // constexpr char serverCountryName[] = "server_country_name"; + // constexpr char lastUpdated[] = "last_updated"; constexpr char activeDeviceCount[] = "active_device_count"; constexpr char maxDeviceCount[] = "max_device_count"; constexpr char subscriptionEndDate[] = "subscription_end_date"; @@ -38,7 +38,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const switch (role) { case SubscriptionStatusRole: { - return ApiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active"); + return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active"); } case EndDateRole: { return QDateTime::fromString(m_accountInfoData.subscriptionEndDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); @@ -46,9 +46,15 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const case ConnectedDevicesRole: { return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount); } - // case ServiceDescriptionRole: { - // return apiServiceData.serviceInfo.name; - // } + case ServiceDescriptionRole: { + if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) { + return tr("Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to 200 " + "Mb/s"); + } else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { + return tr("Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and " + "more. YouTube is not included in the free plan."); + } + } } return QVariant(); @@ -60,21 +66,23 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons AccountInfoData accountInfoData; - auto availableCountries = accountInfoObject.value(configKey::availableCountries).toArray(); - for (const auto &country : availableCountries) { - auto countryObject = country.toObject(); - CountryInfo countryInfo; - countryInfo.serverCountryCode = countryObject.value(configKey::serverCountryCode).toString(); - countryInfo.serverCountryName = countryObject.value(configKey::serverCountryName).toString(); - countryInfo.lastUpdated = countryObject.value(configKey::lastUpdated).toString(); + m_availableCountries = accountInfoObject.value(configKey::availableCountries).toArray(); + // for (const auto &country : availableCountries) { + // auto countryObject = country.toObject(); + // CountryInfo countryInfo; + // countryInfo.serverCountryCode = countryObject.value(configKey::serverCountryCode).toString(); + // countryInfo.serverCountryName = countryObject.value(configKey::serverCountryName).toString(); + // countryInfo.lastUpdated = countryObject.value(configKey::lastUpdated).toString(); - accountInfoData.AvailableCountries.push_back(countryInfo); - } + // accountInfoData.AvailableCountries.push_back(countryInfo); + // } accountInfoData.activeDeviceCount = accountInfoObject.value(configKey::activeDeviceCount).toInt(); accountInfoData.maxDeviceCount = accountInfoObject.value(configKey::maxDeviceCount).toInt(); accountInfoData.subscriptionEndDate = accountInfoObject.value(configKey::subscriptionEndDate).toString(); + accountInfoData.configType = apiUtils::getConfigType(serverConfig); + m_accountInfoData = accountInfoData; endResetModel(); @@ -93,6 +101,11 @@ QVariant ApiAccountInfoModel::data(const QString &roleString) return {}; } +QJsonArray ApiAccountInfoModel::getAvailableCountries() +{ + return m_availableCountries; +} + QHash ApiAccountInfoModel::roleNames() const { QHash roles; diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h index f7cabb696..66d283da6 100644 --- a/client/ui/models/api/apiAccountInfoModel.h +++ b/client/ui/models/api/apiAccountInfoModel.h @@ -5,6 +5,8 @@ #include #include +#include "core/api/apiDefs.h" + class ApiAccountInfoModel : public QAbstractListModel { Q_OBJECT @@ -27,29 +29,23 @@ public slots: void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig); QVariant data(const QString &roleString); + QJsonArray getAvailableCountries(); + protected: QHash roleNames() const override; private: - struct CountryInfo - { - QString serverCountryCode; - QString serverCountryName; - QString lastUpdated; - }; - struct AccountInfoData { QString subscriptionEndDate; int activeDeviceCount; int maxDeviceCount; - QString vpnKey; - - QVector AvailableCountries; + apiDefs::ConfigType configType; }; AccountInfoData m_accountInfoData; + QJsonArray m_availableCountries; }; #endif // APIACCOUNTINFOMODEL_H diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index b72b10c31..f77eb6fce 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -1,7 +1,7 @@ #include "servers_model.h" +#include "core/api/apiDefs.h" #include "core/controllers/serverController.h" -#include "core/enums/apiEnums.h" #include "core/networkUtilities.h" #ifdef Q_OS_IOS @@ -132,10 +132,10 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return serverHasInstalledContainers(index.row()); } case IsServerFromTelegramApiRole: { - return server.value(config_key::configVersion).toInt() == ApiConfigSources::Telegram; + return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::Telegram; } case IsServerFromGatewayApiRole: { - return server.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway; + return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway; } case ApiConfigRole: { return apiConfig; diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 870b83b71..845586ba1 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -112,11 +112,13 @@ ListView { ServersModel.processedIndex = index if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { - ApiSettingsController.getAccountInfo() - if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) } else { + PageController.showBusyIndicator(true) + ApiSettingsController.getAccountInfo() + PageController.showBusyIndicator(false) + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } } else { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 38a87e727..8b97efd12 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -299,11 +299,13 @@ PageType { ServersModel.processedIndex = ServersModel.defaultIndex if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { - ApiSettingsController.getAccountInfo() - if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) } else { + PageController.showBusyIndicator(true) + ApiSettingsController.getAccountInfo() + PageController.showBusyIndicator(false) + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } } else { diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index 853a44a38..9f00f9f42 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -92,6 +92,10 @@ PageType { descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") actionButtonFunction: function() { + PageController.showBusyIndicator(true) + ApiSettingsController.getAccountInfo() + PageController.showBusyIndicator(false) + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml index 9106808ac..d1c8231ed 100644 --- a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -20,31 +20,67 @@ PageType { id: windows readonly property string title: qsTr("Windows") - readonly property string imageSource: "qrc:/images/controls/external-link.svg" - readonly property var handler: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } + readonly property string link: qsTr("") + } + + QtObject { + id: macos + + readonly property string title: qsTr("macOS") + readonly property string link: qsTr("") + } + + QtObject { + id: android + + readonly property string title: qsTr("Android") + readonly property string link: qsTr("") + } + + QtObject { + id: androidTv + + readonly property string title: qsTr("AndroidTV") + readonly property string link: qsTr("") + } + + QtObject { + id: ios + + readonly property string title: qsTr("iOS") + readonly property string link: qsTr("") } QtObject { id: linux - readonly property string title: qsTr("Windows") - readonly property string imageSource: "qrc:/images/controls/external-link.svg" - readonly property var handler: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } + readonly property string title: qsTr("Linux") + readonly property string link: qsTr("") + } + + QtObject { + id: routers + + readonly property string title: qsTr("Routers") + readonly property string link: qsTr("") } property list instructionsModel: [ windows, - linux + macos, + android, + androidTv, + ios, + linux, + routers ] ListViewType { id: listView anchors.fill: parent + anchors.topMargin: 20 + anchors.bottomMargin: 24 model: instructionsModel @@ -62,8 +98,8 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - headerText: "Support" - descriptionText: qsTr("Our technical support specialists are ready to help you at any time") + headerText: qsTr("How to connect on another device") + descriptionText: qsTr("Instructions on the Amnezia website") } } @@ -76,9 +112,11 @@ PageType { Layout.topMargin: 6 text: title - leftImageSource: imageSource + rightImageSource: "qrc:/images/controls/external-link.svg" - clickedFunction: handler + clickedFunction: function() { + Qt.openUrlExternally(link) + } } DividerType {} diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml new file mode 100644 index 000000000..0cac82b84 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -0,0 +1,87 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + property string configExtension: ".conf" + property string configCaption: qsTr("Save AmneziaVPN config") + + ListViewType { + id: listView + + anchors.fill: parent + anchors.topMargin: 20 + anchors.bottomMargin: 24 + + model: ApiCountryModel + + header: ColumnLayout { + width: listView.width + + BackButtonType { + id: backButton + } + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Configuration files") + descriptionText: qsTr("To connect a router or AmneziaWG application") + } + } + + delegate: ColumnLayout { + width: listView.width + + LabelWithButtonType { + id: telegramButton + Layout.fillWidth: true + Layout.topMargin: 6 + + text: countryName + leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" + rightImageSource: "qrc:/images/controls/download.svg" + + clickedFunction: function() { + var fileName = "" + if (GC.isMobile()) { + fileName = countryCode + configExtension + } else { + fileName = SystemController.getFileName(configCaption, + qsTr("Config files (*" + configExtension + ")"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + countryCode, + true, + configExtension) + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + ApiConfigsController.exportNativeConfig(countryCode, fileName) + PageController.showBusyIndicator(false) + } + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 350f2ea20..0e3e72616 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -105,7 +105,7 @@ PageType { actionButtonImage: "qrc:/images/controls/edit-3.svg" headerText: root.processedServer.name - descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") + descriptionText: ApiAccountInfoModel.data("serviceDescription") actionButtonFunction: function() { serverNameEditDrawer.openTriggered() @@ -145,6 +145,8 @@ PageType { spacing: 0 LabelWithButtonType { + id: vpnKey + Layout.fillWidth: true Layout.topMargin: 32 @@ -157,17 +159,22 @@ PageType { } } - DividerType {} + DividerType { + visible: false + } LabelWithButtonType { Layout.fillWidth: true + Layout.topMargin: vpnKey.visible ? 0 : 32 text: qsTr("Configuration files") - descriptionText: qsTr("To connect the router") + descriptionText: qsTr("To connect a router or AmneziaWG application") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { + ApiSettingsController.updateApiCountryModel() + PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml index fafd1c02c..e547359e9 100644 --- a/client/ui/qml/Pages2/PageSettingsApiSupport.qml +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -37,7 +37,7 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - headerText: "Support" + headerText: qsTr("Support") descriptionText: qsTr("Our technical support specialists are ready to help you at any time") } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 31d9f72a5..bdd2366f0 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -95,7 +95,9 @@ PageType { ServersModel.processedIndex = index if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { + PageController.showBusyIndicator(true) ApiSettingsController.getAccountInfo() + PageController.showBusyIndicator(false) PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } else { From 07baf0ed65f771fbf308329b242a93135d5dca0f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 10 Feb 2025 15:10:59 +0700 Subject: [PATCH 05/23] feature: added error handling and minor ui fixes --- client/core/controllers/apiController.cpp | 15 -------- client/core/controllers/apiController.h | 3 -- .../controllers/api/apiConfigsController.cpp | 15 ++++++-- .../ui/controllers/api/apiConfigsController.h | 7 +++- .../controllers/api/apiSettingsController.cpp | 12 ++++-- .../controllers/api/apiSettingsController.h | 3 ++ client/ui/models/api/apiAccountInfoModel.cpp | 37 +++++++++++++------ client/ui/models/api/apiAccountInfoModel.h | 4 +- client/ui/qml/Components/ServersListView.qml | 5 ++- client/ui/qml/Pages2/PageHome.qml | 5 ++- .../PageSettingsApiAvailableCountries.qml | 7 +++- .../Pages2/PageSettingsApiNativeConfigs.qml | 6 ++- .../qml/Pages2/PageSettingsApiServerInfo.qml | 16 ++++++-- .../ui/qml/Pages2/PageSettingsApiSupport.qml | 6 ++- .../ui/qml/Pages2/PageSettingsServersList.qml | 5 ++- client/ui/qml/Pages2/PageStart.qml | 8 ++++ 16 files changed, 101 insertions(+), 53 deletions(-) diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index 34fb30a0d..fab914083 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -213,21 +213,6 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c } } -ErrorCode ApiController::getAccountInfo(const QString &userCountryCode, const QString &serviceType, const QJsonObject &authData, - QByteArray &responseBody) -{ - GatewayController gatewayController(m_gatewayEndpoint, m_isDevEnvironment, requestTimeoutMsecs); - - QJsonObject apiPayload; - apiPayload[configKey::userCountryCode] = userCountryCode; - apiPayload[configKey::serviceType] = serviceType; - apiPayload[configKey::authData] = authData; - - ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody); - - return errorCode; -} - ErrorCode ApiController::getServicesList(QByteArray &responseBody) { GatewayController gatewayController(m_gatewayEndpoint, m_isDevEnvironment, requestTimeoutMsecs); diff --git a/client/core/controllers/apiController.h b/client/core/controllers/apiController.h index ab34bde40..367c89e4e 100644 --- a/client/core/controllers/apiController.h +++ b/client/core/controllers/apiController.h @@ -18,9 +18,6 @@ class ApiController : public QObject public slots: void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig); - - ErrorCode getAccountInfo(const QString &userCountryCode, const QString &serviceType, const QJsonObject &authData, - QByteArray &responseBody); ErrorCode getServicesList(QByteArray &responseBody); ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 62ccebb89..f791c7e81 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -48,8 +48,13 @@ ApiConfigsController::ApiConfigsController(const QSharedPointer &s { } -void ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName) +bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName) { + if (fileName.isEmpty()) { + emit errorOccurred(ErrorCode::PermissionsError); + return false; + } + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); @@ -67,14 +72,16 @@ void ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, QByteArray responseBody; ErrorCode errorCode = gatewayController.post(QString("%1v1/native_config"), apiPayload, responseBody); - // // if (errorCode != ErrorCode::NoError) { - - // // } + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object(); QString nativeConfig = jsonConfig.value(configKey::config).toString(); SystemController::saveFile(fileName, nativeConfig); + return true; } ApiConfigsController::ApiPayloadData ApiConfigsController::generateApiPayloadData(const QString &protocol) diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h index 8f3249a7b..861bc176c 100644 --- a/client/ui/controllers/api/apiConfigsController.h +++ b/client/ui/controllers/api/apiConfigsController.h @@ -11,10 +11,13 @@ class ApiConfigsController : public QObject Q_OBJECT public: ApiConfigsController(const QSharedPointer &serversModel, const std::shared_ptr &settings, - QObject *parent = nullptr); + QObject *parent = nullptr); public slots: - void exportNativeConfig(const QString &serverCountryCode, const QString &fileName); + bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName); + +signals: + void errorOccurred(ErrorCode errorCode); private: struct ApiPayloadData diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp index 00620d07d..0bebf19eb 100644 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -1,5 +1,6 @@ #include "apiSettingsController.h" +#include "core/api/apiUtils.h" #include "core/controllers/gatewayController.h" namespace @@ -49,10 +50,13 @@ bool ApiSettingsController::getAccountInfo() apiPayload[configKey::authData] = authData; QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody); - if (errorCode != ErrorCode::NoError) { - // emit errorOccured(errorCode); - return false; + + if (apiUtils::getConfigType(serverConfig) == apiDefs::ConfigType::AmneziaPremiumV2) { + ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } } QJsonObject accountInfo = QJsonDocument::fromJson(responseBody).object(); diff --git a/client/ui/controllers/api/apiSettingsController.h b/client/ui/controllers/api/apiSettingsController.h index 32aa16731..ae9d91260 100644 --- a/client/ui/controllers/api/apiSettingsController.h +++ b/client/ui/controllers/api/apiSettingsController.h @@ -20,6 +20,9 @@ public slots: bool getAccountInfo(); void updateApiCountryModel(); +signals: + void errorOccurred(ErrorCode errorCode); + private: QSharedPointer m_serversModel; QSharedPointer m_apiAccountInfoModel; diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index 369234916..254097bcb 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -12,9 +12,6 @@ namespace namespace configKey { constexpr char availableCountries[] = "available_countries"; - // constexpr char serverCountryCode[] = "server_country_code"; - // constexpr char serverCountryName[] = "server_country_name"; - // constexpr char lastUpdated[] = "last_updated"; constexpr char activeDeviceCount[] = "active_device_count"; constexpr char maxDeviceCount[] = "max_device_count"; constexpr char subscriptionEndDate[] = "subscription_end_date"; @@ -38,12 +35,23 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const switch (role) { case SubscriptionStatusRole: { + if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { + return tr("Active"); + } + return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active"); } case EndDateRole: { + if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { + return ""; + } + return QDateTime::fromString(m_accountInfoData.subscriptionEndDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); } case ConnectedDevicesRole: { + if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { + return ""; + } return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount); } case ServiceDescriptionRole: { @@ -55,6 +63,9 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const "more. YouTube is not included in the free plan."); } } + case IsComponentVisibleRole: { + return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2; + } } return QVariant(); @@ -67,15 +78,6 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons AccountInfoData accountInfoData; m_availableCountries = accountInfoObject.value(configKey::availableCountries).toArray(); - // for (const auto &country : availableCountries) { - // auto countryObject = country.toObject(); - // CountryInfo countryInfo; - // countryInfo.serverCountryCode = countryObject.value(configKey::serverCountryCode).toString(); - // countryInfo.serverCountryName = countryObject.value(configKey::serverCountryName).toString(); - // countryInfo.lastUpdated = countryObject.value(configKey::lastUpdated).toString(); - - // accountInfoData.AvailableCountries.push_back(countryInfo); - // } accountInfoData.activeDeviceCount = accountInfoObject.value(configKey::activeDeviceCount).toInt(); accountInfoData.maxDeviceCount = accountInfoObject.value(configKey::maxDeviceCount).toInt(); @@ -106,6 +108,16 @@ QJsonArray ApiAccountInfoModel::getAvailableCountries() return m_availableCountries; } +QString ApiAccountInfoModel::getTelegramBotLink() +{ + if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { + return tr("amnezia_free_support_bot"); + } else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) { + return tr("amnezia_premium_support_bot"); + } + return ""; +} + QHash ApiAccountInfoModel::roleNames() const { QHash roles; @@ -113,6 +125,7 @@ QHash ApiAccountInfoModel::roleNames() const roles[EndDateRole] = "endDate"; roles[ConnectedDevicesRole] = "connectedDevices"; roles[ServiceDescriptionRole] = "serviceDescription"; + roles[IsComponentVisibleRole] = "isComponentVisible"; return roles; } diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h index 66d283da6..9dfdc508f 100644 --- a/client/ui/models/api/apiAccountInfoModel.h +++ b/client/ui/models/api/apiAccountInfoModel.h @@ -16,7 +16,8 @@ class ApiAccountInfoModel : public QAbstractListModel SubscriptionStatusRole = Qt::UserRole + 1, ConnectedDevicesRole, ServiceDescriptionRole, - EndDateRole + EndDateRole, + IsComponentVisibleRole }; explicit ApiAccountInfoModel(QObject *parent = nullptr); @@ -30,6 +31,7 @@ public slots: QVariant data(const QString &roleString); QJsonArray getAvailableCountries(); + QString getTelegramBotLink(); protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 845586ba1..3a6792acd 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -116,8 +116,11 @@ ListView { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) } else { PageController.showBusyIndicator(true) - ApiSettingsController.getAccountInfo() + let result = ApiSettingsController.getAccountInfo() PageController.showBusyIndicator(false) + if (!result) { + return + } PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 8b97efd12..d189044dc 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -303,8 +303,11 @@ PageType { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) } else { PageController.showBusyIndicator(true) - ApiSettingsController.getAccountInfo() + let result = ApiSettingsController.getAccountInfo() PageController.showBusyIndicator(false) + if (!result) { + return + } PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index 9f00f9f42..41735b14d 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -89,12 +89,15 @@ PageType { actionButtonImage: "qrc:/images/controls/settings.svg" headerText: root.processedServer.name - descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") + descriptionText: qsTr("Locations for connection") actionButtonFunction: function() { PageController.showBusyIndicator(true) - ApiSettingsController.getAccountInfo() + let result = ApiSettingsController.getAccountInfo() PageController.showBusyIndicator(false) + if (!result) { + return + } PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml index 0cac82b84..4e897e442 100644 --- a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -75,8 +75,12 @@ PageType { } if (fileName !== "") { PageController.showBusyIndicator(true) - ApiConfigsController.exportNativeConfig(countryCode, fileName) + let result = ApiConfigsController.exportNativeConfig(countryCode, fileName) PageController.showBusyIndicator(false) + + if (result) { + PageController.showNotificationMessage(qsTr("Config file saved")) + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 0e3e72616..659df1684 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -141,16 +141,20 @@ PageType { } footer: ColumnLayout { + id: footer + width: listView.width spacing: 0 + readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible") + LabelWithButtonType { id: vpnKey Layout.fillWidth: true Layout.topMargin: 32 - visible: false + visible: footer.isVisibleForAmneziaFree text: qsTr("Subscription key") rightImageSource: "qrc:/images/controls/chevron-right.svg" @@ -160,12 +164,13 @@ PageType { } DividerType { - visible: false + visible: footer.isVisibleForAmneziaFree } LabelWithButtonType { Layout.fillWidth: true - Layout.topMargin: vpnKey.visible ? 0 : 32 + + visible: footer.isVisibleForAmneziaFree text: qsTr("Configuration files") @@ -178,10 +183,13 @@ PageType { } } - DividerType {} + DividerType { + visible: footer.isVisibleForAmneziaFree + } LabelWithButtonType { Layout.fillWidth: true + Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32 text: qsTr("Support") rightImageSource: "qrc:/images/controls/chevron-right.svg" diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml index e547359e9..64921a61a 100644 --- a/client/ui/qml/Pages2/PageSettingsApiSupport.qml +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -44,12 +44,14 @@ PageType { LabelWithButtonType { Layout.fillWidth: true + readonly property string telegramBotLink: ApiAccountInfoModel.getTelegramBotLink() + text: qsTr("Telegram") - descriptionText: qsTr("@amnezia_premium_support_bot") + descriptionText: "@" + telegramBotLink rightImageSource: "qrc:/images/controls/external-link.svg" clickedFunction: function() { - Qt.openUrlExternally(qsTr("https://t.me/amnezia_premium_support_bot")) + Qt.openUrlExternally("https://t.me/" + telegramBotLink) } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index bdd2366f0..fcfcd1145 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -96,8 +96,11 @@ PageType { if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { PageController.showBusyIndicator(true) - ApiSettingsController.getAccountInfo() + let result = ApiSettingsController.getAccountInfo() PageController.showBusyIndicator(false) + if (!result) { + return + } PageController.goToPage(PageEnum.PageSettingsApiServerInfo) } else { diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index b5a61e83c..6081bbc81 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -224,6 +224,14 @@ PageType { } } + Connections { + target: ApiSettingsController + + function onErrorOccurred(error) { + PageController.showErrorMessage(error) + } + } + StackViewType { id: tabBarStackView objectName: "tabBarStackView" From db3164223a037d7bd4da7864f3d09eeabfd3fa5c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 12 Feb 2025 12:43:11 +0700 Subject: [PATCH 06/23] feature: added share vpn key to subscription settings page --- client/CMakeLists.txt | 1 + client/core/api/apiDefs.h | 2 + client/core/defs.h | 3 -- client/core/qrCodeUtils.cpp | 35 ++++++++++++++ client/core/qrCodeUtils.h | 17 +++++++ .../controllers/api/apiConfigsController.cpp | 30 ++++++++++-- .../ui/controllers/api/apiConfigsController.h | 11 +++++ client/ui/controllers/exportController.cpp | 46 ++++--------------- client/ui/controllers/exportController.h | 3 -- client/ui/controllers/importController.cpp | 19 ++++++-- .../qml/Components/ShareConnectionDrawer.qml | 12 +++-- .../qml/Pages2/PageSettingsApiServerInfo.qml | 20 ++++++++ 12 files changed, 147 insertions(+), 52 deletions(-) create mode 100644 client/core/qrCodeUtils.cpp create mode 100644 client/core/qrCodeUtils.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 1ed3df08a..24edfb073 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -59,6 +59,7 @@ endif() qt_standard_project_setup() qt_add_executable(${PROJECT} MANUAL_FINALIZATION core/api/apiDefs.h + core/qrCodeUtils.h core/qrCodeUtils.cpp ) if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h index 0431839c6..2df4c8337 100644 --- a/client/core/api/apiDefs.h +++ b/client/core/api/apiDefs.h @@ -24,6 +24,8 @@ namespace apiDefs constexpr QLatin1String apiConfig("api_config"); constexpr QLatin1String stackType("stack_type"); + + constexpr QLatin1String vpnKey("vpn_key"); } const int requestTimeoutMsecs = 12 * 1000; // 12 secs diff --git a/client/core/defs.h b/client/core/defs.h index c0db2e127..4ea0e6130 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -6,9 +6,6 @@ namespace amnezia { - - constexpr const qint16 qrMagicCode = 1984; - struct ServerCredentials { QString hostName; diff --git a/client/core/qrCodeUtils.cpp b/client/core/qrCodeUtils.cpp new file mode 100644 index 000000000..02f43ff07 --- /dev/null +++ b/client/core/qrCodeUtils.cpp @@ -0,0 +1,35 @@ +#include "qrCodeUtils.h" + +#include +#include + +QList qrCodeUtuls::generateQrCodeImageSeries(const QByteArray &data) +{ + double k = 850; + + quint8 chunksCount = std::ceil(data.size() / k); + QList chunks; + for (int i = 0; i < data.size(); i = i + k) { + QByteArray chunk; + QDataStream s(&chunk, QIODevice::WriteOnly); + s << qrCodeUtuls::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k); + + QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW); + QString svg = QString::fromStdString(toSvgString(qr, 1)); + chunks.append(svgToBase64(svg)); + } + + return chunks; +} + +QString qrCodeUtuls::svgToBase64(const QString &image) +{ + return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data()); +} + +qrcodegen::QrCode qrCodeUtuls::generateQrCode(const QByteArray &data) +{ + return qrcodegen::QrCode::encodeText(data, qrcodegen::QrCode::Ecc::LOW); +} diff --git a/client/core/qrCodeUtils.h b/client/core/qrCodeUtils.h new file mode 100644 index 000000000..f5f207c15 --- /dev/null +++ b/client/core/qrCodeUtils.h @@ -0,0 +1,17 @@ +#ifndef QRCODEUTILS_H +#define QRCODEUTILS_H + +#include + +#include "qrcodegen.hpp" + +namespace qrCodeUtuls +{ + constexpr const qint16 qrMagicCode = 1984; + + QList generateQrCodeImageSeries(const QByteArray &data); + qrcodegen::QrCode generateQrCode(const QByteArray &data); + QString svgToBase64(const QString &image); +}; + +#endif // QRCODEUTILS_H diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index f791c7e81..0858ecff2 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -1,10 +1,11 @@ -#include "ApiConfigsController.h" +#include "apiConfigsController.h" #include "configurators/wireguard_configurator.h" #include "core/api/apiDefs.h" #include "core/controllers/gatewayController.h" -#include "version.h" +#include "core/qrCodeUtils.h" #include "ui/controllers/systemController.h" +#include "version.h" namespace { @@ -43,7 +44,7 @@ namespace } ApiConfigsController::ApiConfigsController(const QSharedPointer &serversModel, const std::shared_ptr &settings, - QObject *parent) + QObject *parent) : QObject(parent), m_serversModel(serversModel), m_settings(settings) { } @@ -84,6 +85,19 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, return true; } +void ApiConfigsController::prepareVpnKeyExport() +{ + auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); + auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + + auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString(); + + auto qr = qrCodeUtuls::generateQrCode(vpnKey.toUtf8()); + m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); + + emit vpnKeyExportReady(); +} + ApiConfigsController::ApiPayloadData ApiConfigsController::generateApiPayloadData(const QString &protocol) { ApiConfigsController::ApiPayloadData apiPayload; @@ -111,3 +125,13 @@ QJsonObject ApiConfigsController::fillApiPayload(const QString &protocol, const return obj; } + +QList ApiConfigsController::getQrCodes() +{ + return m_qrCodes; +} + +int ApiConfigsController::getQrCodesCount() +{ + return m_qrCodes.size(); +} diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h index 861bc176c..f9bf977f4 100644 --- a/client/ui/controllers/api/apiConfigsController.h +++ b/client/ui/controllers/api/apiConfigsController.h @@ -13,11 +13,17 @@ class ApiConfigsController : public QObject ApiConfigsController(const QSharedPointer &serversModel, const std::shared_ptr &settings, QObject *parent = nullptr); + Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY vpnKeyExportReady) + Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY vpnKeyExportReady) + public slots: bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName); + // bool exportVpnKey(const QString &fileName); + void prepareVpnKeyExport(); signals: void errorOccurred(ErrorCode errorCode); + void vpnKeyExportReady(); private: struct ApiPayloadData @@ -31,6 +37,11 @@ public slots: ApiPayloadData generateApiPayloadData(const QString &protocol); QJsonObject fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData); + QList getQrCodes(); + int getQrCodesCount(); + + QList m_qrCodes; + QSharedPointer m_serversModel; std::shared_ptr m_settings; }; diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 8681406e9..516d1f4d0 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -9,8 +9,8 @@ #include #include "core/controllers/vpnConfigurationController.h" +#include "core/qrCodeUtils.h" #include "systemController.h" -#include "qrcodegen.hpp" ExportController::ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const QSharedPointer &clientManagementModel, @@ -50,7 +50,7 @@ void ExportController::generateFullAccessConfig() compressedConfig = qCompress(compressedConfig, 8); m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); - m_qrCodes = generateQrCodeImageSeries(compressedConfig); + m_qrCodes = qrCodeUtuls::generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); } @@ -92,7 +92,7 @@ void ExportController::generateConnectionConfig(const QString &clientName) compressedConfig = qCompress(compressedConfig, 8); m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); - m_qrCodes = generateQrCodeImageSeries(compressedConfig); + m_qrCodes = qrCodeUtuls::generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); } @@ -149,7 +149,7 @@ void ExportController::generateOpenVpnConfig(const QString &clientName) m_config.append(line + "\n"); } - m_qrCodes = generateQrCodeImageSeries(m_config.toUtf8()); + m_qrCodes = qrCodeUtuls::generateQrCodeImageSeries(m_config.toUtf8()); emit exportConfigChanged(); } @@ -167,8 +167,8 @@ void ExportController::generateWireGuardConfig(const QString &clientName) m_config.append(line + "\n"); } - qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW); - m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1))); + auto qr = qrCodeUtuls::generateQrCode(m_config.toUtf8()); + m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); emit exportConfigChanged(); } @@ -187,8 +187,8 @@ void ExportController::generateAwgConfig(const QString &clientName) m_config.append(line + "\n"); } - qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW); - m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1))); + auto qr = qrCodeUtuls::generateQrCode(m_config.toUtf8()); + m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); emit exportConfigChanged(); } @@ -221,8 +221,8 @@ void ExportController::generateShadowSocksConfig() m_nativeConfigString = "ss://" + m_nativeConfigString.toUtf8().toBase64(); - qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_nativeConfigString.toUtf8(), qrcodegen::QrCode::Ecc::LOW); - m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1))); + auto qr = qrCodeUtuls::generateQrCode(m_nativeConfigString.toUtf8()); + m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); emit exportConfigChanged(); } @@ -312,32 +312,6 @@ void ExportController::renameClient(const int row, const QString &clientName, co } } -QList ExportController::generateQrCodeImageSeries(const QByteArray &data) -{ - double k = 850; - - quint8 chunksCount = std::ceil(data.size() / k); - QList chunks; - for (int i = 0; i < data.size(); i = i + k) { - QByteArray chunk; - QDataStream s(&chunk, QIODevice::WriteOnly); - s << amnezia::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k); - - QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - - qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW); - QString svg = QString::fromStdString(toSvgString(qr, 1)); - chunks.append(svgToBase64(svg)); - } - - return chunks; -} - -QString ExportController::svgToBase64(const QString &image) -{ - return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data()); -} - int ExportController::getQrCodesCount() { return m_qrCodes.size(); diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index a2c9fcfaf..5fb3e6b33 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -50,9 +50,6 @@ public slots: void saveFile(const QString &fileName, const QString &data); private: - QList generateQrCodeImageSeries(const QByteArray &data); - QString svgToBase64(const QString &image); - int getQrCodesCount(); void clearPreviousConfig(); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 28bbc9f6b..c79b32882 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -7,6 +7,8 @@ #include #include +#include "core/api/apiDefs.h" +#include "core/api/apiUtils.h" #include "core/errorstrings.h" #include "core/serialization/serialization.h" #include "systemController.h" @@ -45,7 +47,8 @@ namespace if (config.contains(backupPattern)) { return ConfigTypes::Backup; - } else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) || config.contains(amneziaPremiumConfigPattern) + } else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) + || config.contains(amneziaPremiumConfigPattern) || (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName) && config.contains(amneziaConfigPatternPassword))) { return ConfigTypes::Amnezia; @@ -149,8 +152,8 @@ bool ImportController::extractConfigFromData(QString data) m_configType = checkConfigFormat(config); if (m_configType == ConfigTypes::Invalid) { - data.replace("vpn://", ""); - QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + config.replace("vpn://", ""); + QByteArray ba = QByteArray::fromBase64(config.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); QByteArray ba_uncompressed = qUncompress(ba); if (!ba_uncompressed.isEmpty()) { ba = ba_uncompressed; @@ -180,6 +183,13 @@ bool ImportController::extractConfigFromData(QString data) } case ConfigTypes::Amnezia: { m_config = QJsonDocument::fromJson(config.toUtf8()).object(); + + if (apiUtils::isServerFromApi(m_config)) { + auto apiConfig = m_config.value(apiDefs::key::apiConfig).toObject(); + apiConfig[apiDefs::key::vpnKey] = data; + m_config[apiDefs::key::apiConfig] = apiConfig; + } + processAmneziaConfig(m_config); if (!m_config.empty()) { checkForMaliciousStrings(m_config); @@ -680,7 +690,8 @@ void ImportController::processAmneziaConfig(QJsonObject &config) } QJsonObject jsonConfig = QJsonDocument::fromJson(protocolConfig.toUtf8()).object(); - jsonConfig[config_key::mtu] = dockerContainer == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu; + jsonConfig[config_key::mtu] = + dockerContainer == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu; containerConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson()); diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index f98944f07..9a9f1aa16 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -22,7 +22,11 @@ DrawerType2 { property string headerText property string configContentHeaderText - property string contentVisible + property string shareButtonText: qsTr("Share") + property string copyButtonText: qsTr("Copy") + property bool showSettingsButtonVisible: true + + property bool contentVisible property string configExtension: ".vpn" property string configCaption: qsTr("Save AmneziaVPN config") @@ -80,7 +84,7 @@ DrawerType2 { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("Share") + text: root.shareButtonText leftImageSource: "qrc:/images/controls/share-2.svg" clickedFunc: function() { @@ -116,7 +120,7 @@ DrawerType2 { textColor: AmneziaStyle.color.paleGray borderWidth: 1 - text: qsTr("Copy") + text: root.copyButtonText leftImageSource: "qrc:/images/controls/copy.svg" Keys.onReturnPressed: { copyConfigTextButton.clicked() } @@ -153,6 +157,8 @@ DrawerType2 { Layout.leftMargin: 16 Layout.rightMargin: 16 + visible: root.showSettingsButtonVisible + defaultColor: AmneziaStyle.color.transparent hoveredColor: AmneziaStyle.color.translucentWhite pressedColor: AmneziaStyle.color.sheerWhite diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 659df1684..303ba36cd 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -160,6 +160,20 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { + shareConnectionDrawer.headerText = qsTr("Amnezia Premium subscription key") + + shareConnectionDrawer.openTriggered() + shareConnectionDrawer.contentVisible = false + shareConnectionDrawer.showSettingsButtonVisible = false; + shareConnectionDrawer.shareButtonText = qsTr("Save VPN key to file") + shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key") + + + PageController.showBusyIndicator(true) + + ApiConfigsController.prepareVpnKeyExport() + + PageController.showBusyIndicator(false) } } @@ -292,4 +306,10 @@ PageType { } } } + + ShareConnectionDrawer { + id: shareConnectionDrawer + + anchors.fill: parent + } } From e9250afd2b908229749063e495c171b6d422a833 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 15 Feb 2025 11:50:42 +0700 Subject: [PATCH 07/23] refactoring: simplified the validity check of the config before connection - improved project structure --- client/CMakeLists.txt | 5 +- client/amnezia_application.cpp | 290 +-------------- client/amnezia_application.h | 97 +---- client/cmake/sources.cmake | 7 +- client/core/api/apiDefs.h | 2 +- client/core/api/apiUtils.cpp | 5 + client/core/api/apiUtils.h | 1 + client/core/controllers/apiController.cpp | 262 -------------- client/core/controllers/apiController.h | 50 --- client/core/controllers/coreController.cpp | 332 ++++++++++++++++++ client/core/controllers/coreController.h | 133 +++++++ .../vpnConfigurationController.cpp | 6 +- .../controllers/vpnConfigurationController.h | 5 +- client/core/defs.h | 2 + client/core/errorstrings.cpp | 2 + .../controllers/api/apiConfigsController.cpp | 294 +++++++++++++++- .../ui/controllers/api/apiConfigsController.h | 22 +- .../ui/controllers/connectionController.cpp | 136 +------ client/ui/controllers/connectionController.h | 12 +- client/ui/controllers/installController.cpp | 140 +++----- client/ui/controllers/installController.h | 15 +- .../ui/models/{ => api}/apiServicesModel.cpp | 0 client/ui/models/{ => api}/apiServicesModel.h | 0 client/ui/models/servers_model.cpp | 2 +- client/ui/models/servers_model.h | 2 +- .../PageSettingsApiAvailableCountries.qml | 2 +- .../qml/Pages2/PageSettingsApiServerInfo.qml | 2 +- .../Pages2/PageSetupWizardApiServiceInfo.qml | 2 +- .../Pages2/PageSetupWizardConfigSource.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 2 +- client/ui/qml/Pages2/PageStart.qml | 70 ++-- 31 files changed, 937 insertions(+), 965 deletions(-) delete mode 100644 client/core/controllers/apiController.cpp delete mode 100644 client/core/controllers/apiController.h create mode 100644 client/core/controllers/coreController.cpp create mode 100644 client/core/controllers/coreController.h rename client/ui/models/{ => api}/apiServicesModel.cpp (100%) rename client/ui/models/{ => api}/apiServicesModel.h (100%) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 24edfb073..294b9646c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -57,10 +57,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) endif() qt_standard_project_setup() -qt_add_executable(${PROJECT} MANUAL_FINALIZATION - core/api/apiDefs.h - core/qrCodeUtils.h core/qrCodeUtils.cpp -) +qt_add_executable(${PROJECT} MANUAL_FINALIZATION) if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 7e72defe4..8dea8c0a4 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -14,22 +14,14 @@ #include #include "logger.h" +#include "ui/controllers/pageController.h" #include "ui/models/installedAppsModel.h" #include "version.h" #include "platforms/ios/QRCodeReaderBase.h" -#if defined(Q_OS_ANDROID) - #include "core/installedAppsImageProvider.h" - #include "platforms/android/android_controller.h" -#endif #include "protocols/qml_register_protocols.h" -#if defined(Q_OS_IOS) - #include "platforms/ios/ios_controller.h" - #include -#endif - AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv) { setQuitOnLastWindowClosed(false); @@ -84,79 +76,12 @@ void AmneziaApplication::init() m_vpnConnection->moveToThread(&m_vpnConnectionThread); m_vpnConnectionThread.start(); - initModels(); - loadTranslator(); - initControllers(); - -#ifdef Q_OS_ANDROID - if (!AndroidController::initLogging()) { - qFatal("Android logging initialization failed"); - } - AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs()); - connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs); - - AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled()); - connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled); - - connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer); - - connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); }); - - connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) { - m_connectionController->onConnectionStateChanged(state); - if (m_vpnConnection) - m_vpnConnection->restoreConnection(); - }); - if (!AndroidController::instance()->initialize()) { - qFatal("Android controller initialization failed"); - } - - connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) { - emit m_pageController->goToPageHome(); - m_importController->extractConfigFromData(data); - data.clear(); - emit m_pageController->goToPageViewConfig(); - }); - - m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider); -#endif - -#ifdef Q_OS_IOS - IosController::Instance()->initialize(); - connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) { - emit m_pageController->goToPageHome(); - m_importController->extractConfigFromData(data); - emit m_pageController->goToPageViewConfig(); - }); - - connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) { - emit m_pageController->goToPageHome(); - m_pageController->goToPageSettingsBackup(); - emit m_settingsController->importBackupFromOutside(filePath); - }); - - QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); }); - - connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); }); -#endif - -#ifndef Q_OS_ANDROID - m_notificationHandler.reset(NotificationHandler::create(nullptr)); - - connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), - &NotificationHandler::setConnectionState); - - connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow); - connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), - static_cast(&ConnectionController::openConnection)); - connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), - &ConnectionController::closeConnection); - connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated); -#endif + m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine)); m_engine->addImportPath("qrc:/ui/qml/Modules/"); m_engine->load(url); - m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); + + m_coreController->setQmlRoot(); bool enabled = m_settings->isSaveLogs(); #ifndef Q_OS_ANDROID @@ -168,13 +93,13 @@ void AmneziaApplication::init() #endif Logger::setServiceLogsEnabled(enabled); -#ifdef Q_OS_WIN +#ifdef Q_OS_WIN //TODO if (m_parser.isSet("a")) - m_pageController->showOnStartup(); + m_coreController->pageController()->showOnStartup(); else - emit m_pageController->raiseMainWindow(); + emit m_coreController->pageController()->raiseMainWindow(); #else - m_pageController->showOnStartup(); + m_coreController->pageController()->showOnStartup(); #endif // Android TextArea clipboard workaround @@ -231,33 +156,6 @@ void AmneziaApplication::loadFonts() QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf"); } -void AmneziaApplication::loadTranslator() -{ - auto locale = m_settings->getAppLanguage(); - m_translator.reset(new QTranslator()); - updateTranslator(locale); -} - -void AmneziaApplication::updateTranslator(const QLocale &locale) -{ - if (!m_translator->isEmpty()) { - QCoreApplication::removeTranslator(m_translator.get()); - } - - QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm"; - if (m_translator->load(strFileName)) { - if (QCoreApplication::installTranslator(m_translator.get())) { - m_settings->setAppLanguage(locale); - } - } else { - m_settings->setAppLanguage(QLocale::English); - } - - m_engine->retranslate(); - - emit translationsUpdated(); -} - bool AmneziaApplication::parseCommands() { m_parser.setApplicationDescription(APPLICATION_NAME); @@ -295,7 +193,7 @@ void AmneziaApplication::startLocalServer() QLocalSocket *clientConnection = server->nextPendingConnection(); clientConnection->deleteLater(); } - emit m_pageController->raiseMainWindow(); + emit m_coreController->pageController()->raiseMainWindow(); //TODO }); } #endif @@ -304,173 +202,3 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const { return m_engine; } - -void AmneziaApplication::initModels() -{ - m_containersModel.reset(new ContainersModel(this)); - m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); - - m_defaultServerContainersModel.reset(new ContainersModel(this)); - m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get()); - - m_serversModel.reset(new ServersModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); - connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel); - connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(), - &ContainersModel::updateModel); - m_serversModel->resetModel(); - - m_languageModel.reset(new LanguageModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); - connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); - connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); - - m_sitesModel.reset(new SitesModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); - - m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get()); - - m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); - - m_openVpnConfigModel.reset(new OpenVpnConfigModel(this)); - m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get()); - - m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this)); - m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get()); - - m_cloakConfigModel.reset(new CloakConfigModel(this)); - m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get()); - - m_wireGuardConfigModel.reset(new WireGuardConfigModel(this)); - m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get()); - - m_awgConfigModel.reset(new AwgConfigModel(this)); - m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get()); - - m_xrayConfigModel.reset(new XrayConfigModel(this)); - m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get()); - -#ifdef Q_OS_WINDOWS - m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); - m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); -#endif - - m_sftpConfigModel.reset(new SftpConfigModel(this)); - m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get()); - - m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this)); - m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get()); - - m_clientManagementModel.reset(new ClientManagementModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get()); - connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(), - &ServersModel::clearCachedProfile); - - m_apiServicesModel.reset(new ApiServicesModel(this)); - m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get()); - - m_apiCountryModel.reset(new ApiCountryModel(this)); - m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get()); - connect(m_serversModel.get(), &ServersModel::updateApiLanguageModel, this, [this]() { - m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(), - m_serversModel->getProcessedServerData("apiServerCountryCode").toString()); - }); - connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this, - [this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); }); - - m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this)); - m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get()); -} - -void AmneziaApplication::initControllers() -{ - m_connectionController.reset( - new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings)); - m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); - - connect(m_connectionController.get(), qOverload(&ConnectionController::connectionErrorOccurred), this, - [this](const QString &errorMessage) { - emit m_pageController->showErrorMessage(errorMessage); - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); - }); - - connect(m_connectionController.get(), qOverload(&ConnectionController::connectionErrorOccurred), this, - [this](ErrorCode errorCode) { - emit m_pageController->showErrorMessage(errorCode); - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); - }); - - connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(), - &ConnectionController::toggleConnection, Qt::QueuedConnection); - - m_pageController.reset(new PageController(m_serversModel, m_settings)); - m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); - - m_focusController.reset(new FocusController(m_engine, this)); - m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get()); - - m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, - m_apiServicesModel, m_settings)); - m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); - connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), - &PageController::showPassphraseRequestDrawer); - connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), - &InstallController::setEncryptedPassphrase); - connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(), - &ConnectionController::onCurrentContainerUpdated); - - connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() { - disconnect(m_reloadConfigErrorOccurredConnection); - emit m_connectionController->configFromApiUpdated(); - }); - - connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromGateway, this, [this]() { - m_reloadConfigErrorOccurredConnection = connect( - m_installController.get(), qOverload(&InstallController::installationErrorOccurred), this, - [this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); }, - static_cast(Qt::AutoConnection || Qt::SingleShotConnection)); - m_installController->updateServiceFromApi(m_serversModel->getDefaultServerIndex(), "", ""); - }); - - connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromTelegram, this, [this]() { - m_reloadConfigErrorOccurredConnection = connect( - m_installController.get(), qOverload(&InstallController::installationErrorOccurred), this, - [this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); }, - static_cast(Qt::AutoConnection || Qt::SingleShotConnection)); - m_serversModel->removeApiConfig(m_serversModel->getDefaultServerIndex()); - m_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex()); - }); - - connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated); - - m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); - m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); - - m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings)); - m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); - - m_settingsController.reset( - new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings)); - m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); - if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { - QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); - } - connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns); - - m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); - m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); - - m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel)); - m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get()); - - m_systemController.reset(new SystemController(m_settings)); - m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); - - m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_settings)); - m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get()); - - m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_settings)); - m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get()); -} diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 3b9d06dd3..b967f160f 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -12,46 +12,10 @@ #include #endif +#include "core/controllers/coreController.h" #include "settings.h" #include "vpnconnection.h" -#include "ui/controllers/connectionController.h" -#include "ui/controllers/exportController.h" -#include "ui/controllers/importController.h" -#include "ui/controllers/installController.h" -#include "ui/controllers/focusController.h" -#include "ui/controllers/pageController.h" -#include "ui/controllers/settingsController.h" -#include "ui/controllers/sitesController.h" -#include "ui/controllers/systemController.h" -#include "ui/controllers/appSplitTunnelingController.h" -#include "ui/controllers/api/apiConfigsController.h" -#include "ui/controllers/api/apiSettingsController.h" -#include "ui/models/containers_model.h" -#include "ui/models/languageModel.h" -#include "ui/models/protocols/cloakConfigModel.h" -#ifndef Q_OS_ANDROID - #include "ui/notificationhandler.h" -#endif -#ifdef Q_OS_WINDOWS - #include "ui/models/protocols/ikev2ConfigModel.h" -#endif -#include "ui/models/protocols/awgConfigModel.h" -#include "ui/models/protocols/openvpnConfigModel.h" -#include "ui/models/protocols/shadowsocksConfigModel.h" -#include "ui/models/protocols/wireguardConfigModel.h" -#include "ui/models/protocols/xrayConfigModel.h" -#include "ui/models/protocols_model.h" -#include "ui/models/servers_model.h" -#include "ui/models/services/sftpConfigModel.h" -#include "ui/models/services/socks5ProxyConfigModel.h" -#include "ui/models/sites_model.h" -#include "ui/models/clientManagementModel.h" -#include "ui/models/appSplitTunnelingModel.h" -#include "ui/models/apiServicesModel.h" -#include "ui/models/apiCountryModel.h" -#include "ui/models/api/apiAccountInfoModel.h" - #define amnApp (static_cast(QCoreApplication::instance())) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) @@ -70,8 +34,6 @@ class AmneziaApplication : public AMNEZIA_BASE_CLASS void init(); void registerTypes(); void loadFonts(); - void loadTranslator(); - void updateTranslator(const QLocale &locale); bool parseCommands(); #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) @@ -79,71 +41,26 @@ class AmneziaApplication : public AMNEZIA_BASE_CLASS #endif QQmlApplicationEngine *qmlEngine() const; - QNetworkAccessManager *manager() { return m_nam; } - -signals: - void translationsUpdated(); + QNetworkAccessManager *manager() + { + return m_nam; + } private: - void initModels(); - void initControllers(); - QQmlApplicationEngine *m_engine {}; std::shared_ptr m_settings; + QScopedPointer m_coreController; + QSharedPointer m_containerProps; QSharedPointer m_protocolProps; - QSharedPointer m_translator; QCommandLineParser m_parser; - QSharedPointer m_containersModel; - QSharedPointer m_defaultServerContainersModel; - QSharedPointer m_serversModel; - QSharedPointer m_languageModel; - QSharedPointer m_protocolsModel; - QSharedPointer m_sitesModel; - QSharedPointer m_appSplitTunnelingModel; - QSharedPointer m_clientManagementModel; - QSharedPointer m_apiServicesModel; - QSharedPointer m_apiCountryModel; - QSharedPointer m_apiAccountInfoModel; - - QScopedPointer m_openVpnConfigModel; - QScopedPointer m_shadowSocksConfigModel; - QScopedPointer m_cloakConfigModel; - QScopedPointer m_xrayConfigModel; - QScopedPointer m_wireGuardConfigModel; - QScopedPointer m_awgConfigModel; -#ifdef Q_OS_WINDOWS - QScopedPointer m_ikev2ConfigModel; -#endif - QScopedPointer m_sftpConfigModel; - QScopedPointer m_socks5ConfigModel; - QSharedPointer m_vpnConnection; QThread m_vpnConnectionThread; -#ifndef Q_OS_ANDROID - QScopedPointer m_notificationHandler; -#endif - - QScopedPointer m_connectionController; - QScopedPointer m_focusController; - QScopedPointer m_pageController; - QScopedPointer m_installController; - QScopedPointer m_importController; - QScopedPointer m_exportController; - QScopedPointer m_settingsController; - QScopedPointer m_sitesController; - QScopedPointer m_systemController; - QScopedPointer m_appSplitTunnelingController; - - QScopedPointer m_apiSettingsController; - QScopedPointer m_apiConfigsController; QNetworkAccessManager *m_nam; - - QMetaObject::Connection m_reloadConfigErrorOccurredConnection; }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/cmake/sources.cmake b/client/cmake/sources.cmake index 1d7c40803..c3af531a6 100644 --- a/client/cmake/sources.cmake +++ b/client/cmake/sources.cmake @@ -9,7 +9,9 @@ set(HEADERS ${HEADERS} ${CLIENT_ROOT_DIR}/core/errorstrings.h ${CLIENT_ROOT_DIR}/core/scripts_registry.h ${CLIENT_ROOT_DIR}/core/server_defs.h - ${CLIENT_ROOT_DIR}/core/controllers/apiController.h + ${CLIENT_ROOT_DIR}/core/api/apiDefs.h + ${CLIENT_ROOT_DIR}/core/qrCodeUtils.h + ${CLIENT_ROOT_DIR}/core/controllers/coreController.h ${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h ${CLIENT_ROOT_DIR}/core/controllers/serverController.h ${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.h @@ -56,7 +58,8 @@ set(SOURCES ${SOURCES} ${CLIENT_ROOT_DIR}/core/errorstrings.cpp ${CLIENT_ROOT_DIR}/core/scripts_registry.cpp ${CLIENT_ROOT_DIR}/core/server_defs.cpp - ${CLIENT_ROOT_DIR}/core/controllers/apiController.cpp + ${CLIENT_ROOT_DIR}/core/qrCodeUtils.cpp + ${CLIENT_ROOT_DIR}/core/controllers/coreController.cpp ${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp ${CLIENT_ROOT_DIR}/core/controllers/serverController.cpp ${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.cpp diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h index 2df4c8337..b01832ce3 100644 --- a/client/core/api/apiDefs.h +++ b/client/core/api/apiDefs.h @@ -6,7 +6,7 @@ namespace apiDefs { enum ConfigType { - AmneziaFreeV2 = 1, + AmneziaFreeV2 = 0, AmneziaFreeV3, AmneziaPremiumV1, AmneziaPremiumV2, diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp index d90ac1dc8..7c58e0e1e 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/api/apiUtils.cpp @@ -44,3 +44,8 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec } }; } + +apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigObject) +{ + return static_cast(serverConfigObject.value(apiDefs::key::configVersion).toInt()); +} diff --git a/client/core/api/apiUtils.h b/client/core/api/apiUtils.h index 2c9496bd2..bb122736b 100644 --- a/client/core/api/apiUtils.h +++ b/client/core/api/apiUtils.h @@ -12,6 +12,7 @@ namespace apiUtils bool isSubscriptionExpired(const QString &subscriptionEndDate); apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject); + apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject); } #endif // APIUTILS_H diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp deleted file mode 100644 index fab914083..000000000 --- a/client/core/controllers/apiController.cpp +++ /dev/null @@ -1,262 +0,0 @@ -#include "apiController.h" - -#include -#include -#include -#include - -#include "amnezia_application.h" -#include "configurators/wireguard_configurator.h" -#include "core/api/apiDefs.h" -#include "gatewayController.h" -#include "version.h" - -namespace -{ - namespace configKey - { - constexpr char cloak[] = "cloak"; - constexpr char awg[] = "awg"; - - constexpr char apiEdnpoint[] = "api_endpoint"; - constexpr char accessToken[] = "api_key"; - constexpr char certificate[] = "certificate"; - constexpr char publicKey[] = "public_key"; - constexpr char protocol[] = "protocol"; - - constexpr char uuid[] = "installation_uuid"; - constexpr char osVersion[] = "os_version"; - constexpr char appVersion[] = "app_version"; - - constexpr char userCountryCode[] = "user_country_code"; - constexpr char serverCountryCode[] = "server_country_code"; - constexpr char serviceType[] = "service_type"; - constexpr char serviceInfo[] = "service_info"; - - constexpr char aesKey[] = "aes_key"; - constexpr char aesIv[] = "aes_iv"; - constexpr char aesSalt[] = "aes_salt"; - - constexpr char apiPayload[] = "api_payload"; - constexpr char keyPayload[] = "key_payload"; - - constexpr char apiConfig[] = "api_config"; - constexpr char authData[] = "auth_data"; - } - - const int requestTimeoutMsecs = 12 * 1000; // 12 secs -} - -ApiController::ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent) - : QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment) -{ -} - -void ApiController::fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, - const QByteArray &apiResponseBody, QJsonObject &serverConfig) -{ - QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString(); - - data.replace("vpn://", ""); - QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - - if (ba.isEmpty()) { - emit errorOccurred(ErrorCode::ApiConfigEmptyError); - return; - } - - QByteArray ba_uncompressed = qUncompress(ba); - if (!ba_uncompressed.isEmpty()) { - ba = ba_uncompressed; - } - - QString configStr = ba; - if (protocol == configKey::cloak) { - configStr.replace("", "\n"); - configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); - } else if (protocol == configKey::awg) { - configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); - auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - auto containers = newServerConfig.value(config_key::containers).toArray(); - if (containers.isEmpty()) { - return; // todo process error - } - auto container = containers.at(0).toObject(); - QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg); - auto containerConfig = container.value(containerName).toObject(); - auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object(); - containerConfig[config_key::junkPacketCount] = protocolConfig.value(config_key::junkPacketCount); - containerConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize); - containerConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize); - containerConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize); - containerConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize); - containerConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader); - containerConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader); - containerConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader); - containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader); - container[containerName] = containerConfig; - containers.replace(0, container); - newServerConfig[config_key::containers] = containers; - configStr = QString(QJsonDocument(newServerConfig).toJson()); - } - - QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1); - serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2); - serverConfig[config_key::containers] = newServerConfig.value(config_key::containers); - serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); - - if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { - serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion); - serverConfig[config_key::description] = newServerConfig.value(config_key::description); - serverConfig[config_key::name] = newServerConfig.value(config_key::name); - } - - auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString(); - serverConfig[config_key::defaultContainer] = defaultContainer; - - QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap(); - map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap()); - auto apiConfig = QJsonObject::fromVariantMap(map); - - if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { - apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject()); - } - - serverConfig[configKey::apiConfig] = apiConfig; - - return; -} - -ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol) -{ - ApiController::ApiPayloadData apiPayload; - if (protocol == configKey::cloak) { - apiPayload.certRequest = OpenVpnConfigurator::createCertRequest(); - } else if (protocol == configKey::awg) { - auto connData = WireguardConfigurator::genClientKeys(); - apiPayload.wireGuardClientPubKey = connData.clientPubKey; - apiPayload.wireGuardClientPrivKey = connData.clientPrivKey; - } - return apiPayload; -} - -QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData) -{ - QJsonObject obj; - if (protocol == configKey::cloak) { - obj[configKey::certificate] = apiPayloadData.certRequest.request; - } else if (protocol == configKey::awg) { - obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey; - } - - obj[configKey::osVersion] = QSysInfo::productType(); - obj[configKey::appVersion] = QString(APP_VERSION); - - return obj; -} - -void ApiController::updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig) -{ -#ifdef Q_OS_IOS - IosController::Instance()->requestInetAccess(); - QThread::msleep(10); -#endif - - if (serverConfig.value(config_key::configVersion).toInt()) { - QNetworkRequest request; - request.setTransferTimeout(requestTimeoutMsecs); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8()); - QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString(); - request.setUrl(endpoint); - - QString protocol = serverConfig.value(configKey::protocol).toString(); - - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); - - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::uuid] = installationUuid; - - QByteArray requestBody = QJsonDocument(apiPayload).toJson(); - - QNetworkReply *reply = amnApp->manager()->post(request, requestBody); - - QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable { - if (reply->error() == QNetworkReply::NoError) { - auto apiResponseBody = reply->readAll(); - fillServerConfig(protocol, apiPayloadData, apiResponseBody, serverConfig); - emit finished(serverConfig, serverIndex); - } else { - if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError - || reply->error() == QNetworkReply::NetworkError::TimeoutError) { - emit errorOccurred(ErrorCode::ApiConfigTimeoutError); - } else { - QString err = reply->errorString(); - qDebug() << QString::fromUtf8(reply->readAll()); - qDebug() << reply->error(); - qDebug() << err; - qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - emit errorOccurred(ErrorCode::ApiConfigDownloadError); - } - } - - reply->deleteLater(); - }); - - QObject::connect(reply, &QNetworkReply::errorOccurred, - [this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; }); - connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList &errors) { - qDebug().noquote() << errors; - emit errorOccurred(ErrorCode::ApiConfigSslError); - }); - } -} - -ErrorCode ApiController::getServicesList(QByteArray &responseBody) -{ - GatewayController gatewayController(m_gatewayEndpoint, m_isDevEnvironment, requestTimeoutMsecs); - - QJsonObject apiPayload; - apiPayload[configKey::osVersion] = QSysInfo::productType(); - - ErrorCode errorCode = gatewayController.post(QString("%1v1/services"), apiPayload, responseBody); - if (errorCode == ErrorCode::NoError) { - if (!responseBody.contains("services")) { - return ErrorCode::ApiServicesMissingError; - } - } - - return errorCode; -} - -ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, - const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, - QJsonObject &serverConfig) -{ - GatewayController gatewayController(m_gatewayEndpoint, m_isDevEnvironment, requestTimeoutMsecs); - - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); - - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = userCountryCode; - if (!serverCountryCode.isEmpty()) { - apiPayload[configKey::serverCountryCode] = serverCountryCode; - } - apiPayload[configKey::serviceType] = serviceType; - apiPayload[configKey::uuid] = installationUuid; - if (!authData.isEmpty()) { - apiPayload[configKey::authData] = authData; - } - - QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody); - - if (errorCode == ErrorCode::NoError) { - fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig); - } - - return errorCode; -} - - diff --git a/client/core/controllers/apiController.h b/client/core/controllers/apiController.h deleted file mode 100644 index 367c89e4e..000000000 --- a/client/core/controllers/apiController.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef APICONTROLLER_H -#define APICONTROLLER_H - -#include - -#include "configurators/openvpn_configurator.h" - -#ifdef Q_OS_IOS - #include "platforms/ios/ios_controller.h" -#endif - -class ApiController : public QObject -{ - Q_OBJECT - -public: - explicit ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent = nullptr); - -public slots: - void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig); - ErrorCode getServicesList(QByteArray &responseBody); - ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, - const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, - QJsonObject &serverConfig); - -signals: - void errorOccurred(ErrorCode errorCode); - void finished(const QJsonObject &config, const int serverIndex); - -private: - struct ApiPayloadData - { - OpenVpnConfigurator::ConnectionData certRequest; - - QString wireGuardClientPrivKey; - QString wireGuardClientPubKey; - }; - - ApiPayloadData generateApiPayloadData(const QString &protocol); - QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData); - void fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, const QByteArray &apiResponseBody, - QJsonObject &serverConfig); - QStringList getProxyUrls(); - - QString m_gatewayEndpoint; - QStringList m_proxyUrls; - bool m_isDevEnvironment = false; -}; - -#endif // APICONTROLLER_H diff --git a/client/core/controllers/coreController.cpp b/client/core/controllers/coreController.cpp new file mode 100644 index 000000000..551eb61fd --- /dev/null +++ b/client/core/controllers/coreController.cpp @@ -0,0 +1,332 @@ +#include "coreController.h" + +#if defined(Q_OS_ANDROID) + #include "core/installedAppsImageProvider.h" + #include "platforms/android/android_controller.h" +#endif + +#if defined(Q_OS_IOS) + #include "platforms/ios/ios_controller.h" + #include +#endif + +CoreController::CoreController(const QSharedPointer &vpnConnection, const std::shared_ptr &settings, + QQmlApplicationEngine *engine, QObject *parent) + : QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine) +{ + initModels(); + initControllers(); + initSignalHandlers(); + + initNotificationHandler(); + + auto locale = m_settings->getAppLanguage(); + m_translator.reset(new QTranslator()); + updateTranslator(locale); +} + +void CoreController::initModels() +{ + m_containersModel.reset(new ContainersModel(this)); + m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + + m_defaultServerContainersModel.reset(new ContainersModel(this)); + m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get()); + + m_serversModel.reset(new ServersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + + m_languageModel.reset(new LanguageModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); + + m_sitesModel.reset(new SitesModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); + + m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get()); + + m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); + + m_openVpnConfigModel.reset(new OpenVpnConfigModel(this)); + m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get()); + + m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this)); + m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get()); + + m_cloakConfigModel.reset(new CloakConfigModel(this)); + m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get()); + + m_wireGuardConfigModel.reset(new WireGuardConfigModel(this)); + m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get()); + + m_awgConfigModel.reset(new AwgConfigModel(this)); + m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get()); + + m_xrayConfigModel.reset(new XrayConfigModel(this)); + m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get()); + +#ifdef Q_OS_WINDOWS + m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); + m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); +#endif + + m_sftpConfigModel.reset(new SftpConfigModel(this)); + m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get()); + + m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this)); + m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get()); + + m_clientManagementModel.reset(new ClientManagementModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get()); + + m_apiServicesModel.reset(new ApiServicesModel(this)); + m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get()); + + m_apiCountryModel.reset(new ApiCountryModel(this)); + m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get()); + + m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this)); + m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get()); +} + +void CoreController::initControllers() +{ + m_connectionController.reset( + new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings)); + m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + + m_pageController.reset(new PageController(m_serversModel, m_settings)); + m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + + m_focusController.reset(new FocusController(m_engine, this)); + m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get()); + + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings)); + m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + + connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(), + &ConnectionController::onCurrentContainerUpdated); // TODO remove this + + m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); + + m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings)); + m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); + + m_settingsController.reset( + new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings)); + m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + + m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); + m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); + + m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel)); + m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get()); + + m_systemController.reset(new SystemController(m_settings)); + m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); + + m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_settings)); + m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get()); + + m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings)); + m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get()); +} + +void CoreController::initAndroidController() +{ +#ifdef Q_OS_ANDROID + if (!AndroidController::initLogging()) { + qFatal("Android logging initialization failed"); + } + AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs()); + connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs); + + AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled()); + connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled); + + connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer); + + connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); }); + + connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) { + m_connectionController->onConnectionStateChanged(state); + if (m_vpnConnection) + m_vpnConnection->restoreConnection(); + }); + if (!AndroidController::instance()->initialize()) { + qFatal("Android controller initialization failed"); + } + + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) { + emit m_pageController->goToPageHome(); + m_importController->extractConfigFromData(data); + data.clear(); + emit m_pageController->goToPageViewConfig(); + }); + + m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider); +#endif +} + +void CoreController::initAppleController() +{ +#ifdef Q_OS_IOS + IosController::Instance()->initialize(); + connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) { + emit m_pageController->goToPageHome(); + m_importController->extractConfigFromData(data); + emit m_pageController->goToPageViewConfig(); + }); + + connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) { + emit m_pageController->goToPageHome(); + m_pageController->goToPageSettingsBackup(); + emit m_settingsController->importBackupFromOutside(filePath); + }); + + QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); }); + + connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); }); +#endif +} + +void CoreController::initSignalHandlers() +{ + initApiCountryModelUpdateHandler(); + initContainerModelUpdateHandler(); + initAdminConfigRevokedHandler(); + initConnectionErrorOccurredHandler(); + initPassphraseRequestHandler(); + initTranslationsUpdatedHandler(); + initAutoConnectHandler(); + initAmneziaDnsToggledHandler(); + initPrepareConfigHandler(); +} + +void CoreController::initNotificationHandler() +{ +#ifndef Q_OS_ANDROID + m_notificationHandler.reset(NotificationHandler::create(nullptr)); + + connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), + &NotificationHandler::setConnectionState); + + connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow); + connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), + static_cast(&ConnectionController::openConnection)); + connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), + &ConnectionController::closeConnection); + connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated); +#endif +} + +void CoreController::updateTranslator(const QLocale &locale) +{ + if (!m_translator->isEmpty()) { + QCoreApplication::removeTranslator(m_translator.get()); + } + + QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm"; + if (m_translator->load(strFileName)) { + if (QCoreApplication::installTranslator(m_translator.get())) { + m_settings->setAppLanguage(locale); + } + } else { + m_settings->setAppLanguage(QLocale::English); + } + + m_engine->retranslate(); + + emit translationsUpdated(); +} + +void CoreController::setQmlRoot() +{ + m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); +} + +void CoreController::initApiCountryModelUpdateHandler() +{ + // TODO + connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() { + m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(), + m_serversModel->getProcessedServerData("apiServerCountryCode").toString()); + }); + connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this, + [this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); }); +} + +void CoreController::initContainerModelUpdateHandler() +{ + connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel); + connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(), + &ContainersModel::updateModel); + m_serversModel->resetModel(); +} + +void CoreController::initAdminConfigRevokedHandler() +{ + connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(), + &ServersModel::clearCachedProfile); +} + +void CoreController::initConnectionErrorOccurredHandler() +{ + connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](ErrorCode errorCode) { + emit m_pageController->showErrorMessage(errorCode); + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); + }); +} + +void CoreController::initPassphraseRequestHandler() +{ + connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), + &PageController::showPassphraseRequestDrawer); + connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), + &InstallController::setEncryptedPassphrase); +} + +void CoreController::initTranslationsUpdatedHandler() +{ + connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &CoreController::updateTranslator); + connect(this, &CoreController::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); + connect(this, &CoreController::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated); +} + +void CoreController::initAutoConnectHandler() +{ + if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { + QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); + } +} + +void CoreController::initAmneziaDnsToggledHandler() +{ + connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns); +} + +void CoreController::initPrepareConfigHandler() +{ + connect(m_connectionController.get(), &ConnectionController::prepareConfig, this, [this]() { + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing); + + if (!m_apiConfigsController->isConfigValid()) { + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); + return; + } + + if (!m_installController->isConfigValid()) { + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); + return; + } + + m_connectionController->openConnection(); + }); +} + +QSharedPointer CoreController::pageController() const +{ + return m_pageController; +} diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h new file mode 100644 index 000000000..c67c9cccc --- /dev/null +++ b/client/core/controllers/coreController.h @@ -0,0 +1,133 @@ +#ifndef CORECONTROLLER_H +#define CORECONTROLLER_H + +#include +#include +#include + +#include "ui/controllers/api/apiConfigsController.h" +#include "ui/controllers/api/apiSettingsController.h" +#include "ui/controllers/appSplitTunnelingController.h" +#include "ui/controllers/connectionController.h" +#include "ui/controllers/exportController.h" +#include "ui/controllers/focusController.h" +#include "ui/controllers/importController.h" +#include "ui/controllers/installController.h" +#include "ui/controllers/pageController.h" +#include "ui/controllers/settingsController.h" +#include "ui/controllers/sitesController.h" +#include "ui/controllers/systemController.h" + +#include "ui/models/containers_model.h" +#include "ui/models/languageModel.h" +#include "ui/models/protocols/cloakConfigModel.h" +#ifdef Q_OS_WINDOWS + #include "ui/models/protocols/ikev2ConfigModel.h" +#endif +#include "ui/models/api/apiAccountInfoModel.h" +#include "ui/models/api/apiServicesModel.h" +#include "ui/models/apiCountryModel.h" +#include "ui/models/appSplitTunnelingModel.h" +#include "ui/models/clientManagementModel.h" +#include "ui/models/protocols/awgConfigModel.h" +#include "ui/models/protocols/openvpnConfigModel.h" +#include "ui/models/protocols/shadowsocksConfigModel.h" +#include "ui/models/protocols/wireguardConfigModel.h" +#include "ui/models/protocols/xrayConfigModel.h" +#include "ui/models/protocols_model.h" +#include "ui/models/servers_model.h" +#include "ui/models/services/sftpConfigModel.h" +#include "ui/models/services/socks5ProxyConfigModel.h" +#include "ui/models/sites_model.h" + +#ifndef Q_OS_ANDROID + #include "ui/notificationhandler.h" +#endif + +class CoreController : public QObject +{ + Q_OBJECT + +public: + explicit CoreController(const QSharedPointer &vpnConnection, const std::shared_ptr &settings, + QQmlApplicationEngine *engine, QObject *parent = nullptr); + + QSharedPointer pageController() const; + void setQmlRoot(); + +signals: + void translationsUpdated(); + +private: + void initModels(); + void initControllers(); + void initAndroidController(); + void initAppleController(); + void initSignalHandlers(); + + void initNotificationHandler(); + + void updateTranslator(const QLocale &locale); + + void initApiCountryModelUpdateHandler(); + void initContainerModelUpdateHandler(); + void initAdminConfigRevokedHandler(); + void initConnectionErrorOccurredHandler(); + void initPassphraseRequestHandler(); + void initTranslationsUpdatedHandler(); + void initAutoConnectHandler(); + void initAmneziaDnsToggledHandler(); + void initPrepareConfigHandler(); + + QQmlApplicationEngine *m_engine {}; // TODO use parent child system here? + std::shared_ptr m_settings; + QSharedPointer m_vpnConnection; + QSharedPointer m_translator; + +#ifndef Q_OS_ANDROID + QScopedPointer m_notificationHandler; +#endif + + QMetaObject::Connection m_reloadConfigErrorOccurredConnection; + + QScopedPointer m_connectionController; + QScopedPointer m_focusController; + QSharedPointer m_pageController; // TODO + QScopedPointer m_installController; + QScopedPointer m_importController; + QScopedPointer m_exportController; + QScopedPointer m_settingsController; + QScopedPointer m_sitesController; + QScopedPointer m_systemController; + QScopedPointer m_appSplitTunnelingController; + + QScopedPointer m_apiSettingsController; + QScopedPointer m_apiConfigsController; + + QSharedPointer m_containersModel; + QSharedPointer m_defaultServerContainersModel; + QSharedPointer m_serversModel; + QSharedPointer m_languageModel; + QSharedPointer m_protocolsModel; + QSharedPointer m_sitesModel; + QSharedPointer m_appSplitTunnelingModel; + QSharedPointer m_clientManagementModel; + + QSharedPointer m_apiServicesModel; + QSharedPointer m_apiCountryModel; + QSharedPointer m_apiAccountInfoModel; + + QScopedPointer m_openVpnConfigModel; + QScopedPointer m_shadowSocksConfigModel; + QScopedPointer m_cloakConfigModel; + QScopedPointer m_xrayConfigModel; + QScopedPointer m_wireGuardConfigModel; + QScopedPointer m_awgConfigModel; +#ifdef Q_OS_WINDOWS + QScopedPointer m_ikev2ConfigModel; +#endif + QScopedPointer m_sftpConfigModel; + QScopedPointer m_socks5ConfigModel; +}; + +#endif // CORECONTROLLER_H diff --git a/client/core/controllers/vpnConfigurationController.cpp b/client/core/controllers/vpnConfigurationController.cpp index 52f42c42e..612879728 100644 --- a/client/core/controllers/vpnConfigurationController.cpp +++ b/client/core/controllers/vpnConfigurationController.cpp @@ -77,8 +77,7 @@ ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isA } QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair &dns, const QJsonObject &serverConfig, - const QJsonObject &containerConfig, const DockerContainer container, - ErrorCode &errorCode) + const QJsonObject &containerConfig, const DockerContainer container) { QJsonObject vpnConfiguration {}; @@ -103,7 +102,8 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair &settings, QSharedPointer serverController, QObject *parent = nullptr); + explicit VpnConfigurationsController(const std::shared_ptr &settings, QSharedPointer serverController, + QObject *parent = nullptr); public slots: ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container, @@ -21,7 +22,7 @@ public slots: const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol, QString &protocolConfigString); QJsonObject createVpnConfiguration(const QPair &dns, const QJsonObject &serverConfig, - const QJsonObject &containerConfig, const DockerContainer container, ErrorCode &errorCode); + const QJsonObject &containerConfig, const DockerContainer container); static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut); signals: diff --git a/client/core/defs.h b/client/core/defs.h index 4ea0e6130..c7fde0ee9 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -44,6 +44,7 @@ namespace amnezia InternalError = 101, NotImplementedError = 102, AmneziaServiceNotRunning = 103, + NotSupportedOnThisPlatform = 104, // Server errors ServerCheckFailed = 200, @@ -94,6 +95,7 @@ namespace amnezia // import and install errors ImportInvalidConfigError = 900, ImportOpenConfigError = 901, + NoInstalledContainersError = 902, // Android errors AndroidError = 1000, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 70f433c62..976915c21 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -12,6 +12,7 @@ QString errorString(ErrorCode code) { case(ErrorCode::UnknownError): errorMessage = QObject::tr("Unknown error"); break; case(ErrorCode::NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break; case(ErrorCode::AmneziaServiceNotRunning): errorMessage = QObject::tr("Background service is not running"); break; + case(ErrorCode::NotSupportedOnThisPlatform): errorMessage = QObject::tr("The selected protocol is not supported on the current platform"); break; // Server errors case(ErrorCode::ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break; @@ -51,6 +52,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break; case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break; + case(ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break; // Android errors case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break; diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 0858ecff2..ee1332536 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -1,8 +1,13 @@ #include "apiConfigsController.h" +#include + +#include "amnezia_application.h" #include "configurators/wireguard_configurator.h" #include "core/api/apiDefs.h" +#include "core/api/apiUtils.h" #include "core/controllers/gatewayController.h" +#include "core/networkUtilities.h" #include "core/qrCodeUtils.h" #include "ui/controllers/systemController.h" #include "version.h" @@ -18,6 +23,7 @@ namespace constexpr char accessToken[] = "api_key"; constexpr char certificate[] = "certificate"; constexpr char publicKey[] = "public_key"; + constexpr char protocol[] = "protocol"; constexpr char uuid[] = "installation_uuid"; constexpr char osVersion[] = "os_version"; @@ -43,9 +49,10 @@ namespace } } -ApiConfigsController::ApiConfigsController(const QSharedPointer &serversModel, const std::shared_ptr &settings, - QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_settings(settings) +ApiConfigsController::ApiConfigsController(const QSharedPointer &serversModel, + const QSharedPointer &apiServicesModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_apiServicesModel(apiServicesModel), m_settings(settings) { } @@ -98,6 +105,211 @@ void ApiConfigsController::prepareVpnKeyExport() emit vpnKeyExportReady(); } +bool ApiConfigsController::fillAvailableServices() +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + + QJsonObject apiPayload; + apiPayload[configKey::osVersion] = QSysInfo::productType(); + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/services"), apiPayload, responseBody); + if (errorCode == ErrorCode::NoError) { + if (!responseBody.contains("services")) { + errorCode = ErrorCode::ApiServicesMissingError; + } + } + + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } + + QJsonObject data = QJsonDocument::fromJson(responseBody).object(); + m_apiServicesModel->updateModel(data); + return true; +} + +bool ApiConfigsController::importServiceFromGateway() +{ + if (m_serversModel->isServerFromApiAlreadyExists(m_apiServicesModel->getCountryCode(), m_apiServicesModel->getSelectedServiceType(), + m_apiServicesModel->getSelectedServiceProtocol())) { + emit errorOccurred(ErrorCode::ApiConfigAlreadyAdded); + return false; + } + + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + + auto installationUuid = m_settings->getInstallationUuid(true); + auto userCountryCode = m_apiServicesModel->getCountryCode(); + auto serviceType = m_apiServicesModel->getSelectedServiceType(); + auto serviceProtocol = m_apiServicesModel->getSelectedServiceProtocol(); + + ApiPayloadData apiPayloadData = generateApiPayloadData(serviceProtocol); + + QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData); + apiPayload[configKey::userCountryCode] = userCountryCode; + apiPayload[configKey::serviceType] = serviceType; + apiPayload[configKey::uuid] = installationUuid; + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody); + + QJsonObject serverConfig; + if (errorCode == ErrorCode::NoError) { + fillServerConfig(serviceProtocol, apiPayloadData, responseBody, serverConfig); + + QJsonObject apiConfig = serverConfig.value(configKey::apiConfig).toObject(); + apiConfig.insert(configKey::userCountryCode, m_apiServicesModel->getCountryCode()); + apiConfig.insert(configKey::serviceType, m_apiServicesModel->getSelectedServiceType()); + apiConfig.insert(configKey::serviceProtocol, m_apiServicesModel->getSelectedServiceProtocol()); + + serverConfig.insert(configKey::apiConfig, apiConfig); + + m_serversModel->addServer(serverConfig); + emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName())); + return true; + } else { + emit errorOccurred(errorCode); + return false; + } +} + +bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, + bool reloadServiceConfig) +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + + auto serverConfig = m_serversModel->getServerConfig(serverIndex); + auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); + auto authData = serverConfig.value(configKey::authData).toObject(); + + auto installationUuid = m_settings->getInstallationUuid(true); + auto userCountryCode = apiConfig.value(configKey::userCountryCode).toString(); + auto serviceType = apiConfig.value(configKey::serviceType).toString(); + auto serviceProtocol = apiConfig.value(configKey::serviceProtocol).toString(); + + ApiPayloadData apiPayloadData = generateApiPayloadData(serviceProtocol); + + QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData); + apiPayload[configKey::userCountryCode] = userCountryCode; + apiPayload[configKey::serviceType] = serviceType; + apiPayload[configKey::uuid] = installationUuid; + + if (!newCountryCode.isEmpty()) { + apiPayload[configKey::serverCountryCode] = newCountryCode; + } + if (!authData.isEmpty()) { + apiPayload[configKey::authData] = authData; + } + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody); + + QJsonObject newServerConfig; + if (errorCode == ErrorCode::NoError) { + fillServerConfig(serviceProtocol, apiPayloadData, responseBody, newServerConfig); + + QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject(); + newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode)); + newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType)); + newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol)); + + newServerConfig.insert(configKey::apiConfig, newApiConfig); + newServerConfig.insert(configKey::authData, authData); + + m_serversModel->editServer(newServerConfig, serverIndex); + if (reloadServiceConfig) { + emit reloadServerFromApiFinished(tr("API config reloaded")); + } else if (newCountryName.isEmpty()) { + emit updateServerFromApiFinished(); + } else { + emit changeApiCountryFinished(tr("Successfully changed the country of connection to %1").arg(newCountryName)); + } + return true; + } else { + emit errorOccurred(errorCode); + return false; + } +} + +bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) +{ + auto serverConfig = m_serversModel->getServerConfig(serverIndex); + auto installationUuid = m_settings->getInstallationUuid(true); + +#ifdef Q_OS_IOS + IosController::Instance()->requestInetAccess(); + QThread::msleep(10); +#endif + + if (serverConfig.value(config_key::configVersion).toInt()) { + QNetworkRequest request; + request.setTransferTimeout(apiDefs::requestTimeoutMsecs); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8()); + QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString(); + request.setUrl(endpoint); + + QString protocol = serverConfig.value(configKey::protocol).toString(); + + ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + + QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); + apiPayload[configKey::uuid] = installationUuid; + + QByteArray requestBody = QJsonDocument(apiPayload).toJson(); + + QNetworkReply *reply = amnApp->manager()->post(request, requestBody); + + QEventLoop wait; + connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); + + QList sslErrors; + connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); + wait.exec(); + + auto errorCode = NetworkUtilities::checkNetworkReplyErrors(sslErrors, reply); + if (errorCode != ErrorCode::NoError) { + reply->deleteLater(); + emit errorOccurred(errorCode); + return false; + } + + auto apiResponseBody = reply->readAll(); + reply->deleteLater(); + fillServerConfig(protocol, apiPayloadData, apiResponseBody, serverConfig); + m_serversModel->editServer(serverConfig, serverIndex); + emit updateServerFromApiFinished(); + } + return true; +} + +bool ApiConfigsController::isConfigValid() +{ + int serverIndex = m_serversModel->getDefaultServerIndex(); + QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex); + auto configSource = apiUtils::getConfigSource(serverConfigObject); + + if (configSource == apiDefs::ConfigSource::Telegram + && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { + m_serversModel->removeApiConfig(serverIndex); + return updateServiceFromTelegram(serverIndex); + } else if (configSource == apiDefs::ConfigSource::AmneziaGateway + && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { + return updateServiceFromGateway(serverIndex, "", ""); + } else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) { + qDebug() << "attempt to update api config by expires_at event"; + if (configSource == apiDefs::ConfigSource::Telegram) { + return updateServiceFromGateway(serverIndex, "", ""); + } else { + m_serversModel->removeApiConfig(serverIndex); + return updateServiceFromTelegram(serverIndex); + } + } + return true; +} + ApiConfigsController::ApiPayloadData ApiConfigsController::generateApiPayloadData(const QString &protocol) { ApiConfigsController::ApiPayloadData apiPayload; @@ -126,6 +338,82 @@ QJsonObject ApiConfigsController::fillApiPayload(const QString &protocol, const return obj; } +void ApiConfigsController::fillServerConfig(const QString &protocol, const ApiPayloadData &apiPayloadData, + const QByteArray &apiResponseBody, QJsonObject &serverConfig) +{ + QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString(); + + data.replace("vpn://", ""); + QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + if (ba.isEmpty()) { + emit errorOccurred(ErrorCode::ApiConfigEmptyError); + return; + } + + QByteArray ba_uncompressed = qUncompress(ba); + if (!ba_uncompressed.isEmpty()) { + ba = ba_uncompressed; + } + + QString configStr = ba; + if (protocol == configKey::cloak) { + configStr.replace("", "\n"); + configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); + } else if (protocol == configKey::awg) { + configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); + auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + auto containers = newServerConfig.value(config_key::containers).toArray(); + if (containers.isEmpty()) { + return; // todo process error + } + auto container = containers.at(0).toObject(); + QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg); + auto containerConfig = container.value(containerName).toObject(); + auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object(); + containerConfig[config_key::junkPacketCount] = protocolConfig.value(config_key::junkPacketCount); + containerConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize); + containerConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize); + containerConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize); + containerConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize); + containerConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader); + containerConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader); + containerConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader); + containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader); + container[containerName] = containerConfig; + containers.replace(0, container); + newServerConfig[config_key::containers] = containers; + configStr = QString(QJsonDocument(newServerConfig).toJson()); + } + + QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1); + serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2); + serverConfig[config_key::containers] = newServerConfig.value(config_key::containers); + serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); + + if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { + serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion); + serverConfig[config_key::description] = newServerConfig.value(config_key::description); + serverConfig[config_key::name] = newServerConfig.value(config_key::name); + } + + auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString(); + serverConfig[config_key::defaultContainer] = defaultContainer; + + QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap(); + map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap()); + auto apiConfig = QJsonObject::fromVariantMap(map); + + if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { + apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject()); + } + + serverConfig[configKey::apiConfig] = apiConfig; + + return; +} + QList ApiConfigsController::getQrCodes() { return m_qrCodes; diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h index f9bf977f4..a8202afc6 100644 --- a/client/ui/controllers/api/apiConfigsController.h +++ b/client/ui/controllers/api/apiConfigsController.h @@ -4,14 +4,15 @@ #include #include "configurators/openvpn_configurator.h" +#include "ui/models/api/apiServicesModel.h" #include "ui/models/servers_model.h" class ApiConfigsController : public QObject { Q_OBJECT public: - ApiConfigsController(const QSharedPointer &serversModel, const std::shared_ptr &settings, - QObject *parent = nullptr); + ApiConfigsController(const QSharedPointer &serversModel, const QSharedPointer &apiServicesModel, + const std::shared_ptr &settings, QObject *parent = nullptr); Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY vpnKeyExportReady) Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY vpnKeyExportReady) @@ -21,8 +22,22 @@ public slots: // bool exportVpnKey(const QString &fileName); void prepareVpnKeyExport(); + bool fillAvailableServices(); + bool importServiceFromGateway(); + bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, + bool reloadServiceConfig = false); + bool updateServiceFromTelegram(const int serverIndex); + + bool isConfigValid(); + signals: void errorOccurred(ErrorCode errorCode); + + void installServerFromApiFinished(const QString &message); + void changeApiCountryFinished(const QString &message); + void reloadServerFromApiFinished(const QString &message); + void updateServerFromApiFinished(); + void vpnKeyExportReady(); private: @@ -36,6 +51,8 @@ public slots: ApiPayloadData generateApiPayloadData(const QString &protocol); QJsonObject fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData); + void fillServerConfig(const QString &protocol, const ApiPayloadData &apiPayloadData, const QByteArray &apiResponseBody, + QJsonObject &serverConfig); QList getQrCodes(); int getQrCodesCount(); @@ -43,6 +60,7 @@ public slots: QList m_qrCodes; QSharedPointer m_serversModel; + QSharedPointer m_apiServicesModel; std::shared_ptr m_settings; }; diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 9cd386c5d..9fc60493b 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -5,10 +5,8 @@ #else #include #endif -#include #include "core/controllers/vpnConfigurationController.h" -#include "core/api/apiDefs.h" #include "version.h" ConnectionController::ConnectionController(const QSharedPointer &serversModel, @@ -27,7 +25,7 @@ ConnectionController::ConnectionController(const QSharedPointer &s connect(this, &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); - connect(this, &ConnectionController::configFromApiUpdated, this, &ConnectionController::continueConnection); + connect(this, &ConnectionController::connectButtonClicked, this, &ConnectionController::toggleConnection, Qt::QueuedConnection); m_state = Vpn::ConnectionState::Disconnected; } @@ -35,8 +33,7 @@ ConnectionController::ConnectionController(const QSharedPointer &s void ConnectionController::openConnection() { #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) - { + if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) { emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning); return; } @@ -44,26 +41,24 @@ void ConnectionController::openConnection() int serverIndex = m_serversModel->getDefaultServerIndex(); QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); - auto configVersion = serverConfig.value(config_key::configVersion).toInt(); - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing); + DockerContainer container = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); - if (configVersion == apiDefs::ConfigSource::Telegram - && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { - emit updateApiConfigFromTelegram(); - } else if (configVersion == apiDefs::ConfigSource::AmneziaGateway - && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { - emit updateApiConfigFromGateway(); - } else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) { - qDebug() << "attempt to update api config by expires_at event"; - if (configVersion == apiDefs::ConfigSource::Telegram) { - emit updateApiConfigFromTelegram(); - } else { - emit updateApiConfigFromGateway(); - } - } else { - continueConnection(); + if (!m_containersModel->isSupportedByCurrentPlatform(container)) { + emit connectionErrorOccurred(ErrorCode::NotSupportedOnThisPlatform); + return; } + + QSharedPointer serverController(new ServerController(m_settings)); + VpnConfigurationsController vpnConfigurationController(m_settings, serverController); + + QJsonObject containerConfig = m_containersModel->getContainerConfig(container); + ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); + + auto dns = m_serversModel->getDnsPair(serverIndex); + + auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container); + emit connectToVpn(serverIndex, credentials, container, vpnConfiguration); } void ConnectionController::closeConnection() @@ -167,7 +162,7 @@ void ConnectionController::toggleConnection() } else if (isConnected()) { closeConnection(); } else { - openConnection(); + emit prepareConfig(); } } @@ -180,98 +175,3 @@ bool ConnectionController::isConnected() const { return m_isConnected; } - -bool ConnectionController::isProtocolConfigExists(const QJsonObject &containerConfig, const DockerContainer container) -{ - for (Proto protocol : ContainerProps::protocolsForContainer(container)) { - QString protocolConfig = - containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString(); - - if (protocolConfig.isEmpty()) { - return false; - } - } - return true; -} - -void ConnectionController::continueConnection() -{ - int serverIndex = m_serversModel->getDefaultServerIndex(); - QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); - auto configVersion = serverConfig.value(config_key::configVersion).toInt(); - - if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { - emit noInstalledContainers(); - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); - return; - } - - DockerContainer container = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); - - if (!m_containersModel->isSupportedByCurrentPlatform(container)) { - emit connectionErrorOccurred(tr("The selected protocol is not supported on the current platform")); - return; - } - - if (container == DockerContainer::None) { - emit connectionErrorOccurred(tr("VPN Protocols is not installed.\n Please install VPN container at first")); - return; - } - - QSharedPointer serverController(new ServerController(m_settings)); - VpnConfigurationsController vpnConfigurationController(m_settings, serverController); - - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - ErrorCode errorCode = updateProtocolConfig(container, credentials, containerConfig, serverController); - if (errorCode != ErrorCode::NoError) { - emit connectionErrorOccurred(errorCode); - return; - } - - auto dns = m_serversModel->getDnsPair(serverIndex); - - auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container, errorCode); - if (errorCode != ErrorCode::NoError) { - emit connectionErrorOccurred(tr("unable to create configuration")); - return; - } - - emit connectToVpn(serverIndex, credentials, container, vpnConfiguration); -} - -ErrorCode ConnectionController::updateProtocolConfig(const DockerContainer container, const ServerCredentials &credentials, - QJsonObject &containerConfig, QSharedPointer serverController) -{ - QFutureWatcher watcher; - - if (serverController.isNull()) { - serverController.reset(new ServerController(m_settings)); - } - - QFuture future = QtConcurrent::run([this, container, &credentials, &containerConfig, &serverController]() { - ErrorCode errorCode = ErrorCode::NoError; - if (!isProtocolConfigExists(containerConfig, container)) { - VpnConfigurationsController vpnConfigurationController(m_settings, serverController); - errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container, containerConfig); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - m_serversModel->updateContainerConfig(container, containerConfig); - - errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig, - QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - } - return errorCode; - }); - - QEventLoop wait; - connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); - watcher.setFuture(future); - wait.exec(); - - return watcher.result(); -} diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 25d4d74a1..cabeb601a 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -40,30 +40,20 @@ public slots: void onTranslationsUpdated(); - ErrorCode updateProtocolConfig(const DockerContainer container, const ServerCredentials &credentials, QJsonObject &containerConfig, - QSharedPointer serverController = nullptr); - signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration); void disconnectFromVpn(); void connectionStateChanged(); - void connectionErrorOccurred(const QString &errorMessage); void connectionErrorOccurred(ErrorCode errorCode); void reconnectWithUpdatedContainer(const QString &message); - void noInstalledContainers(); - void connectButtonClicked(); void preparingConfig(); - - void updateApiConfigFromGateway(); - void updateApiConfigFromTelegram(); - void configFromApiUpdated(); + void prepareConfig(); private: Vpn::ConnectionState getCurrentConnectionState(); - bool isProtocolConfigExists(const QJsonObject &containerConfig, const DockerContainer container); void continueConnection(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 26e433e02..7a6d8d40e 100755 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -6,8 +6,8 @@ #include #include #include +#include -#include "core/controllers/apiController.h" #include "core/controllers/serverController.h" #include "core/controllers/vpnConfigurationController.h" #include "core/networkUtilities.h" @@ -15,6 +15,7 @@ #include "ui/models/protocols/awgConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h" #include "utilities.h" +#include "core/api/apiUtils.h" namespace { @@ -39,14 +40,12 @@ namespace InstallController::InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const QSharedPointer &protocolsModel, const QSharedPointer &clientManagementModel, - const QSharedPointer &apiServicesModel, const std::shared_ptr &settings, - QObject *parent) + const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_protocolModel(protocolsModel), m_clientManagementModel(clientManagementModel), - m_apiServicesModel(apiServicesModel), m_settings(settings) { } @@ -773,110 +772,79 @@ void InstallController::addEmptyServer() emit installServerFinished(tr("Server added successfully")); } -bool InstallController::fillAvailableServices() +bool InstallController::isConfigValid() { - ApiController apiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv()); + int serverIndex = m_serversModel->getDefaultServerIndex(); + QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex); - QByteArray responseBody; - ErrorCode errorCode = apiController.getServicesList(responseBody); - if (errorCode != ErrorCode::NoError) { - emit installationErrorOccurred(errorCode); - return false; + if (apiUtils::isServerFromApi(serverConfigObject)) { + return true; } - QJsonObject data = QJsonDocument::fromJson(responseBody).object(); - m_apiServicesModel->updateModel(data); - return true; -} - -bool InstallController::installServiceFromApi() -{ - if (m_serversModel->isServerFromApiAlreadyExists(m_apiServicesModel->getCountryCode(), m_apiServicesModel->getSelectedServiceType(), - m_apiServicesModel->getSelectedServiceProtocol())) { - emit installationErrorOccurred(ErrorCode::ApiConfigAlreadyAdded); + if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { + emit noInstalledContainers(); return false; } - ApiController apiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv()); - QJsonObject serverConfig; + DockerContainer container = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); - ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(), - m_apiServicesModel->getSelectedServiceType(), - m_apiServicesModel->getSelectedServiceProtocol(), "", QJsonObject(), serverConfig); - if (errorCode != ErrorCode::NoError) { - emit installationErrorOccurred(errorCode); + if (container == DockerContainer::None) { + emit installationErrorOccurred(ErrorCode::NoInstalledContainersError); return false; } - auto serviceInfo = m_apiServicesModel->getSelectedServiceInfo(); - QJsonObject apiConfig = serverConfig.value(configKey::apiConfig).toObject(); - apiConfig.insert(configKey::serviceInfo, serviceInfo); - apiConfig.insert(configKey::userCountryCode, m_apiServicesModel->getCountryCode()); - apiConfig.insert(configKey::serviceType, m_apiServicesModel->getSelectedServiceType()); - apiConfig.insert(configKey::serviceProtocol, m_apiServicesModel->getSelectedServiceProtocol()); - - serverConfig.insert(configKey::apiConfig, apiConfig); - - m_serversModel->addServer(serverConfig); - emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName())); - return true; -} + QSharedPointer serverController(new ServerController(m_settings)); + VpnConfigurationsController vpnConfigurationController(m_settings, serverController); -bool InstallController::updateServiceFromApi(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, - bool reloadServiceConfig) -{ - ApiController apiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv()); + QJsonObject containerConfig = m_containersModel->getContainerConfig(container); + ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - auto serverConfig = m_serversModel->getServerConfig(serverIndex); - auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); - auto authData = serverConfig.value(configKey::authData).toObject(); + QFutureWatcher watcher; - QJsonObject newServerConfig; + QFuture future = QtConcurrent::run([this, container, &credentials, &containerConfig, &serverController]() { + ErrorCode errorCode = ErrorCode::NoError; - ErrorCode errorCode = apiController.getConfigForService( - m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(), - apiConfig.value(configKey::serviceType).toString(), apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode, - authData, newServerConfig); - if (errorCode != ErrorCode::NoError) { - emit installationErrorOccurred(errorCode); - return false; - } + auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) { + for (Proto protocol : ContainerProps::protocolsForContainer(container)) { + QString protocolConfig = + containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString(); - QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject(); - newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode)); - newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType)); - newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol)); + if (protocolConfig.isEmpty()) { + return false; + } + } + return true; + }; - newServerConfig.insert(configKey::apiConfig, newApiConfig); - newServerConfig.insert(configKey::authData, authData); - m_serversModel->editServer(newServerConfig, serverIndex); + if (!isProtocolConfigExists(containerConfig, container)) { + VpnConfigurationsController vpnConfigurationController(m_settings, serverController); + errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container, containerConfig); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + m_serversModel->updateContainerConfig(container, containerConfig); - if (reloadServiceConfig) { - emit reloadServerFromApiFinished(tr("API config reloaded")); - } else if (newCountryName.isEmpty()) { - emit updateServerFromApiFinished(); - } else { - emit changeApiCountryFinished(tr("Successfully changed the country of connection to %1").arg(newCountryName)); - } - return true; -} + errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig, + QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + } + return errorCode; + }); -void InstallController::updateServiceFromTelegram(const int serverIndex) -{ - ApiController *apiController = new ApiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv()); + QEventLoop wait; + connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); + watcher.setFuture(future); + wait.exec(); - auto serverConfig = m_serversModel->getServerConfig(serverIndex); + ErrorCode errorCode = watcher.result(); - apiController->updateServerConfigFromApi(m_settings->getInstallationUuid(true), serverIndex, serverConfig); - connect(apiController, &ApiController::finished, this, [this, apiController](const QJsonObject &config, const int serverIndex) { - m_serversModel->editServer(config, serverIndex); - emit updateServerFromApiFinished(); - apiController->deleteLater(); - }); - connect(apiController, &ApiController::errorOccurred, this, [this, apiController](ErrorCode errorCode) { + if (errorCode != ErrorCode::NoError) { emit installationErrorOccurred(errorCode); - apiController->deleteLater(); - }); + return false; + } + return true; } bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig, diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index d7ab35534..8e42b5b22 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -10,7 +10,6 @@ #include "ui/models/containers_model.h" #include "ui/models/protocols_model.h" #include "ui/models/servers_model.h" -#include "ui/models/apiServicesModel.h" class InstallController : public QObject { @@ -19,7 +18,6 @@ class InstallController : public QObject explicit InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const QSharedPointer &protocolsModel, const QSharedPointer &clientManagementModel, - const QSharedPointer &apiServicesModel, const std::shared_ptr &settings, QObject *parent = nullptr); ~InstallController(); @@ -52,21 +50,13 @@ public slots: void addEmptyServer(); - bool fillAvailableServices(); - bool installServiceFromApi(); - bool updateServiceFromApi(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, bool reloadServiceConfig = false); - - void updateServiceFromTelegram(const int serverIndex); + bool isConfigValid(); signals: void installContainerFinished(const QString &finishMessage, bool isServiceInstall); void installServerFinished(const QString &finishMessage); - void installServerFromApiFinished(const QString &message); void updateContainerFinished(const QString &message); - void updateServerFromApiFinished(); - void changeApiCountryFinished(const QString &message); - void reloadServerFromApiFinished(const QString &message); void scanServerFinished(bool isInstalledContainerFound); @@ -91,6 +81,8 @@ public slots: void cachedProfileCleared(const QString &message); void apiConfigRemoved(const QString &message); + void noInstalledContainers(); + private: void installServer(const DockerContainer container, const QMap &installedContainers, const ServerCredentials &serverCredentials, const QSharedPointer &serverController, @@ -108,7 +100,6 @@ public slots: QSharedPointer m_containersModel; QSharedPointer m_protocolModel; QSharedPointer m_clientManagementModel; - QSharedPointer m_apiServicesModel; std::shared_ptr m_settings; diff --git a/client/ui/models/apiServicesModel.cpp b/client/ui/models/api/apiServicesModel.cpp similarity index 100% rename from client/ui/models/apiServicesModel.cpp rename to client/ui/models/api/apiServicesModel.cpp diff --git a/client/ui/models/apiServicesModel.h b/client/ui/models/api/apiServicesModel.h similarity index 100% rename from client/ui/models/apiServicesModel.h rename to client/ui/models/api/apiServicesModel.h diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index f77eb6fce..7cde28b4a 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -261,7 +261,7 @@ void ServersModel::setProcessedServerIndex(const int index) updateContainersModel(); if (data(index, IsServerFromGatewayApiRole).toBool()) { if (data(index, IsCountrySelectionAvailableRole).toBool()) { - emit updateApiLanguageModel(); + emit updateApiCountryModel(); } emit updateApiServicesModel(); } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 78bc22cc0..4b790c7a8 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -140,7 +140,7 @@ public slots: void defaultServerContainersUpdated(const QJsonArray &containers); void defaultServerDefaultContainerChanged(const int containerIndex); - void updateApiLanguageModel(); + void updateApiCountryModel(); void updateApiServicesModel(); private: diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index 41735b14d..353547efb 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -136,7 +136,7 @@ PageType { PageController.showBusyIndicator(true) var prevIndex = ApiCountryModel.currentIndex ApiCountryModel.currentIndex = index - if (!InstallController.updateServiceFromApi(ServersModel.defaultIndex, countryCode, countryName)) { + if (!ApiConfigsController.updateServiceFromGateway(ServersModel.defaultIndex, countryCode, countryName)) { ApiCountryModel.currentIndex = prevIndex } } diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 303ba36cd..e377140f1 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -253,7 +253,7 @@ PageType { PageController.showNotificationMessage(qsTr("Cannot reload API config during active connection")) } else { PageController.showBusyIndicator(true) - InstallController.updateServiceFromApi(ServersModel.processedIndex, "", "", true) + ApiConfigsController.updateServiceFromGateway(ServersModel.processedIndex, "", "", true) PageController.showBusyIndicator(false) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml index 8c02195e2..134e73b65 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml @@ -138,7 +138,7 @@ PageType { PageController.closePage() } else { PageController.showBusyIndicator(true) - InstallController.installServiceFromApi() + ApiConfigsController.importServiceFromGateway() PageController.showBusyIndicator(false) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index f145127fe..38a1da52d 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -254,7 +254,7 @@ PageType { property bool isVisible: true property var handler: function() { PageController.showBusyIndicator(true) - var result = InstallController.fillAvailableServices() + var result = ApiConfigsController.fillAvailableServices() PageController.showBusyIndicator(false) if (result) { PageController.goToPage(PageEnum.PageSetupWizardApiServicesList) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 27e851de6..76133da80 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -46,7 +46,7 @@ PageType { shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text shareConnectionDrawer.openTriggered() - shareConnectionDrawer.contentVisible = false + shareConnectionDrawer.contentVisible = true PageController.showBusyIndicator(true) switch (type) { diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 6081bbc81..bf13552c4 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -132,29 +132,6 @@ PageType { PageController.showNotificationMessage(message) } - function onInstallServerFromApiFinished(message) { - PageController.showBusyIndicator(false) - if (!ConnectionController.isConnected) { - ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); - ServersModel.processedIndex = ServersModel.defaultIndex - } - - PageController.goToPageHome() - PageController.showNotificationMessage(message) - } - - function onChangeApiCountryFinished(message) { - PageController.showBusyIndicator(false) - - PageController.goToPageHome() - PageController.showNotificationMessage(message) - } - - function onReloadServerFromApiFinished(message) { - PageController.goToPageHome() - PageController.showNotificationMessage(message) - } - function onRemoveProcessedServerFinished(finishedMessage) { if (!ServersModel.getServersCount()) { PageController.goToPageHome() @@ -164,6 +141,14 @@ PageType { } PageController.showNotificationMessage(finishedMessage) } + + function onNoInstalledContainers() { + PageController.setTriggeredByConnectButton(true) + + ServersModel.processedIndex = ServersModel.getDefaultServerIndex() + InstallController.setShouldCreateServer(false) + PageController.goToPage(PageEnum.PageSetupWizardEasy) + } } Connections { @@ -175,14 +160,6 @@ PageType { PageController.showNotificationMessage(message) PageController.closePage() } - - function onNoInstalledContainers() { - PageController.setTriggeredByConnectButton(true) - - ServersModel.processedIndex = ServersModel.getDefaultServerIndex() - InstallController.setShouldCreateServer(false) - PageController.goToPage(PageEnum.PageSetupWizardEasy) - } } Connections { @@ -232,6 +209,37 @@ PageType { } } + Connections { + target: ApiConfigsController + + function onErrorOccurred(error) { + PageController.showErrorMessage(error) + } + + function onInstallServerFromApiFinished(message) { + PageController.showBusyIndicator(false) + if (!ConnectionController.isConnected) { + ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); + ServersModel.processedIndex = ServersModel.defaultIndex + } + + PageController.goToPageHome() + PageController.showNotificationMessage(message) + } + + function onChangeApiCountryFinished(message) { + PageController.showBusyIndicator(false) + + PageController.goToPageHome() + PageController.showNotificationMessage(message) + } + + function onReloadServerFromApiFinished(message) { + PageController.goToPageHome() + PageController.showNotificationMessage(message) + } + } + StackViewType { id: tabBarStackView objectName: "tabBarStackView" From 52c12940c4fdb0b8d4bd2beb4c4e9fc016cc1b53 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 15 Feb 2025 13:57:44 +0700 Subject: [PATCH 08/23] bugfix: fixed visability of share drawer --- client/ui/qml/Components/ShareConnectionDrawer.qml | 4 ---- client/ui/qml/Pages2/PageSettingsApiServerInfo.qml | 1 - client/ui/qml/Pages2/PageShare.qml | 1 - client/ui/qml/Pages2/PageShareFullAccess.qml | 1 - 4 files changed, 7 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 9a9f1aa16..a7910ebc4 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -26,8 +26,6 @@ DrawerType2 { property string copyButtonText: qsTr("Copy") property bool showSettingsButtonVisible: true - property bool contentVisible - property string configExtension: ".vpn" property string configCaption: qsTr("Save AmneziaVPN config") property string configFileName: "amnezia_config" @@ -75,8 +73,6 @@ DrawerType2 { header: ColumnLayout { width: listView.width - visible: root.contentVisible - BasicButtonType { id: shareButton Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index e377140f1..05012b46c 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -163,7 +163,6 @@ PageType { shareConnectionDrawer.headerText = qsTr("Amnezia Premium subscription key") shareConnectionDrawer.openTriggered() - shareConnectionDrawer.contentVisible = false shareConnectionDrawer.showSettingsButtonVisible = false; shareConnectionDrawer.shareButtonText = qsTr("Save VPN key to file") shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key") diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 76133da80..af208544d 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -46,7 +46,6 @@ PageType { shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text shareConnectionDrawer.openTriggered() - shareConnectionDrawer.contentVisible = true PageController.showBusyIndicator(true) switch (type) { diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index af409d722..70fd62926 100644 --- a/client/ui/qml/Pages2/PageShareFullAccess.qml +++ b/client/ui/qml/Pages2/PageShareFullAccess.qml @@ -141,7 +141,6 @@ PageType { shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text shareConnectionDrawer.openTriggered() - shareConnectionDrawer.contentVisible = true PageController.showBusyIndicator(false) } From a1ca994c8be71574f938800b70201f4f2b2f4ac5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 15 Feb 2025 13:58:48 +0700 Subject: [PATCH 09/23] feature: added 409 error handling from server response --- client/core/api/apiUtils.cpp | 29 +++++++++++++++++++ client/core/api/apiUtils.h | 4 +++ client/core/controllers/gatewayController.cpp | 6 ++-- client/core/defs.h | 1 + client/core/errorstrings.cpp | 1 + client/core/networkUtilities.cpp | 20 ------------- client/core/networkUtilities.h | 5 ---- .../controllers/api/apiConfigsController.cpp | 2 +- 8 files changed, 39 insertions(+), 29 deletions(-) diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp index 7c58e0e1e..088fdba1a 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/api/apiUtils.cpp @@ -49,3 +49,32 @@ apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigO { return static_cast(serverConfigObject.value(apiDefs::key::configVersion).toInt()); } + +amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply) +{ + const int httpStatusCodeConflict = 409; + + if (!sslErrors.empty()) { + qDebug().noquote() << sslErrors; + return amnezia::ErrorCode::ApiConfigSslError; + } else if (reply->error() == QNetworkReply::NoError) { + return amnezia::ErrorCode::NoError; + } else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError + || reply->error() == QNetworkReply::NetworkError::TimeoutError) { + return amnezia::ErrorCode::ApiConfigTimeoutError; + } else { + QString err = reply->errorString(); + int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + qDebug() << QString::fromUtf8(reply->readAll()); + qDebug() << reply->error(); + qDebug() << err; + qDebug() << httpStatusCode; + if (httpStatusCode == httpStatusCodeConflict) { + return amnezia::ErrorCode::ApiConfigLimitError; + } + return amnezia::ErrorCode::ApiConfigDownloadError; + } + + qDebug() << "something went wrong"; + return amnezia::ErrorCode::InternalError; +} diff --git a/client/core/api/apiUtils.h b/client/core/api/apiUtils.h index bb122736b..82ac315be 100644 --- a/client/core/api/apiUtils.h +++ b/client/core/api/apiUtils.h @@ -1,9 +1,11 @@ #ifndef APIUTILS_H #define APIUTILS_H +#include #include #include "apiDefs.h" +#include "core/defs.h" namespace apiUtils { @@ -13,6 +15,8 @@ namespace apiUtils apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject); apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject); + + amnezia::ErrorCode checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply); } #endif // APIUTILS_H diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index 44a3d5d1e..268561ae1 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -9,7 +9,7 @@ #include "QRsa.h" #include "amnezia_application.h" -#include "core/networkUtilities.h" +#include "core/api/apiUtils.h" #include "utilities.h" namespace @@ -75,7 +75,7 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction); } - auto errorCode = NetworkUtilities::checkNetworkReplyErrors(sslErrors, reply); + auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply); reply->deleteLater(); return errorCode; @@ -165,7 +165,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction); } - auto errorCode = NetworkUtilities::checkNetworkReplyErrors(sslErrors, reply); + auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply); reply->deleteLater(); if (errorCode) { return errorCode; diff --git a/client/core/defs.h b/client/core/defs.h index c7fde0ee9..330ebc48a 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -109,6 +109,7 @@ namespace amnezia ApiMissingAgwPublicKey = 1105, ApiConfigDecryptionError = 1106, ApiServicesMissingError = 1107, + ApiConfigLimitError = 1108, // QFile errors OpenError = 1200, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 976915c21..c45b1eafb 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -66,6 +66,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break; case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break; case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break; + case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break; // QFile errors case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; diff --git a/client/core/networkUtilities.cpp b/client/core/networkUtilities.cpp index 7d98e6a19..a5825f0d4 100644 --- a/client/core/networkUtilities.cpp +++ b/client/core/networkUtilities.cpp @@ -107,26 +107,6 @@ QStringList NetworkUtilities::summarizeRoutes(const QStringList &ips, const QStr return QStringList(); } -amnezia::ErrorCode NetworkUtilities::checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply) -{ - if (!sslErrors.empty()) { - qDebug().noquote() << sslErrors; - return amnezia::ErrorCode::ApiConfigSslError; - } else if (reply->error() == QNetworkReply::NoError) { - return amnezia::ErrorCode::NoError; - } else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError - || reply->error() == QNetworkReply::NetworkError::TimeoutError) { - return amnezia::ErrorCode::ApiConfigTimeoutError; - } else { - QString err = reply->errorString(); - qDebug() << QString::fromUtf8(reply->readAll()); - qDebug() << reply->error(); - qDebug() << err; - qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - return amnezia::ErrorCode::ApiConfigDownloadError; - } -} - QString NetworkUtilities::getIPAddress(const QString &host) { QHostAddress address(host); diff --git a/client/core/networkUtilities.h b/client/core/networkUtilities.h index 805ce9e50..3b64b547c 100644 --- a/client/core/networkUtilities.h +++ b/client/core/networkUtilities.h @@ -7,8 +7,6 @@ #include #include -#include "core/defs.h" - class NetworkUtilities : public QObject { @@ -33,9 +31,6 @@ class NetworkUtilities : public QObject static QString ipAddressFromIpWithSubnet(const QString ip); static QStringList summarizeRoutes(const QStringList &ips, const QString cidr); - - static amnezia::ErrorCode checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply); - }; #endif // NETWORKUTILITIES_H diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index ee1332536..7085d9a2e 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -269,7 +269,7 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); wait.exec(); - auto errorCode = NetworkUtilities::checkNetworkReplyErrors(sslErrors, reply); + auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply); if (errorCode != ErrorCode::NoError) { reply->deleteLater(); emit errorOccurred(errorCode); From c128ba981ccb0106979da9d73f704dba2d704c89 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 15 Feb 2025 15:29:53 +0700 Subject: [PATCH 10/23] chore: fixed android build --- client/core/controllers/coreController.cpp | 2 ++ client/core/controllers/coreController.h | 2 +- client/core/controllers/gatewayController.cpp | 3 +++ client/core/qrCodeUtils.cpp | 8 ++++---- client/core/qrCodeUtils.h | 2 +- .../controllers/api/apiConfigsController.cpp | 4 ++-- .../ui/controllers/api/apiSettingsController.h | 2 +- client/ui/controllers/exportController.cpp | 18 +++++++++--------- client/ui/controllers/importController.cpp | 3 ++- client/ui/models/{ => api}/apiCountryModel.cpp | 4 ++-- client/ui/models/{ => api}/apiCountryModel.h | 2 +- 11 files changed, 28 insertions(+), 22 deletions(-) rename client/ui/models/{ => api}/apiCountryModel.cpp (93%) rename client/ui/models/{ => api}/apiCountryModel.h (91%) diff --git a/client/core/controllers/coreController.cpp b/client/core/controllers/coreController.cpp index 551eb61fd..a077d8093 100644 --- a/client/core/controllers/coreController.cpp +++ b/client/core/controllers/coreController.cpp @@ -1,5 +1,7 @@ #include "coreController.h" +#include + #if defined(Q_OS_ANDROID) #include "core/installedAppsImageProvider.h" #include "platforms/android/android_controller.h" diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h index c67c9cccc..1b725825b 100644 --- a/client/core/controllers/coreController.h +++ b/client/core/controllers/coreController.h @@ -26,7 +26,7 @@ #endif #include "ui/models/api/apiAccountInfoModel.h" #include "ui/models/api/apiServicesModel.h" -#include "ui/models/apiCountryModel.h" +#include "ui/models/api/apiCountryModel.h" #include "ui/models/appSplitTunnelingModel.h" #include "ui/models/clientManagementModel.h" #include "ui/models/protocols/awgConfigModel.h" diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index 268561ae1..d7a4b018a 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -1,5 +1,8 @@ #include "gatewayController.h" +#include +#include + #include #include #include diff --git a/client/core/qrCodeUtils.cpp b/client/core/qrCodeUtils.cpp index 02f43ff07..a18af1726 100644 --- a/client/core/qrCodeUtils.cpp +++ b/client/core/qrCodeUtils.cpp @@ -3,7 +3,7 @@ #include #include -QList qrCodeUtuls::generateQrCodeImageSeries(const QByteArray &data) +QList qrCodeUtils::generateQrCodeImageSeries(const QByteArray &data) { double k = 850; @@ -12,7 +12,7 @@ QList qrCodeUtuls::generateQrCodeImageSeries(const QByteArray &data) for (int i = 0; i < data.size(); i = i + k) { QByteArray chunk; QDataStream s(&chunk, QIODevice::WriteOnly); - s << qrCodeUtuls::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k); + s << qrCodeUtils::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k); QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); @@ -24,12 +24,12 @@ QList qrCodeUtuls::generateQrCodeImageSeries(const QByteArray &data) return chunks; } -QString qrCodeUtuls::svgToBase64(const QString &image) +QString qrCodeUtils::svgToBase64(const QString &image) { return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data()); } -qrcodegen::QrCode qrCodeUtuls::generateQrCode(const QByteArray &data) +qrcodegen::QrCode qrCodeUtils::generateQrCode(const QByteArray &data) { return qrcodegen::QrCode::encodeText(data, qrcodegen::QrCode::Ecc::LOW); } diff --git a/client/core/qrCodeUtils.h b/client/core/qrCodeUtils.h index f5f207c15..cda0723b2 100644 --- a/client/core/qrCodeUtils.h +++ b/client/core/qrCodeUtils.h @@ -5,7 +5,7 @@ #include "qrcodegen.hpp" -namespace qrCodeUtuls +namespace qrCodeUtils { constexpr const qint16 qrMagicCode = 1984; diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 7085d9a2e..33894e793 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -99,8 +99,8 @@ void ApiConfigsController::prepareVpnKeyExport() auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString(); - auto qr = qrCodeUtuls::generateQrCode(vpnKey.toUtf8()); - m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); + auto qr = qrCodeUtils::generateQrCode(vpnKey.toUtf8()); + m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); emit vpnKeyExportReady(); } diff --git a/client/ui/controllers/api/apiSettingsController.h b/client/ui/controllers/api/apiSettingsController.h index ae9d91260..5374e8997 100644 --- a/client/ui/controllers/api/apiSettingsController.h +++ b/client/ui/controllers/api/apiSettingsController.h @@ -4,7 +4,7 @@ #include #include "ui/models/api/apiAccountInfoModel.h" -#include "ui/models/apiCountryModel.h" +#include "ui/models/api/apiCountryModel.h" #include "ui/models/servers_model.h" class ApiSettingsController : public QObject diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 516d1f4d0..b47111ae5 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -50,7 +50,7 @@ void ExportController::generateFullAccessConfig() compressedConfig = qCompress(compressedConfig, 8); m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); - m_qrCodes = qrCodeUtuls::generateQrCodeImageSeries(compressedConfig); + m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); } @@ -92,7 +92,7 @@ void ExportController::generateConnectionConfig(const QString &clientName) compressedConfig = qCompress(compressedConfig, 8); m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); - m_qrCodes = qrCodeUtuls::generateQrCodeImageSeries(compressedConfig); + m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); } @@ -149,7 +149,7 @@ void ExportController::generateOpenVpnConfig(const QString &clientName) m_config.append(line + "\n"); } - m_qrCodes = qrCodeUtuls::generateQrCodeImageSeries(m_config.toUtf8()); + m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8()); emit exportConfigChanged(); } @@ -167,8 +167,8 @@ void ExportController::generateWireGuardConfig(const QString &clientName) m_config.append(line + "\n"); } - auto qr = qrCodeUtuls::generateQrCode(m_config.toUtf8()); - m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); + auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8()); + m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); emit exportConfigChanged(); } @@ -187,8 +187,8 @@ void ExportController::generateAwgConfig(const QString &clientName) m_config.append(line + "\n"); } - auto qr = qrCodeUtuls::generateQrCode(m_config.toUtf8()); - m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); + auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8()); + m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); emit exportConfigChanged(); } @@ -221,8 +221,8 @@ void ExportController::generateShadowSocksConfig() m_nativeConfigString = "ss://" + m_nativeConfigString.toUtf8().toBase64(); - auto qr = qrCodeUtuls::generateQrCode(m_nativeConfigString.toUtf8()); - m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); + auto qr = qrCodeUtils::generateQrCode(m_nativeConfigString.toUtf8()); + m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); emit exportConfigChanged(); } diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index c79b32882..178efc16a 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -10,6 +10,7 @@ #include "core/api/apiDefs.h" #include "core/api/apiUtils.h" #include "core/errorstrings.h" +#include "core/qrCodeUtils.h" #include "core/serialization/serialization.h" #include "systemController.h" #include "utilities.h" @@ -579,7 +580,7 @@ bool ImportController::parseQrCodeChunk(const QString &code) qint16 magic; s >> magic; - if (magic == amnezia::qrMagicCode) { + if (magic == qrCodeUtils::qrMagicCode) { quint8 chunksCount; s >> chunksCount; if (m_totalQrCodeChunksCount != chunksCount) { diff --git a/client/ui/models/apiCountryModel.cpp b/client/ui/models/api/apiCountryModel.cpp similarity index 93% rename from client/ui/models/apiCountryModel.cpp rename to client/ui/models/api/apiCountryModel.cpp index 922a9d568..0043efd2f 100644 --- a/client/ui/models/apiCountryModel.cpp +++ b/client/ui/models/api/apiCountryModel.cpp @@ -47,11 +47,11 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const return QVariant(); } -void ApiCountryModel::updateModel(const QJsonArray &data, const QString ¤tCountryCode) +void ApiCountryModel::updateModel(const QJsonArray &countries, const QString ¤tCountryCode) { beginResetModel(); - m_countries = data; + m_countries = countries; for (int i = 0; i < m_countries.size(); i++) { if (m_countries.at(i).toObject().value(configKey::serverCountryCode).toString() == currentCountryCode) { m_currentIndex = i; diff --git a/client/ui/models/apiCountryModel.h b/client/ui/models/api/apiCountryModel.h similarity index 91% rename from client/ui/models/apiCountryModel.h rename to client/ui/models/api/apiCountryModel.h index b9e243d05..c935ebcef 100644 --- a/client/ui/models/apiCountryModel.h +++ b/client/ui/models/api/apiCountryModel.h @@ -24,7 +24,7 @@ class ApiCountryModel : public QAbstractListModel Q_PROPERTY(int currentIndex READ getCurrentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) public slots: - void updateModel(const QJsonArray &data, const QString ¤tCountryCode); + void updateModel(const QJsonArray &countries, const QString ¤tCountryCode); int getCurrentIndex(); void setCurrentIndex(const int i); From a5254ac238a6d1293191165f9fea0bfeb7f91104 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 19 Feb 2025 14:56:53 +0700 Subject: [PATCH 11/23] chore: fixed qr code display --- .../controllers/api/apiConfigsController.cpp | 4 ++-- .../qml/Components/ShareConnectionDrawer.qml | 23 +++++++++++-------- .../qml/Pages2/PageSettingsApiServerInfo.qml | 2 +- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 33894e793..0861471a6 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -99,8 +99,7 @@ void ApiConfigsController::prepareVpnKeyExport() auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString(); - auto qr = qrCodeUtils::generateQrCode(vpnKey.toUtf8()); - m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); + m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(vpnKey.toUtf8()); emit vpnKeyExportReady(); } @@ -214,6 +213,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode)); newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType)); newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol)); + newApiConfig.insert(apiDefs::key::vpnKey, apiConfig.value(apiDefs::key::vpnKey)); newServerConfig.insert(configKey::apiConfig, newApiConfig); newServerConfig.insert(configKey::authData, authData); diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index a7910ebc4..dd59180b0 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -24,7 +24,7 @@ DrawerType2 { property string configContentHeaderText property string shareButtonText: qsTr("Share") property string copyButtonText: qsTr("Copy") - property bool showSettingsButtonVisible: true + property bool isSelfHostedConfig: true property string configExtension: ".vpn" property string configCaption: qsTr("Save AmneziaVPN config") @@ -153,7 +153,7 @@ DrawerType2 { Layout.leftMargin: 16 Layout.rightMargin: 16 - visible: root.showSettingsButtonVisible + visible: root.isSelfHostedConfig defaultColor: AmneziaStyle.color.transparent hoveredColor: AmneziaStyle.color.translucentWhite @@ -285,6 +285,8 @@ DrawerType2 { delegate: ColumnLayout { width: listView.width + property bool isQrCodeVisible: root.isSelfHostedConfig ? ExportController.qrCodesCount > 0 : ApiConfigsController.qrCodesCount > 0 + Rectangle { id: qrCodeContainer @@ -294,7 +296,7 @@ DrawerType2 { Layout.leftMargin: 16 Layout.rightMargin: 16 - visible: ExportController.qrCodesCount > 0 + visible: isQrCodeVisible color: "white" @@ -302,7 +304,8 @@ DrawerType2 { anchors.fill: parent smooth: false - source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : "" + source: root.isSelfHostedConfig ? (isQrCodeVisible ? ExportController.qrCodes[0] : "") : + (isQrCodeVisible ? ApiConfigsController.qrCodes[0] : "") property bool isFocusable: true @@ -333,15 +336,17 @@ DrawerType2 { Timer { property int index: 0 interval: 1000 - running: ExportController.qrCodesCount > 0 + running: isQrCodeVisible repeat: true onTriggered: { - if (ExportController.qrCodesCount > 0) { + if (isQrCodeVisible) { index++ - if (index >= ExportController.qrCodesCount) { + let qrCodesCount = root.isSelfHostedConfig ? ExportController.qrCodesCount : ApiConfigsController.qrCodesCount + if (index >= qrCodesCount) { index = 0 } - parent.source = ExportController.qrCodes[index] + + parent.source = root.isSelfHostedConfig ? ExportController.qrCodes[index] : ApiConfigsController.qrCodes[index] } } } @@ -359,7 +364,7 @@ DrawerType2 { Layout.leftMargin: 16 Layout.rightMargin: 16 - visible: ExportController.qrCodesCount > 0 + visible: isQrCodeVisible horizontalAlignment: Text.AlignHCenter text: qsTr("To read the QR code in the Amnezia app, select \"Add server\" → \"I have data to connect\" → \"QR code, key or settings file\"") diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 05012b46c..75c1e893d 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -163,7 +163,7 @@ PageType { shareConnectionDrawer.headerText = qsTr("Amnezia Premium subscription key") shareConnectionDrawer.openTriggered() - shareConnectionDrawer.showSettingsButtonVisible = false; + shareConnectionDrawer.isSelfHostedConfig = false; shareConnectionDrawer.shareButtonText = qsTr("Save VPN key to file") shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key") From eda24765e70e4561b470bd2a8352be6010687a55 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 19 Feb 2025 20:27:15 +0700 Subject: [PATCH 12/23] feature: added error messages handler --- client/core/controllers/coreController.cpp | 22 +++++++++++-------- client/core/controllers/coreController.h | 3 ++- .../PageSettingsApiAvailableCountries.qml | 1 + client/ui/qml/Pages2/PageStart.qml | 7 ------ 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/client/core/controllers/coreController.cpp b/client/core/controllers/coreController.cpp index a077d8093..3d71ce80b 100644 --- a/client/core/controllers/coreController.cpp +++ b/client/core/controllers/coreController.cpp @@ -196,10 +196,11 @@ void CoreController::initAppleController() void CoreController::initSignalHandlers() { + initErrorMessagesHandler(); + initApiCountryModelUpdateHandler(); initContainerModelUpdateHandler(); initAdminConfigRevokedHandler(); - initConnectionErrorOccurredHandler(); initPassphraseRequestHandler(); initTranslationsUpdatedHandler(); initAutoConnectHandler(); @@ -244,6 +245,17 @@ void CoreController::updateTranslator(const QLocale &locale) emit translationsUpdated(); } +void CoreController::initErrorMessagesHandler() +{ + connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](ErrorCode errorCode) { + emit m_pageController->showErrorMessage(errorCode); + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); + }); + + connect(m_apiConfigsController.get(), &ApiConfigsController::errorOccurred, m_pageController.get(), + qOverload(&PageController::showErrorMessage)); +} + void CoreController::setQmlRoot() { m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); @@ -274,14 +286,6 @@ void CoreController::initAdminConfigRevokedHandler() &ServersModel::clearCachedProfile); } -void CoreController::initConnectionErrorOccurredHandler() -{ - connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](ErrorCode errorCode) { - emit m_pageController->showErrorMessage(errorCode); - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); - }); -} - void CoreController::initPassphraseRequestHandler() { connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h index 1b725825b..029044c4b 100644 --- a/client/core/controllers/coreController.h +++ b/client/core/controllers/coreController.h @@ -69,10 +69,11 @@ class CoreController : public QObject void updateTranslator(const QLocale &locale); + void initErrorMessagesHandler(); + void initApiCountryModelUpdateHandler(); void initContainerModelUpdateHandler(); void initAdminConfigRevokedHandler(); - void initConnectionErrorOccurredHandler(); void initPassphraseRequestHandler(); void initTranslationsUpdatedHandler(); void initAutoConnectHandler(); diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index 353547efb..dfbc70b45 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -139,6 +139,7 @@ PageType { if (!ApiConfigsController.updateServiceFromGateway(ServersModel.defaultIndex, countryCode, countryName)) { ApiCountryModel.currentIndex = prevIndex } + PageController.showBusyIndicator(false) } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index bf13552c4..0a21497d5 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -212,12 +212,7 @@ PageType { Connections { target: ApiConfigsController - function onErrorOccurred(error) { - PageController.showErrorMessage(error) - } - function onInstallServerFromApiFinished(message) { - PageController.showBusyIndicator(false) if (!ConnectionController.isConnected) { ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); ServersModel.processedIndex = ServersModel.defaultIndex @@ -228,8 +223,6 @@ PageType { } function onChangeApiCountryFinished(message) { - PageController.showBusyIndicator(false) - PageController.goToPageHome() PageController.showNotificationMessage(message) } From c2b17c128d6c9efa23f46161e0aa5aea0ff81c32 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 19 Feb 2025 22:58:04 +0700 Subject: [PATCH 13/23] feature: added issued configs info parsing --- client/core/api/apiDefs.h | 16 ++++++ .../controllers/api/apiSettingsController.cpp | 1 + client/ui/models/api/apiAccountInfoModel.cpp | 22 ++++---- client/ui/models/api/apiAccountInfoModel.h | 2 + client/ui/models/api/apiCountryModel.cpp | 51 ++++++++++++++----- client/ui/models/api/apiCountryModel.h | 23 ++++++++- 6 files changed, 87 insertions(+), 28 deletions(-) diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h index b01832ce3..2892f90b8 100644 --- a/client/core/api/apiDefs.h +++ b/client/core/api/apiDefs.h @@ -26,6 +26,22 @@ namespace apiDefs constexpr QLatin1String stackType("stack_type"); constexpr QLatin1String vpnKey("vpn_key"); + + constexpr QLatin1String installationUuid("installation_uuid"); + constexpr QLatin1String workerLastUpdated("worker_last_updated"); + constexpr QLatin1String lastDownloaded("last_downloaded"); + constexpr QLatin1String sourceType("source_type"); + + constexpr QLatin1String serverCountryCode("server_country_code"); + constexpr QLatin1String serverCountryName("server_country_name"); + + constexpr QLatin1String osVersion("os_version"); + + constexpr QLatin1String availableCountries("available_countries"); + constexpr QLatin1String activeDeviceCount("active_device_count"); + constexpr QLatin1String maxDeviceCount("max_device_count"); + constexpr QLatin1String subscriptionEndDate("subscription_end_date"); + constexpr QLatin1String issuedConfigs("issued_configs"); } const int requestTimeoutMsecs = 12 * 1000; // 12 secs diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp index 0bebf19eb..5f436436f 100644 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -68,4 +68,5 @@ bool ApiSettingsController::getAccountInfo() void ApiSettingsController::updateApiCountryModel() { m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), ""); + m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo()); } diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index 254097bcb..055c23479 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -8,14 +8,6 @@ namespace { Logger logger("AccountInfoModel"); - - namespace configKey - { - constexpr char availableCountries[] = "available_countries"; - constexpr char activeDeviceCount[] = "active_device_count"; - constexpr char maxDeviceCount[] = "max_device_count"; - constexpr char subscriptionEndDate[] = "subscription_end_date"; - } } ApiAccountInfoModel::ApiAccountInfoModel(QObject *parent) : QAbstractListModel(parent) @@ -77,11 +69,12 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons AccountInfoData accountInfoData; - m_availableCountries = accountInfoObject.value(configKey::availableCountries).toArray(); + m_availableCountries = accountInfoObject.value(apiDefs::key::availableCountries).toArray(); + m_issuedConfigsInfo = accountInfoObject.value(apiDefs::key::issuedConfigs).toArray(); - accountInfoData.activeDeviceCount = accountInfoObject.value(configKey::activeDeviceCount).toInt(); - accountInfoData.maxDeviceCount = accountInfoObject.value(configKey::maxDeviceCount).toInt(); - accountInfoData.subscriptionEndDate = accountInfoObject.value(configKey::subscriptionEndDate).toString(); + accountInfoData.activeDeviceCount = accountInfoObject.value(apiDefs::key::activeDeviceCount).toInt(); + accountInfoData.maxDeviceCount = accountInfoObject.value(apiDefs::key::maxDeviceCount).toInt(); + accountInfoData.subscriptionEndDate = accountInfoObject.value(apiDefs::key::subscriptionEndDate).toString(); accountInfoData.configType = apiUtils::getConfigType(serverConfig); @@ -108,6 +101,11 @@ QJsonArray ApiAccountInfoModel::getAvailableCountries() return m_availableCountries; } +QJsonArray ApiAccountInfoModel::getIssuedConfigsInfo() +{ + return m_issuedConfigsInfo; +} + QString ApiAccountInfoModel::getTelegramBotLink() { if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h index 9dfdc508f..e50ea5f56 100644 --- a/client/ui/models/api/apiAccountInfoModel.h +++ b/client/ui/models/api/apiAccountInfoModel.h @@ -31,6 +31,7 @@ public slots: QVariant data(const QString &roleString); QJsonArray getAvailableCountries(); + QJsonArray getIssuedConfigsInfo(); QString getTelegramBotLink(); protected: @@ -48,6 +49,7 @@ public slots: AccountInfoData m_accountInfoData; QJsonArray m_availableCountries; + QJsonArray m_issuedConfigsInfo; }; #endif // APIACCOUNTINFOMODEL_H diff --git a/client/ui/models/api/apiCountryModel.cpp b/client/ui/models/api/apiCountryModel.cpp index 0043efd2f..d8d74a419 100644 --- a/client/ui/models/api/apiCountryModel.cpp +++ b/client/ui/models/api/apiCountryModel.cpp @@ -2,17 +2,12 @@ #include +#include "core/api/apiDefs.h" #include "logger.h" namespace { Logger logger("ApiCountryModel"); - - namespace configKey - { - constexpr char serverCountryCode[] = "server_country_code"; - constexpr char serverCountryName[] = "server_country_name"; - } } ApiCountryModel::ApiCountryModel(QObject *parent) : QAbstractListModel(parent) @@ -30,17 +25,19 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) return QVariant(); - QJsonObject countryInfo = m_countries.at(index.row()).toObject(); + CountryInfo countryInfo = m_countries.at(index.row()); + IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.value(countryInfo.countryCode); + bool isIssued = !issuedConfigInfo.lastDownloaded.isEmpty(); switch (role) { case CountryCodeRole: { - return countryInfo.value(configKey::serverCountryCode).toString(); + return countryInfo.countryCode; } case CountryNameRole: { - return countryInfo.value(configKey::serverCountryName).toString(); + return countryInfo.countryName; } case CountryImageCodeRole: { - return countryInfo.value(configKey::serverCountryCode).toString().toUpper(); + return countryInfo.countryCode.toUpper(); } } @@ -51,13 +48,39 @@ void ApiCountryModel::updateModel(const QJsonArray &countries, const QString &cu { beginResetModel(); - m_countries = countries; - for (int i = 0; i < m_countries.size(); i++) { - if (m_countries.at(i).toObject().value(configKey::serverCountryCode).toString() == currentCountryCode) { + m_countries.clear(); + for (int i = 0; i < countries.size(); i++) { + CountryInfo countryInfo; + QJsonObject countryObject = countries.at(i).toObject(); + + countryInfo.countryName = countryObject.value(apiDefs::key::serverCountryName).toString(); + countryInfo.countryCode = countryObject.value(apiDefs::key::serverCountryCode).toString(); + + if (countryInfo.countryCode == currentCountryCode) { m_currentIndex = i; emit currentIndexChanged(m_currentIndex); - break; } + m_countries.push_back(countryInfo); + } + + endResetModel(); +} + +void ApiCountryModel::updateIssuedConfigsInfo(const QJsonArray &issuedConfigs) +{ + beginResetModel(); + + for (int i = 0; i < issuedConfigs.size(); i++) { + IssuedConfigInfo issuedConfigInfo; + QJsonObject issuedConfigObject = issuedConfigs.at(i).toObject(); + + issuedConfigInfo.installationUuid = issuedConfigObject.value(apiDefs::key::installationUuid).toString(); + issuedConfigInfo.workerLastUpdated = issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString(); + issuedConfigInfo.lastDownloaded = issuedConfigObject.value(apiDefs::key::lastDownloaded).toString(); + issuedConfigInfo.sourceType = issuedConfigObject.value(apiDefs::key::sourceType).toString(); + issuedConfigInfo.osVersion = issuedConfigObject.value(apiDefs::key::osVersion).toString(); + + m_issuedConfigs.insert(issuedConfigObject.value(apiDefs::key::serverCountryCode).toString(), issuedConfigInfo); } endResetModel(); diff --git a/client/ui/models/api/apiCountryModel.h b/client/ui/models/api/apiCountryModel.h index c935ebcef..e57ec0dd9 100644 --- a/client/ui/models/api/apiCountryModel.h +++ b/client/ui/models/api/apiCountryModel.h @@ -2,6 +2,7 @@ #define APICOUNTRYMODEL_H #include +#include #include class ApiCountryModel : public QAbstractListModel @@ -12,7 +13,8 @@ class ApiCountryModel : public QAbstractListModel enum Roles { CountryNameRole = Qt::UserRole + 1, CountryCodeRole, - CountryImageCodeRole + CountryImageCodeRole, + IsIssuedRole }; explicit ApiCountryModel(QObject *parent = nullptr); @@ -25,6 +27,7 @@ class ApiCountryModel : public QAbstractListModel public slots: void updateModel(const QJsonArray &countries, const QString ¤tCountryCode); + void updateIssuedConfigsInfo(const QJsonArray &issuedConfigs); int getCurrentIndex(); void setCurrentIndex(const int i); @@ -36,7 +39,23 @@ public slots: QHash roleNames() const override; private: - QJsonArray m_countries; + struct IssuedConfigInfo + { + QString installationUuid; + QString workerLastUpdated; + QString lastDownloaded; + QString sourceType; + QString osVersion; + }; + + struct CountryInfo + { + QString countryName; + QString countryCode; + }; + + QVector m_countries; + QHash m_issuedConfigs; int m_currentIndex; }; From 95121c06e2bb109e45b36f6c06138f79dfbddb25 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 20 Feb 2025 13:44:19 +0700 Subject: [PATCH 14/23] feature: added functionality to revoke api configs --- .../controllers/api/apiConfigsController.cpp | 53 +++++- .../ui/controllers/api/apiConfigsController.h | 2 + client/ui/models/api/apiCountryModel.cpp | 4 + .../Pages2/PageSettingsApiNativeConfigs.qml | 154 ++++++++++++++++-- .../qml/Pages2/PageSettingsApiServerInfo.qml | 42 ++++- 5 files changed, 232 insertions(+), 23 deletions(-) diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 0861471a6..cd3c9f1bb 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -73,7 +73,7 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); - apiPayload[configKey::serverCountryCode] = apiConfigObject.value(configKey::serverCountryCode); + apiPayload[configKey::serverCountryCode] = serverCountryCode; apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); apiPayload[configKey::uuid] = m_settings->getInstallationUuid(true); @@ -92,6 +92,31 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, return true; } +bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode) +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + + auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); + auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + + QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); + ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + + QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); + apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); + apiPayload[configKey::serverCountryCode] = serverCountryCode; + apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); + apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_native_config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } + return true; +} + void ApiConfigsController::prepareVpnKeyExport() { auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); @@ -285,6 +310,32 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) return true; } +bool ApiConfigsController::deactivateDevice() +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + + auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); + auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + + QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); + ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + + QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); + apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); + apiPayload[configKey::serverCountryCode] = apiConfigObject.value(configKey::serverCountryCode); + apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); + apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); + apiPayload[configKey::uuid] = m_settings->getInstallationUuid(true); + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } + return true; +} + bool ApiConfigsController::isConfigValid() { int serverIndex = m_serversModel->getDefaultServerIndex(); diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h index a8202afc6..f83549428 100644 --- a/client/ui/controllers/api/apiConfigsController.h +++ b/client/ui/controllers/api/apiConfigsController.h @@ -19,6 +19,7 @@ class ApiConfigsController : public QObject public slots: bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName); + bool revokeNativeConfig(const QString &serverCountryCode); // bool exportVpnKey(const QString &fileName); void prepareVpnKeyExport(); @@ -27,6 +28,7 @@ public slots: bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, bool reloadServiceConfig = false); bool updateServiceFromTelegram(const int serverIndex); + bool deactivateDevice(); bool isConfigValid(); diff --git a/client/ui/models/api/apiCountryModel.cpp b/client/ui/models/api/apiCountryModel.cpp index d8d74a419..a43184b43 100644 --- a/client/ui/models/api/apiCountryModel.cpp +++ b/client/ui/models/api/apiCountryModel.cpp @@ -39,6 +39,9 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const case CountryImageCodeRole: { return countryInfo.countryCode.toUpper(); } + case IsIssuedRole: { + return isIssued; + } } return QVariant(); @@ -103,5 +106,6 @@ QHash ApiCountryModel::roleNames() const roles[CountryNameRole] = "countryName"; roles[CountryCodeRole] = "countryCode"; roles[CountryImageCodeRole] = "countryImageCode"; + roles[IsIssuedRole] = "isIssued"; return roles; } diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml index 4e897e442..b2ab2c058 100644 --- a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -60,32 +60,152 @@ PageType { text: countryName leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" - rightImageSource: "qrc:/images/controls/download.svg" + rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg" clickedFunction: function() { - var fileName = "" - if (GC.isMobile()) { - fileName = countryCode + configExtension - } else { - fileName = SystemController.getFileName(configCaption, - qsTr("Config files (*" + configExtension + ")"), - StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + countryCode, - true, - configExtension) + if (isIssued) { + moreOptionsDrawer.countryName = countryName + moreOptionsDrawer.countryCode = countryCode + moreOptionsDrawer.openTriggered() } - if (fileName !== "") { - PageController.showBusyIndicator(true) - let result = ApiConfigsController.exportNativeConfig(countryCode, fileName) - PageController.showBusyIndicator(false) + issueConfig(countryCode) + } + } + + DividerType {} + } + } + + DrawerType2 { + id: moreOptionsDrawer + + property string countryName + property string countryCode + + anchors.fill: parent + expandedHeight: parent.height * 0.4375 + + expandedStateContent: Item { + implicitHeight: moreOptionsDrawer.expandedHeight + + BackButtonType { + id: moreOptionsDrawerBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + moreOptionsDrawer.closeTriggered() + } + } + + FlickableType { + anchors.top: moreOptionsDrawerBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + contentHeight: moreOptionsDrawerContent.height + + ColumnLayout { + id: moreOptionsDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Configuration file ") + moreOptionsDrawer.countryName + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Create a new") + descriptionText: qsTr("The previously created one will stop working") - if (result) { - PageController.showNotificationMessage(qsTr("Config file saved")) + clickedFunction: function() { + showQuestion(true, moreOptionsDrawer.countryCode, moreOptionsDrawer.countryName) } } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + text: qsTr("Revoke the current configuration file") + + clickedFunction: function() { + showQuestion(false, moreOptionsDrawer.countryCode, moreOptionsDrawer.countryName) + } + } + + DividerType {} } } + } + } - DividerType {} + function issueConfig(countryCode) { + var fileName = "" + if (GC.isMobile()) { + fileName = countryCode + configExtension + } else { + fileName = SystemController.getFileName(configCaption, + qsTr("Config files (*" + configExtension + ")"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + countryCode, + true, + configExtension) } + if (fileName !== "") { + PageController.showBusyIndicator(true) + let result = ApiConfigsController.exportNativeConfig(countryCode, fileName) + if (result) { + ApiSettingsController.getAccountInfo() + } + + PageController.showBusyIndicator(false) + + if (result) { + PageController.showNotificationMessage(qsTr("Config file saved")) + } + } + } + + function revokeConfig(countryCode) { + PageController.showBusyIndicator(true) + let result = ApiConfigsController.revokeNativeConfig(countryCode) + if (result) { + ApiSettingsController.getAccountInfo() + } + PageController.showBusyIndicator(false) + + if (result) { + PageController.showNotificationMessage(qsTr("The config has been revoked")) + } + } + + function showQuestion(isConfigIssue, countryCode, countryName) { + var headerText = qsTr("Revoke the actual %1 configuration file?").arg(countryName) + var descriptionText = qsTr("The previously created file will no longer be valid. It will not be possible to connect using it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (isConfigIssue) { + issueConfig(countryCode) + } else { + revokeConfig(countryCode) + } + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 75c1e893d..000edba77 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -257,10 +257,45 @@ PageType { } } var noButtonFunction = function() { - if (!GC.isMobile()) { - removeButton.forceActiveFocus() + } + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + BasicButtonType { + id: revokeButton + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 24 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + textColor: AmneziaStyle.color.vibrantRed + + text: qsTr("Deactivate the subscription on this device") + + clickedFunc: function() { + var headerText = qsTr("Deactivate the subscription on this device?") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("The next time the “Connect” button is pressed, the device will be activated again")) + } else { + PageController.showBusyIndicator(true) + if (ApiConfigsController.deactivateDevice()) { + ApiSettingsController.getAccountInfo() + } + PageController.showBusyIndicator(false) } } + var noButtonFunction = function() { + } showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } @@ -295,9 +330,6 @@ PageType { } } var noButtonFunction = function() { - if (!GC.isMobile()) { - removeButton.forceActiveFocus() - } } showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) From 5f6cd282d320fd52236984370372f28f4b45b47d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 21 Feb 2025 14:14:22 +0700 Subject: [PATCH 15/23] chore: added links to instructions --- client/ui/qml/Pages2/PageSettingsApiInstructions.qml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml index d1c8231ed..b73d8e167 100644 --- a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -20,21 +20,21 @@ PageType { id: windows readonly property string title: qsTr("Windows") - readonly property string link: qsTr("") + readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#windows") } QtObject { id: macos readonly property string title: qsTr("macOS") - readonly property string link: qsTr("") + readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#macos") } QtObject { id: android readonly property string title: qsTr("Android") - readonly property string link: qsTr("") + readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#android") } QtObject { @@ -48,21 +48,21 @@ PageType { id: ios readonly property string title: qsTr("iOS") - readonly property string link: qsTr("") + readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#ios") } QtObject { id: linux readonly property string title: qsTr("Linux") - readonly property string link: qsTr("") + readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#linux") } QtObject { id: routers readonly property string title: qsTr("Routers") - readonly property string link: qsTr("") + readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#routers") } property list instructionsModel: [ From 48980c486efd348e50a7b48d1987f0bbba1fbb48 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 21 Feb 2025 14:15:03 +0700 Subject: [PATCH 16/23] chore: fixed qr code with vpnkey processing --- client/ui/controllers/importController.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 178efc16a..1bba0e8a3 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -155,9 +155,9 @@ bool ImportController::extractConfigFromData(QString data) if (m_configType == ConfigTypes::Invalid) { config.replace("vpn://", ""); QByteArray ba = QByteArray::fromBase64(config.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QByteArray ba_uncompressed = qUncompress(ba); - if (!ba_uncompressed.isEmpty()) { - ba = ba_uncompressed; + QByteArray baUncompressed = qUncompress(ba); + if (!baUncompressed.isEmpty()) { + ba = baUncompressed; } config = ba; @@ -228,6 +228,21 @@ bool ImportController::extractConfigFromQr(const QByteArray &data) return true; } + m_configType = checkConfigFormat(data); + if (m_configType == ConfigTypes::Invalid) { + QByteArray ba = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QByteArray baUncompressed = qUncompress(ba); + + if (!baUncompressed.isEmpty()) { + ba = baUncompressed; + } + + if (!ba.isEmpty()) { + m_config = QJsonDocument::fromJson(ba).object(); + return true; + } + } + return false; } From 8afe50cd87975cf7b1a5771c4c40bc73cae35cc3 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 21 Feb 2025 14:15:23 +0700 Subject: [PATCH 17/23] chore: fixed native config post processing --- client/ui/controllers/api/apiConfigsController.cpp | 3 +++ client/ui/qml/Pages2/PageSettingsApiServerInfo.qml | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index cd3c9f1bb..8c6d53c20 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -87,6 +87,7 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object(); QString nativeConfig = jsonConfig.value(configKey::config).toString(); + nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); SystemController::saveFile(fileName, nativeConfig); return true; @@ -124,6 +125,8 @@ void ApiConfigsController::prepareVpnKeyExport() auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString(); + vpnKey.replace("vpn://", ""); + m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(vpnKey.toUtf8()); emit vpnKeyExportReady(); diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 000edba77..befe7370a 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -217,7 +217,7 @@ PageType { LabelWithButtonType { Layout.fillWidth: true - text: qsTr("How to connect on another devicey") + text: qsTr("How to connect on another device") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -266,11 +266,12 @@ PageType { BasicButtonType { id: revokeButton Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 24 Layout.bottomMargin: 16 Layout.leftMargin: 8 implicitHeight: 32 + visible: footer.isVisibleForAmneziaFree + defaultColor: "transparent" hoveredColor: AmneziaStyle.color.translucentWhite pressedColor: AmneziaStyle.color.sheerWhite From 6a424e98588b882ac9d246cdfe5972b180f1a87f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 21 Feb 2025 14:16:40 +0700 Subject: [PATCH 18/23] chore: added link to android tv instruction --- client/ui/qml/Pages2/PageSettingsApiInstructions.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml index b73d8e167..70a0df74a 100644 --- a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -41,7 +41,7 @@ PageType { id: androidTv readonly property string title: qsTr("AndroidTV") - readonly property string link: qsTr("") + readonly property string link: qsTr("https://docs.amnezia.org/ru/documentation/instructions/android_tv_connect/") } QtObject { From d19017f87b991479e2f312404500937bf68087b1 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 22 Feb 2025 14:42:09 +0700 Subject: [PATCH 19/23] chore: minor ui fixes --- client/amnezia_application.cpp | 10 ++++++++++ client/amnezia_application.h | 7 +++---- client/core/api/apiDefs.h | 1 + client/core/api/apiUtils.cpp | 12 ++++++++---- client/core/controllers/gatewayController.cpp | 12 +++++++----- .../ui/controllers/api/apiConfigsController.cpp | 17 ++++++++++++++--- .../ui/controllers/api/apiConfigsController.h | 4 ++++ .../controllers/api/apiSettingsController.cpp | 15 ++++++++++++++- .../ui/controllers/api/apiSettingsController.h | 2 +- client/ui/models/api/apiCountryModel.cpp | 9 ++++++++- client/ui/qml/Components/ServersListView.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 2 +- .../PageSettingsApiAvailableCountries.qml | 2 +- .../qml/Pages2/PageSettingsApiNativeConfigs.qml | 10 +++++----- .../ui/qml/Pages2/PageSettingsApiServerInfo.qml | 12 +++++++++++- .../ui/qml/Pages2/PageSettingsServersList.qml | 2 +- 16 files changed, 90 insertions(+), 29 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 8dea8c0a4..f32d525af 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -202,3 +202,13 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const { return m_engine; } + +QNetworkAccessManager *AmneziaApplication::networkManager() +{ + return m_nam; +} + +QClipboard *AmneziaApplication::getClipboard() +{ + return this->clipboard(); +} diff --git a/client/amnezia_application.h b/client/amnezia_application.h index b967f160f..ea5f6f521 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -11,6 +11,7 @@ #else #include #endif +#include #include "core/controllers/coreController.h" #include "settings.h" @@ -41,10 +42,8 @@ class AmneziaApplication : public AMNEZIA_BASE_CLASS #endif QQmlApplicationEngine *qmlEngine() const; - QNetworkAccessManager *manager() - { - return m_nam; - } + QNetworkAccessManager *networkManager(); + QClipboard *getClipboard(); private: QQmlApplicationEngine *m_engine {}; diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h index 2892f90b8..41dd80bab 100644 --- a/client/core/api/apiDefs.h +++ b/client/core/api/apiDefs.h @@ -24,6 +24,7 @@ namespace apiDefs constexpr QLatin1String apiConfig("api_config"); constexpr QLatin1String stackType("stack_type"); + constexpr QLatin1String serviceType("service_type"); constexpr QLatin1String vpnKey("vpn_key"); diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp index 088fdba1a..6166c5120 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/api/apiUtils.cpp @@ -27,15 +27,19 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec case apiDefs::ConfigSource::Telegram: { }; case apiDefs::ConfigSource::AmneziaGateway: { - constexpr QLatin1String premium("prem"); - constexpr QLatin1String free("free"); + constexpr QLatin1String stackPremium("prem"); + constexpr QLatin1String stackFree("free"); + + constexpr QLatin1String servicePremium("amnezia-premium"); + constexpr QLatin1String serviceFree("amnezia-free"); auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject(); auto stackType = apiConfigObject.value(apiDefs::key::stackType).toString(); + auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString(); - if (stackType == premium) { + if (serviceType == servicePremium || stackType == stackPremium) { return apiDefs::ConfigType::AmneziaPremiumV2; - } else if (stackType == free) { + } else if (serviceType == serviceFree || stackType == stackFree) { return apiDefs::ConfigType::AmneziaFreeV3; } } diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index d7a4b018a..41fe61f8a 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -47,7 +47,7 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo request.setUrl(QString(endpoint).arg(m_gatewayEndpoint)); QNetworkReply *reply; - reply = amnApp->manager()->get(request); + reply = amnApp->networkManager()->get(request); QEventLoop wait; QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); @@ -61,7 +61,7 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) { auto requestFunction = [&request, &responseBody](const QString &url) { request.setUrl(url); - return amnApp->manager()->get(request); + return amnApp->networkManager()->get(request); }; auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply, @@ -137,7 +137,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64()); requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64()); - QNetworkReply *reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson()); + QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson()); QEventLoop wait; connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); @@ -151,7 +151,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, false)) { auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) { request.setUrl(url); - return amnApp->manager()->post(request, QJsonDocument(requestBody).toJson()); + return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson()); }; auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt, @@ -205,7 +205,7 @@ QStringList GatewayController::getProxyUrls() for (const auto &proxyStorageUrl : proxyStorageUrl) { request.setUrl(proxyStorageUrl); - reply = amnApp->manager()->get(request); + reply = amnApp->networkManager()->get(request); connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); @@ -256,6 +256,8 @@ QStringList GatewayController::getProxyUrls() bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key, const QByteArray &iv, const QByteArray &salt) { + qDebug() << reply->error(); + qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) { qDebug() << "Timeout occurred"; return true; diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 8c6d53c20..0fe304c2a 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -1,13 +1,13 @@ #include "apiConfigsController.h" #include +#include #include "amnezia_application.h" #include "configurators/wireguard_configurator.h" #include "core/api/apiDefs.h" #include "core/api/apiUtils.h" #include "core/controllers/gatewayController.h" -#include "core/networkUtilities.h" #include "core/qrCodeUtils.h" #include "ui/controllers/systemController.h" #include "version.h" @@ -76,7 +76,6 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, apiPayload[configKey::serverCountryCode] = serverCountryCode; apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); - apiPayload[configKey::uuid] = m_settings->getInstallationUuid(true); QByteArray responseBody; ErrorCode errorCode = gatewayController.post(QString("%1v1/native_config"), apiPayload, responseBody); @@ -124,6 +123,7 @@ void ApiConfigsController::prepareVpnKeyExport() auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString(); + m_vpnKey = vpnKey; vpnKey.replace("vpn://", ""); @@ -132,6 +132,12 @@ void ApiConfigsController::prepareVpnKeyExport() emit vpnKeyExportReady(); } +void ApiConfigsController::copyVpnKeyToClipboard() +{ + auto clipboard = amnApp->getClipboard(); + clipboard->setText(m_vpnKey); +} + bool ApiConfigsController::fillAvailableServices() { GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); @@ -288,7 +294,7 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) QByteArray requestBody = QJsonDocument(apiPayload).toJson(); - QNetworkReply *reply = amnApp->manager()->post(request, requestBody); + QNetworkReply *reply = amnApp->networkManager()->post(request, requestBody); QEventLoop wait; connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); @@ -477,3 +483,8 @@ int ApiConfigsController::getQrCodesCount() { return m_qrCodes.size(); } + +QString ApiConfigsController::getVpnKey() +{ + return m_vpnKey; +} diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h index f83549428..26b029780 100644 --- a/client/ui/controllers/api/apiConfigsController.h +++ b/client/ui/controllers/api/apiConfigsController.h @@ -16,12 +16,14 @@ class ApiConfigsController : public QObject Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY vpnKeyExportReady) Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY vpnKeyExportReady) + Q_PROPERTY(QString vpnKey READ getVpnKey NOTIFY vpnKeyExportReady) public slots: bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName); bool revokeNativeConfig(const QString &serverCountryCode); // bool exportVpnKey(const QString &fileName); void prepareVpnKeyExport(); + void copyVpnKeyToClipboard(); bool fillAvailableServices(); bool importServiceFromGateway(); @@ -58,8 +60,10 @@ public slots: QList getQrCodes(); int getQrCodesCount(); + QString getVpnKey(); QList m_qrCodes; + QString m_vpnKey; QSharedPointer m_serversModel; QSharedPointer m_apiServicesModel; diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp index 5f436436f..53b6e5a8d 100644 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -1,5 +1,8 @@ #include "apiSettingsController.h" +#include +#include + #include "core/api/apiUtils.h" #include "core/controllers/gatewayController.h" @@ -35,8 +38,14 @@ ApiSettingsController::~ApiSettingsController() { } -bool ApiSettingsController::getAccountInfo() +bool ApiSettingsController::getAccountInfo(bool reload) { + if (reload) { + QEventLoop wait; + QTimer::singleShot(1000, &wait, &QEventLoop::quit); + wait.exec(); + } + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs); auto processedIndex = m_serversModel->getProcessedServerIndex(); @@ -62,6 +71,10 @@ bool ApiSettingsController::getAccountInfo() QJsonObject accountInfo = QJsonDocument::fromJson(responseBody).object(); m_apiAccountInfoModel->updateModel(accountInfo, serverConfig); + if (reload) { + updateApiCountryModel(); + } + return true; } diff --git a/client/ui/controllers/api/apiSettingsController.h b/client/ui/controllers/api/apiSettingsController.h index 5374e8997..e12e232ab 100644 --- a/client/ui/controllers/api/apiSettingsController.h +++ b/client/ui/controllers/api/apiSettingsController.h @@ -17,7 +17,7 @@ class ApiSettingsController : public QObject ~ApiSettingsController(); public slots: - bool getAccountInfo(); + bool getAccountInfo(bool reload); void updateApiCountryModel(); signals: diff --git a/client/ui/models/api/apiCountryModel.cpp b/client/ui/models/api/apiCountryModel.cpp index a43184b43..4ded6fed4 100644 --- a/client/ui/models/api/apiCountryModel.cpp +++ b/client/ui/models/api/apiCountryModel.cpp @@ -8,6 +8,8 @@ namespace { Logger logger("ApiCountryModel"); + + constexpr QLatin1String countryConfig("country_config"); } ApiCountryModel::ApiCountryModel(QObject *parent) : QAbstractListModel(parent) @@ -27,7 +29,7 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const CountryInfo countryInfo = m_countries.at(index.row()); IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.value(countryInfo.countryCode); - bool isIssued = !issuedConfigInfo.lastDownloaded.isEmpty(); + bool isIssued = issuedConfigInfo.sourceType == countryConfig; switch (role) { case CountryCodeRole: { @@ -73,10 +75,15 @@ void ApiCountryModel::updateIssuedConfigsInfo(const QJsonArray &issuedConfigs) { beginResetModel(); + m_issuedConfigs.clear(); for (int i = 0; i < issuedConfigs.size(); i++) { IssuedConfigInfo issuedConfigInfo; QJsonObject issuedConfigObject = issuedConfigs.at(i).toObject(); + if (issuedConfigObject.value(apiDefs::key::sourceType).toString() != countryConfig) { + continue; + } + issuedConfigInfo.installationUuid = issuedConfigObject.value(apiDefs::key::installationUuid).toString(); issuedConfigInfo.workerLastUpdated = issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString(); issuedConfigInfo.lastDownloaded = issuedConfigObject.value(apiDefs::key::lastDownloaded).toString(); diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 3a6792acd..d0567a8c6 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -116,7 +116,7 @@ ListView { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) } else { PageController.showBusyIndicator(true) - let result = ApiSettingsController.getAccountInfo() + let result = ApiSettingsController.getAccountInfo(false) PageController.showBusyIndicator(false) if (!result) { return diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d189044dc..f7233a89e 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -303,7 +303,7 @@ PageType { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) } else { PageController.showBusyIndicator(true) - let result = ApiSettingsController.getAccountInfo() + let result = ApiSettingsController.getAccountInfo(false) PageController.showBusyIndicator(false) if (!result) { return diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index dfbc70b45..f70b646b4 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -93,7 +93,7 @@ PageType { actionButtonFunction: function() { PageController.showBusyIndicator(true) - let result = ApiSettingsController.getAccountInfo() + let result = ApiSettingsController.getAccountInfo(false) PageController.showBusyIndicator(false) if (!result) { return diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml index b2ab2c058..7fa2b8f4c 100644 --- a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -54,7 +54,6 @@ PageType { width: listView.width LabelWithButtonType { - id: telegramButton Layout.fillWidth: true Layout.topMargin: 6 @@ -67,8 +66,9 @@ PageType { moreOptionsDrawer.countryName = countryName moreOptionsDrawer.countryCode = countryCode moreOptionsDrawer.openTriggered() + } else { + issueConfig(countryCode) } - issueConfig(countryCode) } } @@ -166,11 +166,10 @@ PageType { PageController.showBusyIndicator(true) let result = ApiConfigsController.exportNativeConfig(countryCode, fileName) if (result) { - ApiSettingsController.getAccountInfo() + ApiSettingsController.getAccountInfo(true) } PageController.showBusyIndicator(false) - if (result) { PageController.showNotificationMessage(qsTr("Config file saved")) } @@ -181,7 +180,7 @@ PageType { PageController.showBusyIndicator(true) let result = ApiConfigsController.revokeNativeConfig(countryCode) if (result) { - ApiSettingsController.getAccountInfo() + ApiSettingsController.getAccountInfo(true) } PageController.showBusyIndicator(false) @@ -202,6 +201,7 @@ PageType { } else { revokeConfig(countryCode) } + moreOptionsDrawer.closeTriggered() } var noButtonFunction = function() { } diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index befe7370a..7080ad263 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -128,7 +128,17 @@ PageType { width: listView.width spacing: 0 + Connections { + target: ApiAccountInfoModel + + function onModelReset() { + delegateItem.rightText = ApiAccountInfoModel.data(contentKey) + } + } + LabelWithImageType { + id: delegateItem + Layout.fillWidth: true Layout.margins: 16 @@ -290,7 +300,7 @@ PageType { } else { PageController.showBusyIndicator(true) if (ApiConfigsController.deactivateDevice()) { - ApiSettingsController.getAccountInfo() + ApiSettingsController.getAccountInfo(true) } PageController.showBusyIndicator(false) } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index fcfcd1145..554b6cbbc 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -96,7 +96,7 @@ PageType { if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { PageController.showBusyIndicator(true) - let result = ApiSettingsController.getAccountInfo() + let result = ApiSettingsController.getAccountInfo(false) PageController.showBusyIndicator(false) if (!result) { return From 19fcddfdaf141129f812133c98432b09ae178eaa Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 23 Feb 2025 14:26:04 +0700 Subject: [PATCH 20/23] chore: added 404 handling for revoke configs - added revoke before remove api server for premium v2 --- client/core/api/apiUtils.cpp | 3 +++ client/core/defs.h | 1 + client/core/errorstrings.cpp | 1 + .../ui/controllers/api/apiConfigsController.cpp | 15 ++++++++++++--- .../ui/qml/Pages2/PageSettingsApiServerInfo.qml | 9 ++++++--- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp index 6166c5120..9f518b52b 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/api/apiUtils.cpp @@ -57,6 +57,7 @@ apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigO amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply) { const int httpStatusCodeConflict = 409; + const int httpStatusCodeNotFound = 404; if (!sslErrors.empty()) { qDebug().noquote() << sslErrors; @@ -75,6 +76,8 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList &ssl qDebug() << httpStatusCode; if (httpStatusCode == httpStatusCodeConflict) { return amnezia::ErrorCode::ApiConfigLimitError; + } else if (httpStatusCode == httpStatusCodeNotFound) { + return amnezia::ErrorCode::ApiNotFoundError; } return amnezia::ErrorCode::ApiConfigDownloadError; } diff --git a/client/core/defs.h b/client/core/defs.h index 330ebc48a..6c85c65d2 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -110,6 +110,7 @@ namespace amnezia ApiConfigDecryptionError = 1106, ApiServicesMissingError = 1107, ApiConfigLimitError = 1108, + ApiNotFoundError = 1109, // QFile errors OpenError = 1200, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index c45b1eafb..2b9182cf7 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -67,6 +67,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break; case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break; case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break; + case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break; // QFile errors case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 0fe304c2a..27813f9c7 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -110,7 +110,7 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode) QByteArray responseBody; ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_native_config"), apiPayload, responseBody); - if (errorCode != ErrorCode::NoError) { + if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { emit errorOccurred(errorCode); return false; } @@ -323,9 +323,14 @@ bool ApiConfigsController::deactivateDevice() { GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); - auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); + auto serverIndex = m_serversModel->getProcessedServerIndex(); + auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) { + return true; + } + QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); @@ -338,10 +343,14 @@ bool ApiConfigsController::deactivateDevice() QByteArray responseBody; ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); - if (errorCode != ErrorCode::NoError) { + if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { emit errorOccurred(errorCode); return false; } + + serverConfigObject.remove(config_key::containers); + m_serversModel->editServer(serverConfigObject, serverIndex); + return true; } diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 7080ad263..44ed6f4be 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -164,7 +164,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 - visible: footer.isVisibleForAmneziaFree + visible: false //footer.isVisibleForAmneziaFree text: qsTr("Subscription key") rightImageSource: "qrc:/images/controls/chevron-right.svg" @@ -187,11 +187,12 @@ PageType { } DividerType { - visible: footer.isVisibleForAmneziaFree + visible: false //footer.isVisibleForAmneziaFree } LabelWithButtonType { Layout.fillWidth: true + Layout.topMargin: 32 visible: footer.isVisibleForAmneziaFree @@ -336,7 +337,9 @@ PageType { PageController.showNotificationMessage(qsTr("Cannot remove server during active connection")) } else { PageController.showBusyIndicator(true) - InstallController.removeProcessedServer() + if (ApiConfigsController.deactivateDevice()) { + InstallController.removeProcessedServer() + } PageController.showBusyIndicator(false) } } From 2b1ec9c693257dfbf0a5f40e5bd0c3b66a1014fd Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 23 Feb 2025 14:39:18 +0700 Subject: [PATCH 21/23] chore: added log to see proxy decrypt errors --- client/core/controllers/gatewayController.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index 41fe61f8a..61c3d5949 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -240,7 +240,7 @@ QStringList GatewayController::getProxyUrls() } } catch (...) { Utils::logException(); - qCritical() << "error loading private key from environment variables or decrypting payload"; + qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody; return {}; } @@ -256,8 +256,6 @@ QStringList GatewayController::getProxyUrls() bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key, const QByteArray &iv, const QByteArray &salt) { - qDebug() << reply->error(); - qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) { qDebug() << "Timeout occurred"; return true; From abd7fdd19cf6fbb94d3a5f46ae5ab3a948fcbb84 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 24 Feb 2025 13:39:03 +0700 Subject: [PATCH 22/23] chore: minor ui fix --- .../qml/Pages2/PageSettingsApiAvailableCountries.qml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index f70b646b4..43fbb160f 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -44,19 +44,11 @@ PageType { } } - ListView { + ListViewType { id: menuContent - property bool isFocusable: true - anchors.fill: parent - ScrollBar.vertical: ScrollBarType {} - - clip: true - reuseItems: true - snapMode: ListView.SnapToItem - model: ApiCountryModel currentIndex: 0 From c28e1b468a03c671d6e8f1fb20ddca838eaa913b Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 24 Feb 2025 13:41:50 +0700 Subject: [PATCH 23/23] chore: bump version --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22141c9dd..946162f8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.3.3 +project(${PROJECT} VERSION 4.8.4.0 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 2076) +set(APP_ANDROID_VERSION_CODE 2077) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux")