From 85fa1ad8b10df65c28e18392044c037f606a3d7a Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:22:14 +0400 Subject: [PATCH 01/15] added check for the root user and the wheel group --- client/core/controllers/serverController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index b6795a01..dea1a40c 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -768,7 +768,7 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo); ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); - if (!stdOut.contains("sudo")) + if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel")) return ErrorCode::ServerUserNotInSudo; return error; From 076b076cd933f671e07f22b2b9fc91e55db79a16 Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Sun, 1 Dec 2024 13:51:03 +0400 Subject: [PATCH 02/15] Verifying the server user to work with sudo (#1254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * checking that the username is root Changing the mechanism for checking that the username is root * wheel group check (#1198) Checking if the user is included in the wheel group * Checking requirements in script (#1210) * Checking requirements in script Checking requirements for sudo users in script * Adding error handling Adding error handling in the server controller for: Sudo package is not pre-installed for sudo users. Server user or associated group is not listed in the sudoers file. Server user password required * adding error codes * added extended error descriptions * checking sudo permission for root Сhecking sudo permission for root. Сhecking and redefining the system language. * Username if whoami returns an error Сommand to use home directory name if whoami returns error or is missing. * Correcting text error Correction of the text of the extended description of the package manager error * Updating translations * Optimization check_user_in_sudo.sh * exceptions for missing uname * output only for groups sudo or wheel --- client/core/controllers/serverController.cpp | 10 ++++++---- client/core/defs.h | 3 +++ client/core/errorstrings.cpp | 7 +++++-- client/server_scripts/check_user_in_sudo.sh | 6 ++++-- client/server_scripts/prepare_host.sh | 2 +- client/translations/amneziavpn_ar_EG.ts | 6 +++--- client/translations/amneziavpn_fa_IR.ts | 8 ++++---- client/translations/amneziavpn_hi_IN.ts | 8 ++++---- client/translations/amneziavpn_my_MM.ts | 8 ++++---- client/translations/amneziavpn_ru_RU.ts | 6 +++--- client/translations/amneziavpn_uk_UA.ts | 8 ++++---- client/translations/amneziavpn_ur_PK.ts | 6 +++--- client/translations/amneziavpn_zh_CN.ts | 8 ++++---- 13 files changed, 48 insertions(+), 38 deletions(-) diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index dea1a40c..17f02383 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -751,10 +751,6 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container) { - if (credentials.userName == "root") { - return ErrorCode::NoError; - } - QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; @@ -770,6 +766,12 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel")) return ErrorCode::ServerUserNotInSudo; + if (stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found")) + return ErrorCode::SudoPackageIsNotPreinstalled; + if (stdOut.contains("sudoers")) + return ErrorCode::ServerUserNotAllowedInSudoers; + if (stdOut.contains("password is required")) + return ErrorCode::ServerUserPasswordRequired; return error; } diff --git a/client/core/defs.h b/client/core/defs.h index d00d347b..ea8e5b6d 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -56,6 +56,9 @@ namespace amnezia ServerCancelInstallation = 204, ServerUserNotInSudo = 205, ServerPacketManagerError = 206, + SudoPackageIsNotPreinstalled = 207, + ServerUserNotAllowedInSudoers = 208, + ServerUserPasswordRequired = 209, // Ssh connection errors SshRequestDeniedError = 300, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 49534606..b5594d52 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -19,8 +19,11 @@ QString errorString(ErrorCode code) { case(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break; case(ErrorCode::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break; case(ErrorCode::ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break; - case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break; - case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break; + case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user is not a member of the sudo group"); break; + case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Package manager error"); break; + case(ErrorCode::SudoPackageIsNotPreinstalled): errorMessage = QObject::tr("The sudo package is not pre-installed"); break; + case(ErrorCode::ServerUserNotAllowedInSudoers): errorMessage = QObject::tr("The user is not allowed in sudoers"); break; + case(ErrorCode::ServerUserPasswordRequired): errorMessage = QObject::tr("The user's password is required"); break; // Libssh errors case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break; diff --git a/client/server_scripts/check_user_in_sudo.sh b/client/server_scripts/check_user_in_sudo.sh index e7ee953c..0f362394 100644 --- a/client/server_scripts/check_user_in_sudo.sh +++ b/client/server_scripts/check_user_in_sudo.sh @@ -1,2 +1,4 @@ -CUR_USER=$(whoami);\ -groups $CUR_USER \ No newline at end of file +CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ +echo $LANG | grep -qE "en_US.UTF-8|^C.UTF-8" || export LC_ALL=C;\ +if [ "$CUR_USER" = "root" ]; then command -v sudo > /dev/null 2>&1 && sudo -nu $CUR_USER sudo -n uname > /dev/null;\ +else groups $CUR_USER | grep -E "\|\" && sudo -nu $CUR_USER sudo -n uname > /dev/null; fi diff --git a/client/server_scripts/prepare_host.sh b/client/server_scripts/prepare_host.sh index c6defdb0..d06bdafb 100644 --- a/client/server_scripts/prepare_host.sh +++ b/client/server_scripts/prepare_host.sh @@ -1,4 +1,4 @@ -CUR_USER=$(whoami);\ +CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ sudo mkdir -p $DOCKERFILE_FOLDER;\ sudo chown $CUR_USER $DOCKERFILE_FOLDER;\ if ! sudo docker network ls | grep -q amnezia-dns-net; then sudo docker network create \ diff --git a/client/translations/amneziavpn_ar_EG.ts b/client/translations/amneziavpn_ar_EG.ts index e176d8eb..7a092a74 100644 --- a/client/translations/amneziavpn_ar_EG.ts +++ b/client/translations/amneziavpn_ar_EG.ts @@ -3254,8 +3254,8 @@ Already installed containers were found on the server. All installed containers - The user does not have permission to use sudo - ليس لدي المستخدم الصلحيات لأستخدام sudo + The user is not a member of the sudo group + المستخدم ليس عضوًا في مجموعة sudo @@ -3319,7 +3319,7 @@ Already installed containers were found on the server. All installed containers - Server error: Packet manager error + Server error: Package manager error خطأ في الخادم: خطأ في مدير الحزم diff --git a/client/translations/amneziavpn_fa_IR.ts b/client/translations/amneziavpn_fa_IR.ts index 6cd78e77..80de1287 100644 --- a/client/translations/amneziavpn_fa_IR.ts +++ b/client/translations/amneziavpn_fa_IR.ts @@ -3388,8 +3388,8 @@ It's okay as long as it's from someone you trust. - The user does not have permission to use sudo - The user does not have permission to use sudo + The user is not a member of the sudo group + کاربر عضو گروه sudo نیست @@ -3510,8 +3510,8 @@ It's okay as long as it's from someone you trust. - Server error: Packet manager error - Server error: Packet manager error + Server error: Package manager error + خطای سرور: خطای مدیر بسته diff --git a/client/translations/amneziavpn_hi_IN.ts b/client/translations/amneziavpn_hi_IN.ts index ab459b7c..5255fce3 100644 --- a/client/translations/amneziavpn_hi_IN.ts +++ b/client/translations/amneziavpn_hi_IN.ts @@ -3354,13 +3354,13 @@ Already installed containers were found on the server. All installed containers - The user does not have permission to use sudo - उपयोगकर्ता के पास sudo का उपयोग करने की अनुमति नहीं है + The user is not a member of the sudo group + उपयोगकर्ता sudo समूह का सदस्य नहीं है - Server error: Packet manager error - सर्वर त्रुटि: पैकेट प्रबंधक त्रुटि + Server error: Package manager error + सर्वर त्रुटि: पैकेज प्रबंधक त्रुटि diff --git a/client/translations/amneziavpn_my_MM.ts b/client/translations/amneziavpn_my_MM.ts index 3e964cc9..101abae5 100644 --- a/client/translations/amneziavpn_my_MM.ts +++ b/client/translations/amneziavpn_my_MM.ts @@ -3250,8 +3250,8 @@ Already installed containers were found on the server. All installed containers - The user does not have permission to use sudo - ဤအသုံးပြုသူသည် sudo ကိုအသုံးပြုရန်ခွင့်ပြုချက်မရှိပါ + The user is not a member of the sudo group + ဤအသုံးပြုသူသည် sudo အဖွဲ့၏ အဖွဲ့ဝင်မဟုတ်ပါ @@ -3315,8 +3315,8 @@ Already installed containers were found on the server. All installed containers - Server error: Packet manager error - ဆာဗာ မှားယွင်းမှု: Packet Manager မှားယွင်းမှု + Server error: Package manager error + ဆာဗာ အမှား- Package manager အမှား diff --git a/client/translations/amneziavpn_ru_RU.ts b/client/translations/amneziavpn_ru_RU.ts index 2fb21259..085e868a 100644 --- a/client/translations/amneziavpn_ru_RU.ts +++ b/client/translations/amneziavpn_ru_RU.ts @@ -3524,12 +3524,12 @@ and will not be shared or disclosed to the Amnezia or any third parties - The user does not have permission to use sudo - У пользователя нет прав на использование sudo + The user is not a member of the sudo group + Пользователь не входит в группу sudo - Server error: Packet manager error + Server error: Package manager error Ошибка сервера: ошибка менеджера пакетов diff --git a/client/translations/amneziavpn_uk_UA.ts b/client/translations/amneziavpn_uk_UA.ts index c7195119..c0d3e439 100644 --- a/client/translations/amneziavpn_uk_UA.ts +++ b/client/translations/amneziavpn_uk_UA.ts @@ -3620,13 +3620,13 @@ and will not be shared or disclosed to the Amnezia or any third parties - The user does not have permission to use sudo - The user does not have permission to use sudo + The user is not a member of the sudo group + Користувач не входить до групи sudo - Server error: Packet manager error - + Server error: Package manager error + Помилка сервера: помилка менеджера пакетів diff --git a/client/translations/amneziavpn_ur_PK.ts b/client/translations/amneziavpn_ur_PK.ts index cf445bfa..93057609 100644 --- a/client/translations/amneziavpn_ur_PK.ts +++ b/client/translations/amneziavpn_ur_PK.ts @@ -3353,8 +3353,8 @@ Already installed containers were found on the server. All installed containers - The user does not have permission to use sudo - صارف کو sudo استعمال کرنے کی اجازت نہیں ہے + The user is not a member of the sudo group + صارف sudo گروپ کا رکن نہیں ہے @@ -3418,7 +3418,7 @@ Already installed containers were found on the server. All installed containers - Server error: Packet manager error + Server error: Package manager error سرور خطا: پیکیج منیجر خطا diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 39b6bee0..0de2dace 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -3603,13 +3603,13 @@ and will not be shared or disclosed to the Amnezia or any third parties - The user does not have permission to use sudo - 用户没有root权限 + The user is not a member of the sudo group + 用户不是 sudo 组的成员 - Server error: Packet manager error - + Server error: Package manager error + 服务器错误:包管理器错误 From 62497024f96f93cc733d6db62d94de0a4fd7263f Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Sun, 8 Dec 2024 14:22:10 +0400 Subject: [PATCH 03/15] script simplification Simplifying the script for later adding an exception for root to the server controller --- client/server_scripts/check_user_in_sudo.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/server_scripts/check_user_in_sudo.sh b/client/server_scripts/check_user_in_sudo.sh index 0f362394..21b3cfab 100644 --- a/client/server_scripts/check_user_in_sudo.sh +++ b/client/server_scripts/check_user_in_sudo.sh @@ -1,4 +1,3 @@ CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ echo $LANG | grep -qE "en_US.UTF-8|^C.UTF-8" || export LC_ALL=C;\ -if [ "$CUR_USER" = "root" ]; then command -v sudo > /dev/null 2>&1 && sudo -nu $CUR_USER sudo -n uname > /dev/null;\ -else groups $CUR_USER | grep -E "\|\" && sudo -nu $CUR_USER sudo -n uname > /dev/null; fi +[ "$CUR_USER" = "root" ] || groups $CUR_USER | grep -E "\|\" && sudo -nu $CUR_USER sudo -n uname > /dev/null From 45fb4b0982fcd6265e76f0543e2d411cc11b0a59 Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Sun, 8 Dec 2024 14:31:23 +0400 Subject: [PATCH 04/15] exception for root Exception for root if sudo package is not installed --- client/core/controllers/serverController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index 17f02383..ba589da5 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -766,7 +766,7 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel")) return ErrorCode::ServerUserNotInSudo; - if (stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found")) + if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found")) return ErrorCode::SudoPackageIsNotPreinstalled; if (stdOut.contains("sudoers")) return ErrorCode::ServerUserNotAllowedInSudoers; From 2b85dafa1538b3805cf99c5466d322f5da5dc51d Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:31:19 +0400 Subject: [PATCH 05/15] =?UTF-8?q?=D1=81hanged=20extended=20description?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed extended description for: not allowed in sudoers. --- client/core/errorstrings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index b5594d52..9c847501 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -22,7 +22,7 @@ QString errorString(ErrorCode code) { case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user is not a member of the sudo group"); break; case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Package manager error"); break; case(ErrorCode::SudoPackageIsNotPreinstalled): errorMessage = QObject::tr("The sudo package is not pre-installed"); break; - case(ErrorCode::ServerUserNotAllowedInSudoers): errorMessage = QObject::tr("The user is not allowed in sudoers"); break; + case(ErrorCode::ServerUserNotAllowedInSudoers): errorMessage = QObject::tr("Action not allowed in sudoers"); break; case(ErrorCode::ServerUserPasswordRequired): errorMessage = QObject::tr("The user's password is required"); break; // Libssh errors From 055c8f7b668a11df474e1fe6cbf2de738c0c58ff Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:51:55 +0400 Subject: [PATCH 06/15] splitting the sudo check script The sudo command check script is split to check permissions correctly --- client/server_scripts/check_user_in_sudo.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/server_scripts/check_user_in_sudo.sh b/client/server_scripts/check_user_in_sudo.sh index 21b3cfab..9bb57772 100644 --- a/client/server_scripts/check_user_in_sudo.sh +++ b/client/server_scripts/check_user_in_sudo.sh @@ -1,3 +1,3 @@ CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ echo $LANG | grep -qE "en_US.UTF-8|^C.UTF-8" || export LC_ALL=C;\ -[ "$CUR_USER" = "root" ] || groups $CUR_USER | grep -E "\|\" && sudo -nu $CUR_USER sudo -n uname > /dev/null +[ "$CUR_USER" = "root" ] || groups $CUR_USER | grep -E "\|\" && sudo -nu $CUR_USER uname > /dev/null && sudo -n uname > /dev/null From c9423dd9afc2eaf0bc509571793a5d9d28fc653f Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:31:58 +0400 Subject: [PATCH 07/15] improved script readability --- client/server_scripts/check_user_in_sudo.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/server_scripts/check_user_in_sudo.sh b/client/server_scripts/check_user_in_sudo.sh index 9bb57772..7d748483 100644 --- a/client/server_scripts/check_user_in_sudo.sh +++ b/client/server_scripts/check_user_in_sudo.sh @@ -1,3 +1,5 @@ CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ echo $LANG | grep -qE "en_US.UTF-8|^C.UTF-8" || export LC_ALL=C;\ -[ "$CUR_USER" = "root" ] || groups $CUR_USER | grep -E "\|\" && sudo -nu $CUR_USER uname > /dev/null && sudo -n uname > /dev/null +if [ "$CUR_USER" = "root" ] || groups $CUR_USER | grep -E "\|\";\ +then sudo -nu $CUR_USER uname > /dev/null && sudo -n uname > /dev/null;\ +fi From ce4cb28e747ac63bb206900ebe9744a5edf571a2 Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Tue, 10 Dec 2024 05:44:55 +0400 Subject: [PATCH 08/15] improved script readability --- client/server_scripts/check_user_in_sudo.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/server_scripts/check_user_in_sudo.sh b/client/server_scripts/check_user_in_sudo.sh index 7d748483..27a9772d 100644 --- a/client/server_scripts/check_user_in_sudo.sh +++ b/client/server_scripts/check_user_in_sudo.sh @@ -1,5 +1,5 @@ CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ -echo $LANG | grep -qE "en_US.UTF-8|^C.UTF-8" || export LC_ALL=C;\ -if [ "$CUR_USER" = "root" ] || groups $CUR_USER | grep -E "\|\";\ -then sudo -nu $CUR_USER uname > /dev/null && sudo -n uname > /dev/null;\ +echo $LANG | grep -qE "^(en_US.UTF-8|C.UTF-8|C)$" || export LC_ALL=C;\ +if groups $CUR_USER | grep -E "\<(sudo|wheel)\>" || [ "$CUR_USER" = "root" ]; then \ + sudo -nu $CUR_USER uname > /dev/null && sudo -n uname > /dev/null;\ fi From 795ccaa08092f7150861491c38cdfc9bb23cbb22 Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Tue, 10 Dec 2024 06:13:08 +0400 Subject: [PATCH 09/15] added timestamp removal for sudo --- client/server_scripts/check_user_in_sudo.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/server_scripts/check_user_in_sudo.sh b/client/server_scripts/check_user_in_sudo.sh index 27a9772d..23059ad5 100644 --- a/client/server_scripts/check_user_in_sudo.sh +++ b/client/server_scripts/check_user_in_sudo.sh @@ -1,5 +1,5 @@ CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ echo $LANG | grep -qE "^(en_US.UTF-8|C.UTF-8|C)$" || export LC_ALL=C;\ if groups $CUR_USER | grep -E "\<(sudo|wheel)\>" || [ "$CUR_USER" = "root" ]; then \ - sudo -nu $CUR_USER uname > /dev/null && sudo -n uname > /dev/null;\ + sudo -K && sudo -nu $CUR_USER uname > /dev/null && sudo -n uname > /dev/null;\ fi From 7b2a4ea9220444af574e588213d7e7b6f7e7168d Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:14:29 +0400 Subject: [PATCH 10/15] improved script readability --- client/server_scripts/check_user_in_sudo.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/server_scripts/check_user_in_sudo.sh b/client/server_scripts/check_user_in_sudo.sh index 23059ad5..411c3b42 100644 --- a/client/server_scripts/check_user_in_sudo.sh +++ b/client/server_scripts/check_user_in_sudo.sh @@ -1,5 +1,5 @@ CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ -echo $LANG | grep -qE "^(en_US.UTF-8|C.UTF-8|C)$" || export LC_ALL=C;\ -if groups $CUR_USER | grep -E "\<(sudo|wheel)\>" || [ "$CUR_USER" = "root" ]; then \ +echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\ +if [ "$CUR_USER" = "root" ] || ( groups "$CUR_USER" | grep -E '\<(sudo|wheel)\>' ); then \ sudo -K && sudo -nu $CUR_USER uname > /dev/null && sudo -n uname > /dev/null;\ fi From db83555a0324b66af18e178fecb475f1670d7579 Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Thu, 16 Jan 2025 01:14:42 +0400 Subject: [PATCH 11/15] Checking server user permissions to use sudo Checking server user permissions to use sudo using a package manager. --- client/server_scripts/check_user_in_sudo.sh | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/client/server_scripts/check_user_in_sudo.sh b/client/server_scripts/check_user_in_sudo.sh index e7ee953c..a1413942 100644 --- a/client/server_scripts/check_user_in_sudo.sh +++ b/client/server_scripts/check_user_in_sudo.sh @@ -1,2 +1,11 @@ -CUR_USER=$(whoami);\ -groups $CUR_USER \ No newline at end of file +if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\ +elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\ +elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\ +elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\ +else pm="uname"; opt="-a";\ +fi;\ +CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ +echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\ +if [ "$CUR_USER" = "root" ] || ( groups "$CUR_USER" | grep -E '\<(sudo|wheel)\>' ); then \ + sudo -K && sudo -nu $CUR_USER $pm $opt > /dev/null && sudo -n $pm $opt > /dev/null;\ +fi From 0637c0f5966647e9788a6e41feddfa8c29cb48c1 Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Sat, 1 Mar 2025 14:26:05 +0400 Subject: [PATCH 12/15] polishing extra space removed --- client/server_scripts/check_user_in_sudo.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/server_scripts/check_user_in_sudo.sh b/client/server_scripts/check_user_in_sudo.sh index 1725b946..9fa356fd 100644 --- a/client/server_scripts/check_user_in_sudo.sh +++ b/client/server_scripts/check_user_in_sudo.sh @@ -4,8 +4,8 @@ elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\ elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\ else pm="uname"; opt="-a";\ fi;\ -CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ +CUR_USER=$(whoami 2>/dev/null || echo ~ | sed 's/.*\///');\ echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\ if [ "$CUR_USER" = "root" ] || ( groups "$CUR_USER" | grep -E '\<(sudo|wheel)\>' ); then \ sudo -K && sudo -nu $CUR_USER $pm $opt > /dev/null && sudo -n $pm $opt > /dev/null;\ -fi \ No newline at end of file +fi From 64552d608057908edb2b166c69668e73f790b551 Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Sat, 1 Mar 2025 14:48:08 +0400 Subject: [PATCH 13/15] polishing 2 extra space removed --- client/server_scripts/prepare_host.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/server_scripts/prepare_host.sh b/client/server_scripts/prepare_host.sh index d06bdafb..22a50e31 100644 --- a/client/server_scripts/prepare_host.sh +++ b/client/server_scripts/prepare_host.sh @@ -1,4 +1,4 @@ -CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ +CUR_USER=$(whoami 2>/dev/null || echo ~ | sed 's/.*\///');\ sudo mkdir -p $DOCKERFILE_FOLDER;\ sudo chown $CUR_USER $DOCKERFILE_FOLDER;\ if ! sudo docker network ls | grep -q amnezia-dns-net; then sudo docker network create \ From 059257fc587ae0b6f23ae4c59f270f2c61c32a3d Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Sat, 1 Mar 2025 16:08:52 +0400 Subject: [PATCH 14/15] Merge branch 'dev' into check_sudo_permissions (#1441) * refactoring: improved the performance of secure_settings * bugfix: fixed textFields on PageSetupWizardCredentials * bugfix: fixed scrolling by keys on PageSettingsApiServerInfo * chore: hide site links for ios (#1374) * chore: fixed log output with split tunneling info * chore: hide "open logs folder" button for mobule platforms * chore: fixed again log output with split tunneling info * chore: bump version * Install apparmor (#1379) Install apparmor * chore: returned the backup page for androidTV * Enable PFS for Windows IKEv2 * refactoring: moved api info pages from ServerInfo * refactoring: moved gateway interaction functions to a separate class * bugfix: fixed storeEndpoint parsing * chore: returned links for mobile platforms * Update VPN protocol descriptions * Update VPN description texts * feature: added pages for subscription settings feature * feature: added page for export api native configs * feature: added error handling and minor ui fixes * refactor: update ios build configuration to use automatic code signing and prebuilt OpenVPNAdapter framework * feat: remove OpenVPNAdapter submodule * feat: remove ios openvpn script and associated cmake configuration * Update README.md * Update README_RU.md * Update README.md fix link * feature: added share vpn key to subscription settings page * bugfix: fixed possible crush on android * add timeouts in ipc client init * apply timeouts only for Windows * apply format to file * refactoring: simplified the validity check of the config before connection - improved project structure * bugfix: fixed visability of share drawer * feature: added 409 error handling from server response * chore: fixed android build * chore: fixed qr code display * Rewrite timeouts using waitForSource * feature: added error messages handler * feature: added issued configs info parsing * feature: added functionality to revoke api configs * chore: added links to instructions * chore: fixed qr code with vpnkey processing * chore: fixed native config post processing * chore: added link to android tv instruction * change node to IpcProcessTun2SocksReplica * chore: minor ui fixes * Update Windows OpenSSL (#1426) * Update Windows OpenSSL to 3.0.16 and add shared library for QSslSocket plugin * chore: update link to submodule 3rd-prebuild --------- Co-authored-by: vladimir.kuznetsov * chore: added 404 handling for revoke configs - added revoke before remove api server for premium v2 * chore: added log to see proxy decrypt errors * chore: minor ui fix * chore: bump version * bugfix: fixed mobile controllers initialization (#1436) * bugfix: fixed mobile controllers initialization * chore: bump version * Merge pull request #1440 from amnezia-vpn/feature/subscription-settings-page feature/subscription settings page --------- Co-authored-by: vladimir.kuznetsov Co-authored-by: pokamest Co-authored-by: Mykola Baibuz Co-authored-by: Yaroslav Yashin Co-authored-by: KsZnak Co-authored-by: Cyril Anisimov --- .gitmodules | 3 - CMakeLists.txt | 4 +- README.md | 6 +- README_RU.md | 6 +- client/3rd-prebuilt | 2 +- client/3rd/OpenVPNAdapter | 1 - client/CMakeLists.txt | 178 +----- client/amnezia_application.cpp | 290 +--------- client/amnezia_application.h | 90 +-- client/cmake/ios.cmake | 12 +- client/cmake/sources.cmake | 191 +++++++ client/containers/containers_defs.cpp | 99 ++-- client/core/api/apiDefs.h | 51 ++ client/core/api/apiUtils.cpp | 87 +++ client/core/api/apiUtils.h | 22 + client/core/controllers/apiController.cpp | 509 ----------------- client/core/controllers/apiController.h | 50 -- client/core/controllers/coreController.cpp | 345 +++++++++++ client/core/controllers/coreController.h | 136 +++++ client/core/controllers/gatewayController.cpp | 303 ++++++++++ client/core/controllers/gatewayController.h | 35 ++ .../vpnConfigurationController.cpp | 6 +- .../controllers/vpnConfigurationController.h | 5 +- client/core/defs.h | 7 +- client/core/enums/apiEnums.h | 9 - client/core/errorstrings.cpp | 4 + client/core/ipcclient.cpp | 45 +- client/core/networkUtilities.h | 2 +- client/core/qrCodeUtils.cpp | 35 ++ client/core/qrCodeUtils.h | 17 + client/images/controls/monitor.svg | 5 + client/ios/networkextension/CMakeLists.txt | 7 +- client/ios/scripts/openvpn.sh | 19 - client/platforms/ios/HevSocksTunnel.swift | 1 + .../protocols/ikev2_vpn_protocol_windows.cpp | 2 +- client/protocols/xrayprotocol.cpp | 136 +++-- client/protocols/xrayprotocol.h | 10 +- client/resources.qrc | 9 +- client/secure_qsettings.cpp | 8 +- client/secure_qsettings.h | 6 +- client/server_scripts/install_docker.sh | 5 +- .../ipsec/configure_container.sh | 10 +- .../controllers/api/apiConfigsController.cpp | 537 ++++++++++++++++++ .../ui/controllers/api/apiConfigsController.h | 74 +++ .../controllers/api/apiSettingsController.cpp | 93 +++ .../controllers/api/apiSettingsController.h | 37 ++ .../ui/controllers/connectionController.cpp | 136 +---- client/ui/controllers/connectionController.h | 12 +- client/ui/controllers/exportController.cpp | 46 +- client/ui/controllers/exportController.h | 3 - client/ui/controllers/importController.cpp | 43 +- client/ui/controllers/installController.cpp | 147 ++--- client/ui/controllers/installController.h | 15 +- client/ui/controllers/pageController.h | 10 +- client/ui/models/api/apiAccountInfoModel.cpp | 143 +++++ client/ui/models/api/apiAccountInfoModel.h | 56 ++ client/ui/models/api/apiCountryModel.cpp | 122 ++++ client/ui/models/{ => api}/apiCountryModel.h | 26 +- client/ui/models/api/apiDevicesModel.cpp | 90 +++ client/ui/models/api/apiDevicesModel.h | 52 ++ .../ui/models/{ => api}/apiServicesModel.cpp | 22 +- client/ui/models/{ => api}/apiServicesModel.h | 0 client/ui/models/apiCountryModel.cpp | 84 --- client/ui/models/languageModel.cpp | 2 +- client/ui/models/servers_model.cpp | 8 +- client/ui/models/servers_model.h | 2 +- .../ui/qml/Components/InstalledAppsDrawer.qml | 2 +- .../ui/qml/Components/RenameServerDrawer.qml | 55 ++ client/ui/qml/Components/ServersListView.qml | 19 +- .../qml/Components/ShareConnectionDrawer.qml | 31 +- client/ui/qml/Controls2/ListViewType.qml | 38 ++ .../qml/Controls2/TextFieldWithHeaderType.qml | 7 +- client/ui/qml/Modules/Style/AmneziaStyle.qml | 1 + client/ui/qml/Pages2/PageDevMenu.qml | 10 +- client/ui/qml/Pages2/PageHome.qml | 18 +- .../Pages2/PageProtocolAwgClientSettings.qml | 38 +- .../ui/qml/Pages2/PageProtocolAwgSettings.qml | 70 +-- .../qml/Pages2/PageProtocolCloakSettings.qml | 16 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 12 +- .../PageProtocolShadowSocksSettings.qml | 6 +- .../PageProtocolWireGuardClientSettings.qml | 8 +- .../Pages2/PageProtocolWireGuardSettings.qml | 12 +- .../qml/Pages2/PageProtocolXraySettings.qml | 10 +- .../Pages2/PageServiceSocksProxySettings.qml | 46 +- client/ui/qml/Pages2/PageSettings.qml | 5 +- client/ui/qml/Pages2/PageSettingsAbout.qml | 3 +- ... => PageSettingsApiAvailableCountries.qml} | 82 ++- .../ui/qml/Pages2/PageSettingsApiDevices.qml | 100 ++++ .../Pages2/PageSettingsApiInstructions.qml | 124 ++++ .../Pages2/PageSettingsApiNativeConfigs.qml | 214 +++++++ .../qml/Pages2/PageSettingsApiServerInfo.qml | 351 +++++++++--- .../ui/qml/Pages2/PageSettingsApiSupport.qml | 127 +++++ .../Pages2/PageSettingsAppSplitTunneling.qml | 2 +- client/ui/qml/Pages2/PageSettingsDns.qml | 16 +- client/ui/qml/Pages2/PageSettingsLogging.qml | 2 + .../ui/qml/Pages2/PageSettingsServerData.qml | 10 - .../ui/qml/Pages2/PageSettingsServerInfo.qml | 85 +-- .../ui/qml/Pages2/PageSettingsServersList.qml | 14 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 11 +- .../Pages2/PageSetupWizardApiServiceInfo.qml | 2 +- .../Pages2/PageSetupWizardConfigSource.qml | 10 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 99 ++-- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 5 +- .../PageSetupWizardProtocolSettings.qml | 4 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 4 +- client/ui/qml/Pages2/PageShare.qml | 29 +- client/ui/qml/Pages2/PageShareFullAccess.qml | 1 - client/ui/qml/Pages2/PageStart.qml | 69 ++- client/ui/qml/main2.qml | 6 +- client/vpnconnection.cpp | 22 +- 110 files changed, 4168 insertions(+), 2156 deletions(-) delete mode 160000 client/3rd/OpenVPNAdapter create mode 100644 client/cmake/sources.cmake create mode 100644 client/core/api/apiDefs.h create mode 100644 client/core/api/apiUtils.cpp create mode 100644 client/core/api/apiUtils.h 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 create mode 100644 client/core/controllers/gatewayController.cpp create mode 100644 client/core/controllers/gatewayController.h delete mode 100644 client/core/enums/apiEnums.h create mode 100644 client/core/qrCodeUtils.cpp create mode 100644 client/core/qrCodeUtils.h create mode 100644 client/images/controls/monitor.svg delete mode 100755 client/ios/scripts/openvpn.sh create mode 100644 client/ui/controllers/api/apiConfigsController.cpp create mode 100644 client/ui/controllers/api/apiConfigsController.h create mode 100644 client/ui/controllers/api/apiSettingsController.cpp create mode 100644 client/ui/controllers/api/apiSettingsController.h create mode 100644 client/ui/models/api/apiAccountInfoModel.cpp create mode 100644 client/ui/models/api/apiAccountInfoModel.h create mode 100644 client/ui/models/api/apiCountryModel.cpp rename client/ui/models/{ => api}/apiCountryModel.h (57%) create mode 100644 client/ui/models/api/apiDevicesModel.cpp create mode 100644 client/ui/models/api/apiDevicesModel.h rename client/ui/models/{ => api}/apiServicesModel.cpp (88%) rename client/ui/models/{ => api}/apiServicesModel.h (100%) delete mode 100644 client/ui/models/apiCountryModel.cpp create mode 100644 client/ui/qml/Components/RenameServerDrawer.qml create mode 100644 client/ui/qml/Controls2/ListViewType.qml rename client/ui/qml/Pages2/{PageSettingsApiLanguageList.qml => PageSettingsApiAvailableCountries.qml} (57%) create mode 100644 client/ui/qml/Pages2/PageSettingsApiDevices.qml create mode 100644 client/ui/qml/Pages2/PageSettingsApiInstructions.qml create mode 100644 client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml create mode 100644 client/ui/qml/Pages2/PageSettingsApiSupport.qml diff --git a/.gitmodules b/.gitmodules index 3ceaa56e..decab9b7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "client/3rd/OpenVPNAdapter"] - path = client/3rd/OpenVPNAdapter - url = https://github.com/amnezia-vpn/OpenVPNAdapter.git [submodule "client/3rd/qtkeychain"] path = client/3rd/qtkeychain url = https://github.com/frankosterfeld/qtkeychain.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 434b195f..7c602249 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.1 +project(${PROJECT} VERSION 4.8.4.2 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 2074) +set(APP_ANDROID_VERSION_CODE 2079) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/README.md b/README.md index 368bcfff..992c3ad0 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ [![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org) -### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/kldscp/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting) +### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting) > [!TIP] -> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/kldscp/amnezia.org). +> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ). - + [All releases](https://github.com/amnezia-vpn/amnezia-client/releases) diff --git a/README_RU.md b/README_RU.md index 45b506f2..f9ca7d12 100644 --- a/README_RU.md +++ b/README_RU.md @@ -10,12 +10,12 @@ [![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org) -### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/kldscp/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting) +### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting) > [!TIP] -> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/kldscp/amnezia.org). +> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org). - + [Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases) diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index ba580dc5..e555c78b 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit ba580dc5bd7784f7b1e110ff0365f3286e549a61 +Subproject commit e555c78bcf44070d5c88bcca54480732c9164f18 diff --git a/client/3rd/OpenVPNAdapter b/client/3rd/OpenVPNAdapter deleted file mode 160000 index 7c821a8d..00000000 --- a/client/3rd/OpenVPNAdapter +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7c821a8d5c1ad5ad94e0763b4f25a875b5a6fe1b diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 3ef92385..72adaf25 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -96,11 +96,6 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAK qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) # -- i18n end -if(IOS) - execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) -endif() - set(IS_CI ${CI}) if(IS_CI) message("Detected CI env") @@ -110,8 +105,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 @@ -120,167 +115,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 @@ -324,30 +174,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 aeed439b..f32d525a 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -10,26 +12,16 @@ #include #include #include -#include -#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); @@ -282,19 +180,20 @@ 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(); + emit m_coreController->pageController()->raiseMainWindow(); //TODO }); } #endif @@ -304,163 +203,12 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const return m_engine; } -void AmneziaApplication::initModels() +QNetworkAccessManager *AmneziaApplication::networkManager() { - 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()); }); + return m_nam; } -void AmneziaApplication::initControllers() +QClipboard *AmneziaApplication::getClipboard() { - 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()); + return this->clipboard(); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index cfeac0d1..ea5f6f52 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -11,44 +11,12 @@ #else #include #endif +#include +#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/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" - #define amnApp (static_cast(QCoreApplication::instance())) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) @@ -67,8 +35,6 @@ public: void init(); void registerTypes(); void loadFonts(); - void loadTranslator(); - void updateTranslator(const QLocale &locale); bool parseCommands(); #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) @@ -76,68 +42,24 @@ public: #endif QQmlApplicationEngine *qmlEngine() const; - QNetworkAccessManager *manager() { return m_nam; } - -signals: - void translationsUpdated(); + QNetworkAccessManager *networkManager(); + QClipboard *getClipboard(); 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; - - 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; QNetworkAccessManager *m_nam; - - QMetaObject::Connection m_reloadConfigErrorOccurredConnection; }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index 5fda3506..58192237 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -76,11 +76,7 @@ set_target_properties(${PROJECT} PROPERTIES XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks" XCODE_EMBED_APP_EXTENSIONS networkextension - XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" - XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development" - XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual - XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN" - XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN" + XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic ) set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_SWIFT_VERSION "5.0" @@ -126,9 +122,9 @@ add_subdirectory(ios/networkextension) add_dependencies(${PROJECT} networkextension) set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS - "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework" + "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework" ) -set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos) -target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework") +set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/) +target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework") diff --git a/client/cmake/sources.cmake b/client/cmake/sources.cmake new file mode 100644 index 00000000..c3af531a --- /dev/null +++ b/client/cmake/sources.cmake @@ -0,0 +1,191 @@ +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/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 + ${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}/../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/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 + ${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/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index ce673a85..52b148c0 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -110,22 +110,19 @@ QMap ContainerProps::containerDescriptions() QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its " "own security protocol with SSL/TLS for key exchange.") }, { DockerContainer::ShadowSocks, - QObject::tr("Shadowsocks - masks VPN traffic, making it similar to normal web traffic, but it " - "may be recognized by analysis systems in some highly censored regions.") }, + QObject::tr("Shadowsocks masks VPN traffic, making it resemble normal web traffic, but it may still be detected by certain analysis systems.") }, { DockerContainer::Cloak, QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against " - "active-probing detection. Ideal for bypassing blocking in regions with the highest levels " - "of censorship.") }, + "active-probing detection. It is very resistant to detection, but offers low speed.") }, { DockerContainer::WireGuard, - QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " - "consumption. Recommended for regions with low levels of censorship.") }, + QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power " + "consumption.") }, { DockerContainer::Awg, - QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, " - "but very resistant to blockages. " - "Recommended for regions with high levels of censorship.") }, + QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. " + "It provides high connection speed and ensures stable operation even in the most challenging network conditions.") }, { DockerContainer::Xray, - QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. " - "Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") }, + QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. " + "It is highly resistant to detection and offers high speed.") }, { DockerContainer::Ipsec, QObject::tr("IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after " "signal loss. It has native support on the latest versions of Android and iOS.") }, @@ -144,20 +141,20 @@ QMap ContainerProps::containerDetailedDescriptions() return { { DockerContainer::OpenVpn, QObject::tr( - "OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n" - "It employs its unique security protocol, " - "leveraging the strength of SSL/TLS for encryption and key exchange. " - "Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, " - "catering to a wide range of devices and operating systems. " - "Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, " - "which continually reinforces its security. " - "With a strong balance of performance, security, and compatibility, " - "OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n" - "* Available in the AmneziaVPN across all platforms\n" - "* Normal power consumption on mobile devices\n" - "* Flexible customisation to suit user needs to work with different operating systems and devices\n" - "* Recognised by DPI analysis systems and therefore susceptible to blocking\n" - "* Can operate over both TCP and UDP network protocols.") }, + "OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n" + "It employs its unique security protocol, " + "leveraging the strength of SSL/TLS for encryption and key exchange. " + "Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, " + "catering to a wide range of devices and operating systems. " + "Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, " + "which continually reinforces its security. " + "With a strong balance of performance, security, and compatibility, " + "OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* Normal power consumption on mobile devices\n" + "* Flexible customisation to suit user needs to work with different operating systems and devices\n" + "* Recognised by DPI systems and therefore susceptible to blocking\n" + "* Can operate over both TCP and UDP network protocols.") }, { DockerContainer::ShadowSocks, QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. " "Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection." @@ -169,28 +166,26 @@ QMap ContainerProps::containerDetailedDescriptions() "* Works over TCP network protocol.") }, { DockerContainer::Cloak, QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for " - "protecting against blocking.\n\n" + "protecting against detection.\n\n" "OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client " "and the server.\n\n" - "Cloak protects OpenVPN from detection and blocking. \n\n" + "Cloak protects OpenVPN from detection. \n\n" "Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, " "and also protects the VPN from detection by Active Probing. This makes it very resistant to " "being detected\n\n" "Immediately after receiving the first data packet, Cloak authenticates the incoming connection. " "If authentication fails, the plugin masks the server as a fake website and your VPN becomes " "invisible to analysis systems.\n\n" - "If there is a extreme level of Internet censorship in your region, we advise you to use only " - "OpenVPN over Cloak from the first connection\n\n" "* Available in the AmneziaVPN across all platforms\n" "* High power consumption on mobile devices\n" "* Flexible settings\n" - "* Not recognised by DPI analysis systems\n" + "* Not recognised by detection systems\n" "* Works over TCP network protocol, 443 port.\n") }, { DockerContainer::WireGuard, QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n" "WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption " "settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n" - "WireGuard is very susceptible to blocking due to its distinct packet signatures. " + "WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. " "Unlike some other VPN protocols that employ obfuscation techniques, " "the consistent signature patterns of WireGuard packets can be more easily identified and " "thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n" @@ -213,18 +208,18 @@ QMap ContainerProps::containerDetailedDescriptions() "* Available in the AmneziaVPN across all platforms\n" "* Low power consumption\n" "* Minimum number of settings\n" - "* Not recognised by DPI analysis systems, resistant to blocking\n" + "* Not recognised by traffic analysis systems\n" "* Works over UDP network protocol.") }, { DockerContainer::Xray, - QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, " - "is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n" - "It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, " - "thus presenting an authentic TLS certificate and data. \n" - "This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, " - "legitimate sites without the need for specific configurations. \n" - "Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, " - "REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. " - "This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.") + QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, " + "is designed to provide the highest level of protection against detection through its innovative approach to security and privacy.\n" + "It uniquely identifies attackers during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting attackers to genuine websites, " + "thus presenting an authentic TLS certificate and data. \n" + "This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, " + "legitimate sites without the need for specific configurations. \n" + "Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, " + "REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security. " + "This makes REALITY a robust solution for maintaining internet freedom.") }, { DockerContainer::Ipsec, QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n" @@ -332,9 +327,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c) bool ContainerProps::isEasySetupContainer(DockerContainer container) { switch (container) { - case DockerContainer::WireGuard: return true; case DockerContainer::Awg: return true; - // case DockerContainer::Cloak: return true; default: return false; } } @@ -342,9 +335,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container) QString ContainerProps::easySetupHeader(DockerContainer container) { switch (container) { - case DockerContainer::WireGuard: return tr("Low"); - case DockerContainer::Awg: return tr("High"); - // case DockerContainer::Cloak: return tr("Extreme"); + case DockerContainer::Awg: return tr("Automatic"); default: return ""; } } @@ -352,10 +343,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container) QString ContainerProps::easySetupDescription(DockerContainer container) { switch (container) { - case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy."); - case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases."); - // case DockerContainer::Cloak: - // return tr("Most VPN protocols are blocked. Recommended if other options are not working."); + case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. " + "It provides high connection speed and ensures stable operation even in the most challenging network conditions."); default: return ""; } } @@ -363,9 +352,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container) int ContainerProps::easySetupOrder(DockerContainer container) { switch (container) { - case DockerContainer::WireGuard: return 3; - case DockerContainer::Awg: return 2; - // case DockerContainer::Cloak: return 1; + case DockerContainer::Awg: return 1; default: return 0; } } @@ -384,9 +371,9 @@ bool ContainerProps::isShareable(DockerContainer container) QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig) { QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol)) - .toObject() - .value(config_key::last_config) - .toString(); + .toObject() + .value(config_key::last_config) + .toString(); return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); } diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h new file mode 100644 index 00000000..41dd80ba --- /dev/null +++ b/client/core/api/apiDefs.h @@ -0,0 +1,51 @@ +#ifndef APIDEFS_H +#define APIDEFS_H + +#include + +namespace apiDefs +{ + enum ConfigType { + AmneziaFreeV2 = 0, + 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"); + constexpr QLatin1String serviceType("service_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 +} + +#endif // APIDEFS_H diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp new file mode 100644 index 00000000..9f518b52 --- /dev/null +++ b/client/core/api/apiUtils.cpp @@ -0,0 +1,87 @@ +#include "apiUtils.h" + +#include +#include + +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 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 (serviceType == servicePremium || stackType == stackPremium) { + return apiDefs::ConfigType::AmneziaPremiumV2; + } else if (serviceType == serviceFree || stackType == stackFree) { + return apiDefs::ConfigType::AmneziaFreeV3; + } + } + default: { + return apiDefs::ConfigType::SelfHosted; + } + }; +} + +apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigObject) +{ + return static_cast(serverConfigObject.value(apiDefs::key::configVersion).toInt()); +} + +amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply) +{ + const int httpStatusCodeConflict = 409; + const int httpStatusCodeNotFound = 404; + + 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; + } else if (httpStatusCode == httpStatusCodeNotFound) { + return amnezia::ErrorCode::ApiNotFoundError; + } + 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 new file mode 100644 index 00000000..82ac315b --- /dev/null +++ b/client/core/api/apiUtils.h @@ -0,0 +1,22 @@ +#ifndef APIUTILS_H +#define APIUTILS_H + +#include +#include + +#include "apiDefs.h" +#include "core/defs.h" + +namespace apiUtils +{ + bool isServerFromApi(const QJsonObject &serverConfigObject); + + bool isSubscriptionExpired(const QString &subscriptionEndDate); + + 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/apiController.cpp b/client/core/controllers/apiController.cpp deleted file mode 100644 index 6562632a..00000000 --- a/client/core/controllers/apiController.cpp +++ /dev/null @@ -1,509 +0,0 @@ -#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" - -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 - - 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) - : 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() == ApiConfigSources::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() == ApiConfigSources::AmneziaGateway) { - apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject()); - } - - serverConfig[configKey::apiConfig] = apiConfig; - - 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; - 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) -{ -#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(); - - 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) -{ -#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)); - - 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; - } - - 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; - } - - try { - auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt); - 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/apiController.h b/client/core/controllers/apiController.h deleted file mode 100644 index bcb25f96..00000000 --- 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 00000000..82232c99 --- /dev/null +++ b/client/core/controllers/coreController.cpp @@ -0,0 +1,345 @@ +#include "coreController.h" + +#include + +#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(); + + initAndroidController(); + initAppleController(); + + 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()); + + m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.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_apiDevicesModel, 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() +{ + initErrorMessagesHandler(); + + initApiCountryModelUpdateHandler(); + initContainerModelUpdateHandler(); + initAdminConfigRevokedHandler(); + 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::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)); +} + +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::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 00000000..700504af --- /dev/null +++ b/client/core/controllers/coreController.h @@ -0,0 +1,136 @@ +#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/apiCountryModel.h" +#include "ui/models/api/apiDevicesModel.h" +#include "ui/models/api/apiServicesModel.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 initErrorMessagesHandler(); + + void initApiCountryModelUpdateHandler(); + void initContainerModelUpdateHandler(); + void initAdminConfigRevokedHandler(); + 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; + QSharedPointer m_apiDevicesModel; + + 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/gatewayController.cpp b/client/core/controllers/gatewayController.cpp new file mode 100644 index 00000000..15776328 --- /dev/null +++ b/client/core/controllers/gatewayController.cpp @@ -0,0 +1,303 @@ +#include "gatewayController.h" + +#include +#include + +#include +#include +#include +#include + +#include "QBlockCipher.h" +#include "QRsa.h" + +#include "amnezia_application.h" +#include "core/api/apiUtils.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->networkManager()->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->networkManager()->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 = apiUtils::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->networkManager()->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, true, key, iv, salt)) { + auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) { + request.setUrl(url); + return amnApp->networkManager()->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 = apiUtils::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->networkManager()->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" << encryptedResponseBody; + 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 00000000..45d989f0 --- /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/controllers/vpnConfigurationController.cpp b/client/core/controllers/vpnConfigurationController.cpp index 52f42c42..61287972 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 4a174e95..6c4b93b3 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; @@ -47,6 +44,7 @@ namespace amnezia InternalError = 101, NotImplementedError = 102, AmneziaServiceNotRunning = 103, + NotSupportedOnThisPlatform = 104, // Server errors ServerCheckFailed = 200, @@ -100,6 +98,7 @@ namespace amnezia // import and install errors ImportInvalidConfigError = 900, ImportOpenConfigError = 901, + NoInstalledContainersError = 902, // Android errors AndroidError = 1000, @@ -113,6 +112,8 @@ namespace amnezia ApiMissingAgwPublicKey = 1105, ApiConfigDecryptionError = 1106, ApiServicesMissingError = 1107, + ApiConfigLimitError = 1108, + ApiNotFoundError = 1109, // QFile errors OpenError = 1200, diff --git a/client/core/enums/apiEnums.h b/client/core/enums/apiEnums.h deleted file mode 100644 index 1f050007..00000000 --- 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/core/errorstrings.cpp b/client/core/errorstrings.cpp index ac3ff971..e9f47cff 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; @@ -54,6 +55,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; @@ -67,6 +69,8 @@ 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; + 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/core/ipcclient.cpp b/client/core/ipcclient.cpp index b44da1bf..69edcd15 100644 --- a/client/core/ipcclient.cpp +++ b/client/core/ipcclient.cpp @@ -5,12 +5,12 @@ IpcClient *IpcClient::m_instance = nullptr; IpcClient::IpcClient(QObject *parent) : QObject(parent) { - } IpcClient::~IpcClient() { - if (m_localSocket) m_localSocket->close(); + if (m_localSocket) + m_localSocket->close(); } bool IpcClient::isSocketConnected() const @@ -25,13 +25,15 @@ IpcClient *IpcClient::Instance() QSharedPointer IpcClient::Interface() { - if (!Instance()) return nullptr; + if (!Instance()) + return nullptr; return Instance()->m_ipcClient; } QSharedPointer IpcClient::InterfaceTun2Socks() { - if (!Instance()) return nullptr; + if (!Instance()) + return nullptr; return Instance()->m_Tun2SocksClient; } @@ -42,15 +44,28 @@ bool IpcClient::init(IpcClient *instance) Instance()->m_localSocket = new QLocalSocket(Instance()); connect(Instance()->m_localSocket.data(), &QLocalSocket::connected, &Instance()->m_ClientNode, []() { Instance()->m_ClientNode.addClientSideConnection(Instance()->m_localSocket.data()); + auto cliNode = Instance()->m_ClientNode.acquire(); + cliNode->waitForSource(5000); + Instance()->m_ipcClient.reset(cliNode); + + if (!Instance()->m_ipcClient) { + qWarning() << "IpcClient is not ready!"; + } - Instance()->m_ipcClient.reset(Instance()->m_ClientNode.acquire()); Instance()->m_ipcClient->waitForSource(1000); if (!Instance()->m_ipcClient->isReplicaValid()) { qWarning() << "IpcClient replica is not connected!"; } - Instance()->m_Tun2SocksClient.reset(Instance()->m_ClientNode.acquire()); + auto t2sNode = Instance()->m_ClientNode.acquire(); + t2sNode->waitForSource(5000); + Instance()->m_Tun2SocksClient.reset(t2sNode); + + if (!Instance()->m_Tun2SocksClient) { + qWarning() << "IpcClient::m_Tun2SocksClient is not ready!"; + } + Instance()->m_Tun2SocksClient->waitForSource(1000); if (!Instance()->m_Tun2SocksClient->isReplicaValid()) { @@ -58,9 +73,8 @@ bool IpcClient::init(IpcClient *instance) } }); - connect(Instance()->m_localSocket, &QLocalSocket::disconnected, [instance](){ - instance->m_isSocketConnected = false; - }); + connect(Instance()->m_localSocket, &QLocalSocket::disconnected, + [instance]() { instance->m_isSocketConnected = false; }); Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl()); Instance()->m_localSocket->waitForConnected(); @@ -77,7 +91,7 @@ bool IpcClient::init(IpcClient *instance) QSharedPointer IpcClient::CreatePrivilegedProcess() { - if (! Instance()->m_ipcClient || ! Instance()->m_ipcClient->isReplicaValid()) { + if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) { qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid"; return nullptr; } @@ -100,18 +114,15 @@ QSharedPointer IpcClient::CreatePrivilegedProcess() pd->ipcProcess.reset(priv); if (!pd->ipcProcess) { qWarning() << "Acquire PrivilegedProcess failed"; - } - else { + } else { pd->ipcProcess->waitForSource(1000); if (!pd->ipcProcess->isReplicaValid()) { qWarning() << "PrivilegedProcess replica is not connected!"; } - QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(), [pd](){ - pd->replicaNode->deleteLater(); - }); + QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(), + [pd]() { pd->replicaNode->deleteLater(); }); } - }); pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid)); pd->localSocket->waitForConnected(); @@ -119,5 +130,3 @@ QSharedPointer IpcClient::CreatePrivilegedProcess() auto processReplica = QSharedPointer(pd->ipcProcess); return processReplica; } - - diff --git a/client/core/networkUtilities.h b/client/core/networkUtilities.h index 3057b852..3b64b547 100644 --- a/client/core/networkUtilities.h +++ b/client/core/networkUtilities.h @@ -5,6 +5,7 @@ #include #include #include +#include class NetworkUtilities : public QObject @@ -30,7 +31,6 @@ public: static QString ipAddressFromIpWithSubnet(const QString ip); static QStringList summarizeRoutes(const QStringList &ips, const QString cidr); - }; #endif // NETWORKUTILITIES_H diff --git a/client/core/qrCodeUtils.cpp b/client/core/qrCodeUtils.cpp new file mode 100644 index 00000000..a18af172 --- /dev/null +++ b/client/core/qrCodeUtils.cpp @@ -0,0 +1,35 @@ +#include "qrCodeUtils.h" + +#include +#include + +QList qrCodeUtils::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 << qrCodeUtils::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 qrCodeUtils::svgToBase64(const QString &image) +{ + return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().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 new file mode 100644 index 00000000..cda0723b --- /dev/null +++ b/client/core/qrCodeUtils.h @@ -0,0 +1,17 @@ +#ifndef QRCODEUTILS_H +#define QRCODEUTILS_H + +#include + +#include "qrcodegen.hpp" + +namespace qrCodeUtils +{ + 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/images/controls/monitor.svg b/client/images/controls/monitor.svg new file mode 100644 index 00000000..1cdf57c2 --- /dev/null +++ b/client/images/controls/monitor.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/ios/networkextension/CMakeLists.txt b/client/ios/networkextension/CMakeLists.txt index c448ed08..dde03b3b 100644 --- a/client/ios/networkextension/CMakeLists.txt +++ b/client/ios/networkextension/CMakeLists.txt @@ -27,12 +27,7 @@ set_target_properties(networkextension PROPERTIES XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks" - XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" - XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development" - - XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual - XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN.network-extension" - XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN.network-extension" + XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic ) set_target_properties(networkextension PROPERTIES diff --git a/client/ios/scripts/openvpn.sh b/client/ios/scripts/openvpn.sh deleted file mode 100755 index 544b8078..00000000 --- a/client/ios/scripts/openvpn.sh +++ /dev/null @@ -1,19 +0,0 @@ -XCODEBUILD="/usr/bin/xcodebuild" -WORKINGDIR=`pwd` -PATCH="/usr/bin/patch" - - cat $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/Project.xcconfig > $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/amnezia.xcconfig - cat << EOF >> $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/amnezia.xcconfig - PROJECT_TEMP_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/OpenVPNAdapter.build - CONFIGURATION_BUILD_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/Release-iphoneos - BUILT_PRODUCTS_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/Release-iphoneos -EOF - - - cd 3rd/OpenVPNAdapter - if $XCODEBUILD -scheme OpenVPNAdapter -configuration Release -xcconfig Configuration/amnezia.xcconfig -sdk iphoneos -destination 'generic/platform=iOS' -project OpenVPNAdapter.xcodeproj ; then - echo "OpenVPNAdapter built successfully" - else - echo "OpenVPNAdapter build failed" - fi - cd ../../ diff --git a/client/platforms/ios/HevSocksTunnel.swift b/client/platforms/ios/HevSocksTunnel.swift index a86a0758..87d995e8 100644 --- a/client/platforms/ios/HevSocksTunnel.swift +++ b/client/platforms/ios/HevSocksTunnel.swift @@ -1,4 +1,5 @@ import HevSocks5Tunnel +import NetworkExtension public enum Socks5Tunnel { diff --git a/client/protocols/ikev2_vpn_protocol_windows.cpp b/client/protocols/ikev2_vpn_protocol_windows.cpp index e2e4ca90..b4110f03 100644 --- a/client/protocols/ikev2_vpn_protocol_windows.cpp +++ b/client/protocols/ikev2_vpn_protocol_windows.cpp @@ -238,7 +238,7 @@ ErrorCode Ikev2Protocol::start() "-CipherTransformConstants GCMAES128 " "-EncryptionMethod AES256 " "-IntegrityCheckMethod SHA256 " - "-PfsGroup None " + "-PfsGroup PFS2048 " "-DHGroup Group14 " "-PassThru -Force\"") .arg(tunnelName()); diff --git a/client/protocols/xrayprotocol.cpp b/client/protocols/xrayprotocol.cpp index 7c69ccde..faad8e94 100755 --- a/client/protocols/xrayprotocol.cpp +++ b/client/protocols/xrayprotocol.cpp @@ -1,16 +1,14 @@ #include "xrayprotocol.h" -#include "utilities.h" -#include "core/networkUtilities.h" - #include #include #include #include +#include "core/networkUtilities.h" +#include "utilities.h" -XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent): - VpnProtocol(configuration, parent) +XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent) { readXrayConfiguration(configuration); m_routeGateway = NetworkUtilities::getGatewayAndIface(); @@ -45,10 +43,7 @@ ErrorCode XrayProtocol::start() QStringList args = QStringList() << "-c" << m_xrayCfgFile.fileName() << "-format=json"; - qDebug().noquote() << "XrayProtocol::start()" - << xrayExecPath() << args.join(" "); - - + qDebug().noquote() << "XrayProtocol::start()" << xrayExecPath() << args.join(" "); m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels); m_xrayProcess.setProgram(xrayExecPath()); @@ -66,14 +61,15 @@ ErrorCode XrayProtocol::start() #endif }); - connect(&m_xrayProcess, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) { - qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus; - setConnectionState(Vpn::ConnectionState::Disconnected); - if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) { - emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed); - emit setConnectionState(Vpn::ConnectionState::Error); - } - }); + connect(&m_xrayProcess, QOverload::of(&QProcess::finished), this, + [this](int exitCode, QProcess::ExitStatus exitStatus) { + qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus; + setConnectionState(Vpn::ConnectionState::Disconnected); + if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) { + emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed); + emit setConnectionState(Vpn::ConnectionState::Error); + } + }); m_xrayProcess.start(); m_xrayProcess.waitForStarted(); @@ -82,11 +78,10 @@ ErrorCode XrayProtocol::start() setConnectionState(Vpn::ConnectionState::Connecting); QThread::msleep(1000); return startTun2Sock(); - } - else return ErrorCode::XrayExecutableMissing; + } else + return ErrorCode::XrayExecutableMissing; } - ErrorCode XrayProtocol::startTun2Sock() { m_t2sProcess->start(); @@ -98,71 +93,68 @@ ErrorCode XrayProtocol::startTun2Sock() connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this, [&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; }); - connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, - [&](int vpnState) { - qDebug() << "PrivilegedProcess setConnectionState " << vpnState; - if (vpnState == Vpn::ConnectionState::Connected) - { - setConnectionState(Vpn::ConnectionState::Connecting); - QList dnsAddr; - dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString())); - dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString())); + connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, [&](int vpnState) { + qDebug() << "PrivilegedProcess setConnectionState " << vpnState; + if (vpnState == Vpn::ConnectionState::Connected) { + setConnectionState(Vpn::ConnectionState::Connecting); + QList dnsAddr; + dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString())); + dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString())); #ifdef Q_OS_WIN - QThread::msleep(8000); + QThread::msleep(8000); #endif #ifdef Q_OS_MACOS - QThread::msleep(5000); - IpcClient::Interface()->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr); - IpcClient::Interface()->updateResolvers("utun22", dnsAddr); + QThread::msleep(5000); + IpcClient::Interface()->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr); + IpcClient::Interface()->updateResolvers("utun22", dnsAddr); #endif #ifdef Q_OS_LINUX - QThread::msleep(1000); - IpcClient::Interface()->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr); - IpcClient::Interface()->updateResolvers("tun2", dnsAddr); + QThread::msleep(1000); + IpcClient::Interface()->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr); + IpcClient::Interface()->updateResolvers("tun2", dnsAddr); #endif #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) - // killSwitch toggle - if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { - m_configData.insert("vpnServer", m_remoteAddress); - IpcClient::Interface()->enableKillSwitch(m_configData, 0); - } + // killSwitch toggle + if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { + m_configData.insert("vpnServer", m_remoteAddress); + IpcClient::Interface()->enableKillSwitch(m_configData, 0); + } #endif - if (m_routeMode == 0) { - IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1"); - IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1"); - IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress); - } - IpcClient::Interface()->StopRoutingIpv6(); + if (m_routeMode == Settings::RouteMode::VpnAllSites) { + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress); + } + IpcClient::Interface()->StopRoutingIpv6(); #ifdef Q_OS_WIN - IpcClient::Interface()->updateResolvers("tun2", dnsAddr); - QList netInterfaces = QNetworkInterface::allInterfaces(); - for (int i = 0; i < netInterfaces.size(); i++) { - for (int j = 0; j < netInterfaces.at(i).addressEntries().size(); j++) - { - // killSwitch toggle - if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { - if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { - IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index()); - } - m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index()); - m_configData.insert("vpnGateway", m_vpnGateway); - m_configData.insert("vpnServer", m_remoteAddress); - IpcClient::Interface()->enablePeerTraffic(m_configData); - } + IpcClient::Interface()->updateResolvers("tun2", dnsAddr); + QList netInterfaces = QNetworkInterface::allInterfaces(); + for (int i = 0; i < netInterfaces.size(); i++) { + for (int j = 0; j < netInterfaces.at(i).addressEntries().size(); j++) { + // killSwitch toggle + if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { + if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { + IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index()); } + m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index()); + m_configData.insert("vpnGateway", m_vpnGateway); + m_configData.insert("vpnServer", m_remoteAddress); + IpcClient::Interface()->enablePeerTraffic(m_configData); } -#endif - setConnectionState(Vpn::ConnectionState::Connected); } + } +#endif + setConnectionState(Vpn::ConnectionState::Connected); + } #if !defined(Q_OS_MACOS) - if (vpnState == Vpn::ConnectionState::Disconnected) { - setConnectionState(Vpn::ConnectionState::Disconnected); - IpcClient::Interface()->deleteTun("tun2"); - IpcClient::Interface()->StartRoutingIpv6(); - IpcClient::Interface()->clearSavedRoutes(); - } + if (vpnState == Vpn::ConnectionState::Disconnected) { + setConnectionState(Vpn::ConnectionState::Disconnected); + IpcClient::Interface()->deleteTun("tun2"); + IpcClient::Interface()->StartRoutingIpv6(); + IpcClient::Interface()->clearSavedRoutes(); + } #endif - }); + }); return ErrorCode::NoError; } @@ -204,7 +196,7 @@ void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration) m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt(); m_remoteHost = configuration.value(amnezia::config_key::hostName).toString(); m_remoteAddress = NetworkUtilities::getIPAddress(m_remoteHost); - m_routeMode = configuration.value(amnezia::config_key::splitTunnelType).toInt(); + m_routeMode = static_cast(configuration.value(amnezia::config_key::splitTunnelType).toInt()); m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString(); m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString(); } diff --git a/client/protocols/xrayprotocol.h b/client/protocols/xrayprotocol.h index ee632333..c79ef608 100644 --- a/client/protocols/xrayprotocol.h +++ b/client/protocols/xrayprotocol.h @@ -1,14 +1,16 @@ #ifndef XRAYPROTOCOL_H #define XRAYPROTOCOL_H -#include "openvpnprotocol.h" #include "QProcess" + #include "containers/containers_defs.h" +#include "openvpnprotocol.h" +#include "settings.h" class XrayProtocol : public VpnProtocol { public: - XrayProtocol(const QJsonObject& configuration, QObject* parent = nullptr); + XrayProtocol(const QJsonObject &configuration, QObject *parent = nullptr); virtual ~XrayProtocol() override; ErrorCode start() override; @@ -24,11 +26,12 @@ protected: private: static QString xrayExecPath(); static QString tun2SocksExecPath(); + private: int m_localPort; QString m_remoteHost; QString m_remoteAddress; - int m_routeMode; + Settings::RouteMode m_routeMode; QJsonObject m_configData; QString m_primaryDNS; QString m_secondaryDNS; @@ -37,7 +40,6 @@ private: QSharedPointer m_t2sProcess; #endif QTemporaryFile m_xrayCfgFile; - }; #endif // XRAYPROTOCOL_H diff --git a/client/resources.qrc b/client/resources.qrc index ff03a6e7..16071da0 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,13 @@ ui/qml/Pages2/PageShare.qml 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 + ui/qml/Pages2/PageSettingsApiNativeConfigs.qml + ui/qml/Pages2/PageSettingsApiDevices.qml + images/controls/monitor.svg images/flagKit/ZW.svg diff --git a/client/secure_qsettings.cpp b/client/secure_qsettings.cpp index 88c0242b..4fd199db 100644 --- a/client/secure_qsettings.cpp +++ b/client/secure_qsettings.cpp @@ -15,6 +15,12 @@ using namespace QKeychain; +namespace { + constexpr const char *settingsKeyTag = "settingsKeyTag"; + constexpr const char *settingsIvTag = "settingsIvTag"; + constexpr const char *keyChainName = "AmneziaVPN-Keychain"; +} + SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent) : QObject { parent }, m_settings(organization, application, parent), encryptedKeys({ "Servers/serversList" }) { @@ -49,7 +55,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue // check if value is not encrypted, v. < 2.0.x retVal = m_settings.value(key); if (retVal.isValid()) { - if (retVal.userType() == QVariant::ByteArray && retVal.toByteArray().mid(0, magicString.size()) == magicString) { + if (retVal.userType() == QMetaType::QByteArray && retVal.toByteArray().mid(0, magicString.size()) == magicString) { if (getEncKey().isEmpty() || getEncIv().isEmpty()) { qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty"; diff --git a/client/secure_qsettings.h b/client/secure_qsettings.h index 43890578..3f04096e 100644 --- a/client/secure_qsettings.h +++ b/client/secure_qsettings.h @@ -8,10 +8,6 @@ #include "keychain.h" -constexpr const char *settingsKeyTag = "settingsKeyTag"; -constexpr const char *settingsIvTag = "settingsIvTag"; -constexpr const char *keyChainName = "AmneziaVPN-Keychain"; - class SecureQSettings : public QObject { Q_OBJECT @@ -44,7 +40,7 @@ public: private: QSettings m_settings; - mutable QMap m_cache; + mutable QHash m_cache; QStringList encryptedKeys; // encode only key listed here // only this fields need for backup diff --git a/client/server_scripts/install_docker.sh b/client/server_scripts/install_docker.sh index 6fed78c0..619b08d6 100644 --- a/client/server_scripts/install_docker.sh +++ b/client/server_scripts/install_docker.sh @@ -1,7 +1,7 @@ if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\ elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\ elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\ -elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="> /dev/null 2>&1"; docker_pkg="docker"; dist="archlinux";\ +elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\ else echo "Packet manager not found"; exit 1; fi;\ echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\ if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\ @@ -12,6 +12,9 @@ if ! command -v docker > /dev/null 2>&1; then \ sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\ sleep 5; sudo systemctl enable --now docker; sleep 5;\ fi;\ +if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \ + if ! command -v apparmor_parser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor; fi;\ +fi;\ if [ "$(systemctl is-active docker)" != "active" ]; then \ sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\ sleep 5; sudo systemctl start docker; sleep 5;\ diff --git a/client/server_scripts/ipsec/configure_container.sh b/client/server_scripts/ipsec/configure_container.sh index 76c4dfaf..1f0a45cb 100644 --- a/client/server_scripts/ipsec/configure_container.sh +++ b/client/server_scripts/ipsec/configure_container.sh @@ -33,14 +33,14 @@ conn shared right=%any encapsulation=yes authby=secret - pfs=no + pfs=yes rekey=no keyingtries=5 dpddelay=30 dpdtimeout=120 dpdaction=clear ikev2=never - ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1,aes256-sha2;modp1024,aes128-sha1;modp1024 + ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1,aes256-sha2;modp2048,aes128-sha1;modp2048 phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes256-sha2_512,aes128-sha2,aes256-sha2 ikelifetime=24h salifetime=24h @@ -244,9 +244,9 @@ conn ikev2-cp auto=add ikev2=insist rekey=no - pfs=no - ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1 - phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes128-sha2,aes256-sha2 + pfs=yes + ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1,aes256-sha2;modp2048,aes128-sha1;modp2048 + phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes256-sha2_512,aes128-sha2,aes256-sha2 ikelifetime=24h salifetime=24h encapsulation=yes diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp new file mode 100644 index 00000000..d3c8747d --- /dev/null +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -0,0 +1,537 @@ +#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/qrCodeUtils.h" +#include "ui/controllers/systemController.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 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 QSharedPointer &apiServicesModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_apiServicesModel(apiServicesModel), m_settings(settings) +{ +} + +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()); + 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/native_config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } + + 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; +} + +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 && errorCode != ErrorCode::ApiNotFoundError) { + emit errorOccurred(errorCode); + return false; + } + 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(); + m_vpnKey = vpnKey; + + vpnKey.replace("vpn://", ""); + + m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(vpnKey.toUtf8()); + + 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); + + 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)); + newApiConfig.insert(apiDefs::key::vpnKey, apiConfig.value(apiDefs::key::vpnKey)); + + newServerConfig.insert(configKey::apiConfig, newApiConfig); + newServerConfig.insert(configKey::authData, authData); + // newServerConfig.insert( + + 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->networkManager()->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 = apiUtils::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::deactivateDevice() +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + + 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); + + 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 && errorCode != ErrorCode::ApiNotFoundError) { + emit errorOccurred(errorCode); + return false; + } + + serverConfigObject.remove(config_key::containers); + m_serversModel->editServer(serverConfigObject, serverIndex); + + return true; +} + +bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode) +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + + 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); + + 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); + apiPayload[configKey::uuid] = uuid; + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { + emit errorOccurred(errorCode); + return false; + } + + if (uuid == m_settings->getInstallationUuid(true)) { + serverConfigObject.remove(config_key::containers); + m_serversModel->editServer(serverConfigObject, serverIndex); + } + + 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; + 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; +} + +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; +} + +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 new file mode 100644 index 00000000..2fe981e4 --- /dev/null +++ b/client/ui/controllers/api/apiConfigsController.h @@ -0,0 +1,74 @@ +#ifndef APICONFIGSCONTROLLER_H +#define APICONFIGSCONTROLLER_H + +#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 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) + 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(); + bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, + bool reloadServiceConfig = false); + bool updateServiceFromTelegram(const int serverIndex); + bool deactivateDevice(); + bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode); + + 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: + struct ApiPayloadData + { + OpenVpnConfigurator::ConnectionData certRequest; + + QString wireGuardClientPrivKey; + QString wireGuardClientPubKey; + }; + + 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(); + QString getVpnKey(); + + QList m_qrCodes; + QString m_vpnKey; + + QSharedPointer m_serversModel; + QSharedPointer m_apiServicesModel; + std::shared_ptr m_settings; +}; + +#endif // APICONFIGSCONTROLLER_H diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp new file mode 100644 index 00000000..737bfd1a --- /dev/null +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -0,0 +1,93 @@ +#include "apiSettingsController.h" + +#include +#include + +#include "core/api/apiUtils.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 QSharedPointer &apiCountryModel, + const QSharedPointer &apiDevicesModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), + m_serversModel(serversModel), + m_apiAccountInfoModel(apiAccountInfoModel), + m_apiCountryModel(apiCountryModel), + m_apiDevicesModel(apiDevicesModel), + m_settings(settings) +{ +} + +ApiSettingsController::~ApiSettingsController() +{ +} + +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(); + 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; + + 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(); + m_apiAccountInfoModel->updateModel(accountInfo, serverConfig); + + if (reload) { + updateApiCountryModel(); + updateApiDevicesModel(); + } + + return true; +} + +void ApiSettingsController::updateApiCountryModel() +{ + m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), ""); + m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo()); +} + +void ApiSettingsController::updateApiDevicesModel() +{ + m_apiDevicesModel->updateModel(m_apiAccountInfoModel->getIssuedConfigsInfo()); +} diff --git a/client/ui/controllers/api/apiSettingsController.h b/client/ui/controllers/api/apiSettingsController.h new file mode 100644 index 00000000..afe9a570 --- /dev/null +++ b/client/ui/controllers/api/apiSettingsController.h @@ -0,0 +1,37 @@ +#ifndef APISETTINGSCONTROLLER_H +#define APISETTINGSCONTROLLER_H + +#include + +#include "ui/models/api/apiAccountInfoModel.h" +#include "ui/models/api/apiCountryModel.h" +#include "ui/models/api/apiDevicesModel.h" +#include "ui/models/servers_model.h" + +class ApiSettingsController : public QObject +{ + Q_OBJECT +public: + ApiSettingsController(const QSharedPointer &serversModel, const QSharedPointer &apiAccountInfoModel, + const QSharedPointer &apiCountryModel, const QSharedPointer &apiDevicesModel, + const std::shared_ptr &settings, QObject *parent = nullptr); + ~ApiSettingsController(); + +public slots: + bool getAccountInfo(bool reload); + void updateApiCountryModel(); + void updateApiDevicesModel(); + +signals: + void errorOccurred(ErrorCode errorCode); + +private: + QSharedPointer m_serversModel; + QSharedPointer m_apiAccountInfoModel; + QSharedPointer m_apiCountryModel; + QSharedPointer m_apiDevicesModel; + + std::shared_ptr m_settings; +}; + +#endif // APISETTINGSCONTROLLER_H diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index f9491d4e..9fc60493 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/enums/apiEnums.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 == ApiConfigSources::Telegram - && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { - emit updateApiConfigFromTelegram(); - } else if (configVersion == ApiConfigSources::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) { - 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 25d4d74a..cabeb601 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/exportController.cpp b/client/ui/controllers/exportController.cpp index 8681406e..b47111ae 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 = 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 = 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 = 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"); } - qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW); - m_qrCodes << 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"); } - qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW); - m_qrCodes << 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(); - qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_nativeConfigString.toUtf8(), qrcodegen::QrCode::Ecc::LOW); - m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1))); + auto qr = qrCodeUtils::generateQrCode(m_nativeConfigString.toUtf8()); + m_qrCodes << qrCodeUtils::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 a2c9fcfa..5fb3e6b3 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -50,9 +50,6 @@ signals: 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 28bbc9f6..1bba0e8a 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -7,7 +7,10 @@ #include #include +#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" @@ -45,7 +48,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,11 +153,11 @@ 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); - QByteArray ba_uncompressed = qUncompress(ba); - if (!ba_uncompressed.isEmpty()) { - ba = ba_uncompressed; + config.replace("vpn://", ""); + QByteArray ba = QByteArray::fromBase64(config.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QByteArray baUncompressed = qUncompress(ba); + if (!baUncompressed.isEmpty()) { + ba = baUncompressed; } config = ba; @@ -180,6 +184,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); @@ -217,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; } @@ -569,7 +595,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) { @@ -680,7 +706,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/controllers/installController.cpp b/client/ui/controllers/installController.cpp index ae0804cb..7a6d8d40 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,109 +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); + if (apiUtils::isServerFromApi(serverConfigObject)) { + return true; + } + + if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { + emit noInstalledContainers(); return false; } - QJsonObject data = QJsonDocument::fromJson(responseBody).object(); - m_apiServicesModel->updateModel(data); - return true; -} + DockerContainer container = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); -bool InstallController::installServiceFromApi() -{ - if (m_serversModel->isServerFromApiAlreadyExists(m_apiServicesModel->getCountryCode(), m_apiServicesModel->getSelectedServiceType(), - m_apiServicesModel->getSelectedServiceProtocol())) { - emit installationErrorOccurred(ErrorCode::ApiConfigAlreadyAdded); + if (container == DockerContainer::None) { + emit installationErrorOccurred(ErrorCode::NoInstalledContainersError); return false; } - ApiController apiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv()); - QJsonObject serverConfig; + QSharedPointer serverController(new ServerController(m_settings)); + VpnConfigurationsController vpnConfigurationController(m_settings, serverController); - 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); - return false; - } + QJsonObject containerConfig = m_containersModel->getContainerConfig(container); + ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - 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()); + QFutureWatcher watcher; - serverConfig.insert(configKey::apiConfig, apiConfig); + QFuture future = QtConcurrent::run([this, container, &credentials, &containerConfig, &serverController]() { + ErrorCode errorCode = ErrorCode::NoError; - m_serversModel->addServer(serverConfig); - emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName())); - return true; -} + 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(); -bool InstallController::updateServiceFromApi(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, - bool reloadServiceConfig) -{ - ApiController apiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv()); + if (protocolConfig.isEmpty()) { + return false; + } + } + return true; + }; - auto serverConfig = m_serversModel->getServerConfig(serverIndex); - auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); - auto authData = serverConfig.value(configKey::authData).toObject(); + 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); - 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, - authData, newServerConfig); - if (errorCode != ErrorCode::NoError) { - emit installationErrorOccurred(errorCode); - return false; - } - - 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; -} - -void InstallController::updateServiceFromTelegram(const int serverIndex) -{ - ApiController *apiController = new ApiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv()); - - auto serverConfig = m_serversModel->getServerConfig(serverIndex); - - 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(); + errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig, + QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + } + return errorCode; }); - connect(apiController, &ApiController::errorOccurred, this, [this, apiController](ErrorCode errorCode) { + + QEventLoop wait; + connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); + watcher.setFuture(future); + wait.exec(); + + ErrorCode errorCode = watcher.result(); + + 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 d7ab3553..8e42b5b2 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 @@ public: 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 @@ signals: 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 @@ private: QSharedPointer m_containersModel; QSharedPointer m_protocolModel; QSharedPointer m_clientManagementModel; - QSharedPointer m_apiServicesModel; std::shared_ptr m_settings; diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index ffbdd3a1..60621414 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -31,6 +31,12 @@ namespace PageLoader PageSettingsLogging, PageSettingsSplitTunneling, PageSettingsAppSplitTunneling, + PageSettingsApiServerInfo, + PageSettingsApiAvailableCountries, + PageSettingsApiSupport, + PageSettingsApiInstructions, + PageSettingsApiNativeConfigs, + PageSettingsApiDevices, PageServiceSftpSettings, PageServiceTorWebsiteSettings, @@ -53,7 +59,7 @@ namespace PageLoader PageProtocolOpenVpnSettings, PageProtocolShadowSocksSettings, PageProtocolCloakSettings, - PageProtocolXraySettings, + PageProtocolXraySettings, PageProtocolWireGuardSettings, PageProtocolAwgSettings, PageProtocolIKev2Settings, @@ -104,7 +110,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 00000000..cdb9dfe8 --- /dev/null +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -0,0 +1,143 @@ +#include "apiAccountInfoModel.h" + +#include + +#include "core/api/apiUtils.h" +#include "logger.h" + +namespace +{ + Logger logger("AccountInfoModel"); +} + +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: { + 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: { + 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."); + } + } + case IsComponentVisibleRole: { + return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2; + } + case HasExpiredWorkerRole: { + for (int i = 0; i < m_issuedConfigsInfo.size(); i++) { + QJsonObject issuedConfigObject = m_issuedConfigsInfo.at(i).toObject(); + + auto lastDownloaded = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::lastDownloaded).toString()); + auto workerLastUpdated = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString()); + + if (lastDownloaded < workerLastUpdated) { + return true; + } + } + return false; + } + } + + return QVariant(); +} + +void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig) +{ + beginResetModel(); + + AccountInfoData accountInfoData; + + m_availableCountries = accountInfoObject.value(apiDefs::key::availableCountries).toArray(); + m_issuedConfigsInfo = accountInfoObject.value(apiDefs::key::issuedConfigs).toArray(); + + 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); + + 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 {}; +} + +QJsonArray ApiAccountInfoModel::getAvailableCountries() +{ + return m_availableCountries; +} + +QJsonArray ApiAccountInfoModel::getIssuedConfigsInfo() +{ + return m_issuedConfigsInfo; +} + +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; + roles[SubscriptionStatusRole] = "subscriptionStatus"; + roles[EndDateRole] = "endDate"; + roles[ConnectedDevicesRole] = "connectedDevices"; + roles[ServiceDescriptionRole] = "serviceDescription"; + roles[IsComponentVisibleRole] = "isComponentVisible"; + roles[HasExpiredWorkerRole] = "hasExpiredWorker"; + + return roles; +} diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h new file mode 100644 index 00000000..44eb7ee6 --- /dev/null +++ b/client/ui/models/api/apiAccountInfoModel.h @@ -0,0 +1,56 @@ +#ifndef APIACCOUNTINFOMODEL_H +#define APIACCOUNTINFOMODEL_H + +#include +#include +#include + +#include "core/api/apiDefs.h" + +class ApiAccountInfoModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + SubscriptionStatusRole = Qt::UserRole + 1, + ConnectedDevicesRole, + ServiceDescriptionRole, + EndDateRole, + IsComponentVisibleRole, + HasExpiredWorkerRole + }; + + 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); + + QJsonArray getAvailableCountries(); + QJsonArray getIssuedConfigsInfo(); + QString getTelegramBotLink(); + +protected: + QHash roleNames() const override; + +private: + struct AccountInfoData + { + QString subscriptionEndDate; + int activeDeviceCount; + int maxDeviceCount; + + apiDefs::ConfigType configType; + }; + + 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 new file mode 100644 index 00000000..12f4658e --- /dev/null +++ b/client/ui/models/api/apiCountryModel.cpp @@ -0,0 +1,122 @@ +#include "apiCountryModel.h" + +#include + +#include "core/api/apiDefs.h" +#include "logger.h" + +namespace +{ + Logger logger("ApiCountryModel"); + + constexpr QLatin1String countryConfig("country_config"); +} + +ApiCountryModel::ApiCountryModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int ApiCountryModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_countries.size(); +} + +QVariant ApiCountryModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) + return QVariant(); + + CountryInfo countryInfo = m_countries.at(index.row()); + IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.value(countryInfo.countryCode); + bool isIssued = issuedConfigInfo.sourceType == countryConfig; + + switch (role) { + case CountryCodeRole: { + return countryInfo.countryCode; + } + case CountryNameRole: { + return countryInfo.countryName; + } + case CountryImageCodeRole: { + return countryInfo.countryCode.toUpper(); + } + case IsIssuedRole: { + return isIssued; + } + case IsWorkerExpiredRole: { + return issuedConfigInfo.lastDownloaded < issuedConfigInfo.workerLastUpdated; + } + } + + return QVariant(); +} + +void ApiCountryModel::updateModel(const QJsonArray &countries, const QString ¤tCountryCode) +{ + beginResetModel(); + + 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); + } + m_countries.push_back(countryInfo); + } + + endResetModel(); +} + +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(); + 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(); +} + +int ApiCountryModel::getCurrentIndex() +{ + return m_currentIndex; +} + +void ApiCountryModel::setCurrentIndex(const int i) +{ + m_currentIndex = i; + emit currentIndexChanged(m_currentIndex); +} + +QHash ApiCountryModel::roleNames() const +{ + QHash roles; + roles[CountryNameRole] = "countryName"; + roles[CountryCodeRole] = "countryCode"; + roles[CountryImageCodeRole] = "countryImageCode"; + roles[IsIssuedRole] = "isIssued"; + roles[IsWorkerExpiredRole] = "isWorkerExpired"; + return roles; +} diff --git a/client/ui/models/apiCountryModel.h b/client/ui/models/api/apiCountryModel.h similarity index 57% rename from client/ui/models/apiCountryModel.h rename to client/ui/models/api/apiCountryModel.h index b9e243d0..08ac3685 100644 --- a/client/ui/models/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,9 @@ public: enum Roles { CountryNameRole = Qt::UserRole + 1, CountryCodeRole, - CountryImageCodeRole + CountryImageCodeRole, + IsIssuedRole, + IsWorkerExpiredRole }; explicit ApiCountryModel(QObject *parent = nullptr); @@ -24,7 +27,8 @@ public: 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); + void updateIssuedConfigsInfo(const QJsonArray &issuedConfigs); int getCurrentIndex(); void setCurrentIndex(const int i); @@ -36,7 +40,23 @@ protected: 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; }; diff --git a/client/ui/models/api/apiDevicesModel.cpp b/client/ui/models/api/apiDevicesModel.cpp new file mode 100644 index 00000000..6c0d60d0 --- /dev/null +++ b/client/ui/models/api/apiDevicesModel.cpp @@ -0,0 +1,90 @@ +#include "apiDevicesModel.h" + +#include + +#include "core/api/apiDefs.h" +#include "logger.h" + +namespace +{ + Logger logger("ApiDevicesModel"); + + constexpr QLatin1String gatewayAccount("gateway_account"); +} + +ApiDevicesModel::ApiDevicesModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) +{ +} + +int ApiDevicesModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_issuedConfigs.size(); +} + +QVariant ApiDevicesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) + return QVariant(); + + IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.at(index.row()); + + switch (role) { + case OsVersionRole: { + return issuedConfigInfo.osVersion; + } + case SupportTagRole: { + return issuedConfigInfo.installationUuid; + } + case CountryCodeRole: { + return issuedConfigInfo.countryCode; + } + case LastUpdateRole: { + return QDateTime::fromString(issuedConfigInfo.lastDownloaded, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); + } + case IsCurrentDeviceRole: { + return issuedConfigInfo.installationUuid == m_settings->getInstallationUuid(false); + } + } + + return QVariant(); +} + +void ApiDevicesModel::updateModel(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() != gatewayAccount) { + 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(); + issuedConfigInfo.sourceType = issuedConfigObject.value(apiDefs::key::sourceType).toString(); + issuedConfigInfo.osVersion = issuedConfigObject.value(apiDefs::key::osVersion).toString(); + + issuedConfigInfo.countryName = issuedConfigObject.value(apiDefs::key::serverCountryName).toString(); + issuedConfigInfo.countryCode = issuedConfigObject.value(apiDefs::key::serverCountryCode).toString(); + + m_issuedConfigs.push_back(issuedConfigInfo); + } + + endResetModel(); +} + +QHash ApiDevicesModel::roleNames() const +{ + QHash roles; + roles[OsVersionRole] = "osVersion"; + roles[SupportTagRole] = "supportTag"; + roles[CountryCodeRole] = "countryCode"; + roles[LastUpdateRole] = "lastUpdate"; + roles[IsCurrentDeviceRole] = "isCurrentDevice"; + return roles; +} diff --git a/client/ui/models/api/apiDevicesModel.h b/client/ui/models/api/apiDevicesModel.h new file mode 100644 index 00000000..e6a59dba --- /dev/null +++ b/client/ui/models/api/apiDevicesModel.h @@ -0,0 +1,52 @@ +#ifndef APIDEVICESMODEL_H +#define APIDEVICESMODEL_H + +#include +#include +#include + +#include "settings.h" + +class ApiDevicesModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + OsVersionRole = Qt::UserRole + 1, + SupportTagRole, + CountryCodeRole, + LastUpdateRole, + IsCurrentDeviceRole + }; + + explicit ApiDevicesModel(std::shared_ptr settings, 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 QJsonArray &issuedConfigs); + +protected: + QHash roleNames() const override; + +private: + struct IssuedConfigInfo + { + QString installationUuid; + QString workerLastUpdated; + QString lastDownloaded; + QString sourceType; + QString osVersion; + + QString countryName; + QString countryCode; + }; + + QVector m_issuedConfigs; + + std::shared_ptr m_settings; +}; +#endif // APIDEVICESMODEL_H diff --git a/client/ui/models/apiServicesModel.cpp b/client/ui/models/api/apiServicesModel.cpp similarity index 88% rename from client/ui/models/apiServicesModel.cpp rename to client/ui/models/api/apiServicesModel.cpp index 35322f7d..f1880e4d 100644 --- a/client/ui/models/apiServicesModel.cpp +++ b/client/ui/models/api/apiServicesModel.cpp @@ -65,11 +65,11 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const case CardDescriptionRole: { auto speed = apiServiceData.serviceInfo.speed; if (serviceType == serviceType::amneziaPremium) { - return tr("Classic VPN for comfortable work, downloading large files and watching videos. " - "Works for any sites. Speed up to %1 MBit/s") + return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. " + "Works for any sites with no restrictions. Speed up to %1 MBit/s. Unlimited traffic.") .arg(speed); } else if (serviceType == serviceType::amneziaFree) { - QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. "); + QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan."); if (!isServiceAvailable) { description += tr("

Not available in your region. If you have VPN enabled, disable it, " "return to the previous screen, and try again."); @@ -79,11 +79,10 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const } case ServiceDescriptionRole: { if (serviceType == serviceType::amneziaPremium) { - return tr("Amnezia Premium - A classic VPN for comfortable work, downloading large files, and watching videos in high " - "resolution. " - "It works for all websites, even in countries with the highest level of internet censorship."); + return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. " + "Works for any sites with no restrictions."); } else { - return tr("Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorship"); + return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan."); } } case IsServiceAvailableRole: { @@ -146,13 +145,6 @@ void ApiServicesModel::updateModel(const QJsonObject &data) } else { for (const auto &service : services) { auto serviceObject = service.toObject(); - -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - if (serviceObject.value(configKey::serviceType).toString() == serviceType::amneziaPremium) { - continue; - } -#endif - m_services.push_back(getApiServicesData(serviceObject)); } } @@ -255,7 +247,7 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs serviceData.type = serviceType; serviceData.protocol = serviceProtocol; - serviceData.storeEndpoint = serviceInfo.value(configKey::storeEndpoint).toString(); + serviceData.storeEndpoint = data.value(configKey::storeEndpoint).toString(); if (data.value(configKey::isAvailable).isBool()) { serviceData.isServiceAvailable = data.value(configKey::isAvailable).toBool(); 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/apiCountryModel.cpp b/client/ui/models/apiCountryModel.cpp deleted file mode 100644 index 922a9d56..00000000 --- a/client/ui/models/apiCountryModel.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "apiCountryModel.h" - -#include - -#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) -{ -} - -int ApiCountryModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - return m_countries.size(); -} - -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(); - - switch (role) { - case CountryCodeRole: { - return countryInfo.value(configKey::serverCountryCode).toString(); - } - case CountryNameRole: { - return countryInfo.value(configKey::serverCountryName).toString(); - } - case CountryImageCodeRole: { - return countryInfo.value(configKey::serverCountryCode).toString().toUpper(); - } - } - - return QVariant(); -} - -void ApiCountryModel::updateModel(const QJsonArray &data, const QString ¤tCountryCode) -{ - beginResetModel(); - - m_countries = data; - for (int i = 0; i < m_countries.size(); i++) { - if (m_countries.at(i).toObject().value(configKey::serverCountryCode).toString() == currentCountryCode) { - m_currentIndex = i; - emit currentIndexChanged(m_currentIndex); - break; - } - } - - endResetModel(); -} - -int ApiCountryModel::getCurrentIndex() -{ - return m_currentIndex; -} - -void ApiCountryModel::setCurrentIndex(const int i) -{ - m_currentIndex = i; - emit currentIndexChanged(m_currentIndex); -} - -QHash ApiCountryModel::roleNames() const -{ - QHash roles; - roles[CountryNameRole] = "countryName"; - roles[CountryCodeRole] = "countryCode"; - roles[CountryImageCodeRole] = "countryImageCode"; - return roles; -} diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index fe6f7a6c..0041fdd0 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -108,7 +108,7 @@ QString LanguageModel::getCurrentSiteUrl() { auto language = static_cast(getCurrentLanguageIndex()); switch (language) { - case LanguageSettings::AvailableLanguageEnum::Russian: return "https://storage.googleapis.com/kldscp/amnezia.org"; + case LanguageSettings::AvailableLanguageEnum::Russian: return "https://storage.googleapis.com/amnezia/amnezia.org"; default: return "https://amnezia.org"; } } diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index b72b10c3..7cde28b4 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; @@ -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 78bc22cc..4b790c7a 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -140,7 +140,7 @@ signals: void defaultServerContainersUpdated(const QJsonArray &containers); void defaultServerDefaultContainerChanged(const int containerIndex); - void updateApiLanguageModel(); + void updateApiCountryModel(); void updateApiServicesModel(); private: diff --git a/client/ui/qml/Components/InstalledAppsDrawer.qml b/client/ui/qml/Components/InstalledAppsDrawer.qml index 5835e1c6..ce8ef837 100644 --- a/client/ui/qml/Components/InstalledAppsDrawer.qml +++ b/client/ui/qml/Components/InstalledAppsDrawer.qml @@ -135,7 +135,7 @@ DrawerType2 { backgroundColor: AmneziaStyle.color.slateGray - textFieldPlaceholderText: qsTr("application name") + textField.placeholderText: qsTr("application name") } BasicButtonType { diff --git a/client/ui/qml/Components/RenameServerDrawer.qml b/client/ui/qml/Components/RenameServerDrawer.qml new file mode 100644 index 00000000..d65b9bba --- /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 dc5c5a33..d0567a8c 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -110,7 +110,24 @@ ListView { onClicked: function() { ServersModel.processedIndex = index - PageController.goToPage(PageEnum.PageSettingsServerInfo) + + if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { + if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { + PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) + } else { + PageController.showBusyIndicator(true) + let result = ApiSettingsController.getAccountInfo(false) + PageController.showBusyIndicator(false) + if (!result) { + return + } + + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) + } + } else { + PageController.goToPage(PageEnum.PageSettingsServerInfo) + } + drawer.closeTriggered() } } diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index f98944f0..dd59180b 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -22,7 +22,9 @@ DrawerType2 { property string headerText property string configContentHeaderText - property string contentVisible + property string shareButtonText: qsTr("Share") + property string copyButtonText: qsTr("Copy") + property bool isSelfHostedConfig: true property string configExtension: ".vpn" property string configCaption: qsTr("Save AmneziaVPN config") @@ -71,8 +73,6 @@ DrawerType2 { header: ColumnLayout { width: listView.width - visible: root.contentVisible - BasicButtonType { id: shareButton Layout.fillWidth: true @@ -80,7 +80,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 +116,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 +153,8 @@ DrawerType2 { Layout.leftMargin: 16 Layout.rightMargin: 16 + visible: root.isSelfHostedConfig + defaultColor: AmneziaStyle.color.transparent hoveredColor: AmneziaStyle.color.translucentWhite pressedColor: AmneziaStyle.color.sheerWhite @@ -283,6 +285,8 @@ DrawerType2 { delegate: ColumnLayout { width: listView.width + property bool isQrCodeVisible: root.isSelfHostedConfig ? ExportController.qrCodesCount > 0 : ApiConfigsController.qrCodesCount > 0 + Rectangle { id: qrCodeContainer @@ -292,7 +296,7 @@ DrawerType2 { Layout.leftMargin: 16 Layout.rightMargin: 16 - visible: ExportController.qrCodesCount > 0 + visible: isQrCodeVisible color: "white" @@ -300,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 @@ -331,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] } } } @@ -357,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/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewType.qml new file mode 100644 index 00000000..0de43d77 --- /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/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index fbea618b..c4ed91b3 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -22,11 +22,9 @@ Item { property var clickedFunc property alias textField: textField - property alias textFieldText: textField.text property string textFieldTextColor: AmneziaStyle.color.paleGray property string textFieldTextDisabledColor: AmneziaStyle.color.mutedGray - property string textFieldPlaceholderText property bool textFieldEditable: true property string borderColor: AmneziaStyle.color.slateGray @@ -101,7 +99,6 @@ Item { inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText - placeholderText: root.textFieldPlaceholderText placeholderTextColor: AmneziaStyle.color.charcoalGray selectionColor: AmneziaStyle.color.richBrown @@ -129,8 +126,8 @@ Item { } onActiveFocusChanged: { - if (checkEmptyText && textFieldText === "") { - errorText = qsTr("The field can't be empty") + if (root.checkEmptyText && text === "") { + root.errorText = qsTr("The field can't be empty") } } diff --git a/client/ui/qml/Modules/Style/AmneziaStyle.qml b/client/ui/qml/Modules/Style/AmneziaStyle.qml index f54fefce..4e2e80f0 100644 --- a/client/ui/qml/Modules/Style/AmneziaStyle.qml +++ b/client/ui/qml/Modules/Style/AmneziaStyle.qml @@ -27,5 +27,6 @@ QtObject { readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8) readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65) readonly property color pearlGray: '#EAEAEC' + readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26) } } diff --git a/client/ui/qml/Pages2/PageDevMenu.qml b/client/ui/qml/Pages2/PageDevMenu.qml index d93e5a38..d7afde1d 100644 --- a/client/ui/qml/Pages2/PageDevMenu.qml +++ b/client/ui/qml/Pages2/PageDevMenu.qml @@ -66,18 +66,18 @@ PageType { Layout.leftMargin: 16 headerText: qsTr("Gateway endpoint") - textFieldText: SettingsController.gatewayEndpoint + textField.text: SettingsController.gatewayEndpoint - buttonImageSource: textFieldText !== "" ? "qrc:/images/controls/refresh-cw.svg" : "" + buttonImageSource: textField.text !== "" ? "qrc:/images/controls/refresh-cw.svg" : "" clickedFunc: function() { SettingsController.resetGatewayEndpoint() } textField.onEditingFinished: { - textFieldText = textField.text.replace(/^\s+|\s+$/g, '') - if (textFieldText !== SettingsController.gatewayEndpoint) { - SettingsController.gatewayEndpoint = textFieldText + textField.text = textField.text.replace(/^\s+|\s+$/g, '') + if (textField.text !== SettingsController.gatewayEndpoint) { + SettingsController.gatewayEndpoint = textField.text } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index ae29b80c..f7233a89 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -297,7 +297,23 @@ PageType { onClicked: { ServersModel.processedIndex = ServersModel.defaultIndex - PageController.goToPage(PageEnum.PageSettingsServerInfo) + + if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { + if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { + PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) + } else { + PageController.showBusyIndicator(true) + let result = ApiSettingsController.getAccountInfo(false) + PageController.showBusyIndicator(false) + if (!result) { + return + } + + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) + } + } else { + PageController.goToPage(PageEnum.PageSettingsServerInfo) + } } } } diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml index d31f63e3..c22fdf0c 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml @@ -103,12 +103,12 @@ PageType { Layout.topMargin: 40 headerText: qsTr("MTU") - textFieldText: clientMtu + textField.text: clientMtu textField.validator: IntValidator { bottom: 576; top: 65535 } textField.onEditingFinished: { - if (textFieldText !== clientMtu) { - clientMtu = textFieldText + if (textField.text !== clientMtu) { + clientMtu = textField.text } } checkEmptyText: true @@ -121,12 +121,12 @@ PageType { Layout.topMargin: 16 headerText: "Jc - Junk packet count" - textFieldText: clientJunkPacketCount + textField.text: clientJunkPacketCount textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== clientJunkPacketCount) { - clientJunkPacketCount = textFieldText + if (textField.text !== clientJunkPacketCount) { + clientJunkPacketCount = textField.text } } @@ -141,12 +141,12 @@ PageType { Layout.topMargin: 16 headerText: "Jmin - Junk packet minimum size" - textFieldText: clientJunkPacketMinSize + textField.text: clientJunkPacketMinSize textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== clientJunkPacketMinSize) { - clientJunkPacketMinSize = textFieldText + if (textField.text !== clientJunkPacketMinSize) { + clientJunkPacketMinSize = textField.text } } @@ -161,12 +161,12 @@ PageType { Layout.topMargin: 16 headerText: "Jmax - Junk packet maximum size" - textFieldText: clientJunkPacketMaxSize + textField.text: clientJunkPacketMaxSize textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== clientJunkPacketMaxSize) { - clientJunkPacketMaxSize = textFieldText + if (textField.text !== clientJunkPacketMaxSize) { + clientJunkPacketMaxSize = textField.text } } @@ -189,7 +189,7 @@ PageType { enabled: false headerText: qsTr("Port") - textFieldText: port + textField.text: port } TextFieldWithHeaderType { @@ -200,7 +200,7 @@ PageType { enabled: false headerText: "S1 - Init packet junk size" - textFieldText: serverInitPacketJunkSize + textField.text: serverInitPacketJunkSize } TextFieldWithHeaderType { @@ -211,7 +211,7 @@ PageType { enabled: false headerText: "S2 - Response packet junk size" - textFieldText: serverResponsePacketJunkSize + textField.text: serverResponsePacketJunkSize } TextFieldWithHeaderType { @@ -222,7 +222,7 @@ PageType { enabled: false headerText: "H1 - Init packet magic header" - textFieldText: serverInitPacketMagicHeader + textField.text: serverInitPacketMagicHeader } TextFieldWithHeaderType { @@ -233,7 +233,7 @@ PageType { enabled: false headerText: "H2 - Response packet magic header" - textFieldText: serverResponsePacketMagicHeader + textField.text: serverResponsePacketMagicHeader } TextFieldWithHeaderType { @@ -244,7 +244,7 @@ PageType { enabled: false headerText: "H3 - Underload packet magic header" - textFieldText: serverUnderloadPacketMagicHeader + textField.text: serverUnderloadPacketMagicHeader } TextFieldWithHeaderType { @@ -255,7 +255,7 @@ PageType { enabled: false headerText: "H4 - Transport packet magic header" - textFieldText: serverTransportPacketMagicHeader + textField.text: serverTransportPacketMagicHeader } } } diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index 3594cd9d..8c629b68 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -106,11 +106,11 @@ PageType { enabled: delegateItem.isEnabled headerText: qsTr("VPN address subnet") - textFieldText: subnetAddress + textField.text: subnetAddress textField.onEditingFinished: { - if (textFieldText !== subnetAddress) { - subnetAddress = textFieldText + if (textField.text !== subnetAddress) { + subnetAddress = textField.text } } @@ -125,13 +125,13 @@ PageType { enabled: delegateItem.isEnabled headerText: qsTr("Port") - textFieldText: port + textField.text: port textField.maximumLength: 5 textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textField.text !== port) { + port = textField.text } } @@ -144,16 +144,16 @@ PageType { Layout.topMargin: 16 headerText: qsTr("Jc - Junk packet count") - textFieldText: serverJunkPacketCount + textField.text: serverJunkPacketCount textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText === "") { - textFieldText = "0" + if (textField.text === "") { + textField.text = "0" } - if (textFieldText !== serverJunkPacketCount) { - serverJunkPacketCount = textFieldText + if (textField.text !== serverJunkPacketCount) { + serverJunkPacketCount = textField.text } } @@ -166,12 +166,12 @@ PageType { Layout.topMargin: 16 headerText: qsTr("Jmin - Junk packet minimum size") - textFieldText: serverJunkPacketMinSize + textField.text: serverJunkPacketMinSize textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== serverJunkPacketMinSize) { - serverJunkPacketMinSize = textFieldText + if (textField.text !== serverJunkPacketMinSize) { + serverJunkPacketMinSize = textField.text } } @@ -184,12 +184,12 @@ PageType { Layout.topMargin: 16 headerText: qsTr("Jmax - Junk packet maximum size") - textFieldText: serverJunkPacketMaxSize + textField.text: serverJunkPacketMaxSize textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== serverJunkPacketMaxSize) { - serverJunkPacketMaxSize = textFieldText + if (textField.text !== serverJunkPacketMaxSize) { + serverJunkPacketMaxSize = textField.text } } @@ -202,12 +202,12 @@ PageType { Layout.topMargin: 16 headerText: qsTr("S1 - Init packet junk size") - textFieldText: serverInitPacketJunkSize + textField.text: serverInitPacketJunkSize textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== serverInitPacketJunkSize) { - serverInitPacketJunkSize = textFieldText + if (textField.text !== serverInitPacketJunkSize) { + serverInitPacketJunkSize = textField.text } } @@ -226,12 +226,12 @@ PageType { Layout.topMargin: 16 headerText: qsTr("S2 - Response packet junk size") - textFieldText: serverResponsePacketJunkSize + textField.text: serverResponsePacketJunkSize textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== serverResponsePacketJunkSize) { - serverResponsePacketJunkSize = textFieldText + if (textField.text !== serverResponsePacketJunkSize) { + serverResponsePacketJunkSize = textField.text } } @@ -250,12 +250,12 @@ PageType { Layout.topMargin: 16 headerText: qsTr("H1 - Init packet magic header") - textFieldText: serverInitPacketMagicHeader + textField.text: serverInitPacketMagicHeader textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== serverInitPacketMagicHeader) { - serverInitPacketMagicHeader = textFieldText + if (textField.text !== serverInitPacketMagicHeader) { + serverInitPacketMagicHeader = textField.text } } @@ -268,12 +268,12 @@ PageType { Layout.topMargin: 16 headerText: qsTr("H2 - Response packet magic header") - textFieldText: serverResponsePacketMagicHeader + textField.text: serverResponsePacketMagicHeader textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== serverResponsePacketMagicHeader) { - serverResponsePacketMagicHeader = textFieldText + if (textField.text !== serverResponsePacketMagicHeader) { + serverResponsePacketMagicHeader = textField.text } } @@ -286,12 +286,12 @@ PageType { Layout.topMargin: 16 headerText: qsTr("H4 - Transport packet magic header") - textFieldText: serverTransportPacketMagicHeader + textField.text: serverTransportPacketMagicHeader textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== serverTransportPacketMagicHeader) { - serverTransportPacketMagicHeader = textFieldText + if (textField.text !== serverTransportPacketMagicHeader) { + serverTransportPacketMagicHeader = textField.text } } @@ -304,12 +304,12 @@ PageType { Layout.topMargin: 16 headerText: qsTr("H3 - Underload packet magic header") - textFieldText: serverUnderloadPacketMagicHeader + textField.text: serverUnderloadPacketMagicHeader textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== serverUnderloadPacketMagicHeader) { - serverUnderloadPacketMagicHeader = textFieldText + if (textField.text !== serverUnderloadPacketMagicHeader) { + serverUnderloadPacketMagicHeader = textField.text } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index e90325ba..686ccd7b 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -89,18 +89,18 @@ PageType { Layout.topMargin: 32 headerText: qsTr("Disguised as traffic from") - textFieldText: site + textField.text: site textField.onEditingFinished: { - if (textFieldText !== site) { - var tmpText = textFieldText + if (textField.text !== site) { + var tmpText = textField.text tmpText = tmpText.toLocaleLowerCase() var indexHttps = tmpText.indexOf("https://") if (indexHttps === 0) { - tmpText = textFieldText.substring(8) + tmpText = textField.text.substring(8) } else { - site = textFieldText + site = textField.text } } } @@ -113,13 +113,13 @@ PageType { Layout.topMargin: 16 headerText: qsTr("Port") - textFieldText: port + textField.text: port textField.maximumLength: 5 textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textField.text !== port) { + port = textField.text } } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 6c5ad23f..9cc628b7 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -88,13 +88,13 @@ PageType { Layout.topMargin: 32 headerText: qsTr("VPN address subnet") - textFieldText: subnetAddress + textField.text: subnetAddress parentFlickable: fl textField.onEditingFinished: { - if (textFieldText !== subnetAddress) { - subnetAddress = textFieldText + if (textField.text !== subnetAddress) { + subnetAddress = textField.text } } } @@ -137,13 +137,13 @@ PageType { enabled: isPortEditable headerText: qsTr("Port") - textFieldText: port + textField.text: port textField.maximumLength: 5 textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textField.text !== port) { + port = textField.text } } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 44cbd1ce..5786012b 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -93,13 +93,13 @@ PageType { enabled: isPortEditable headerText: qsTr("Port") - textFieldText: port + textField.text: port textField.maximumLength: 5 textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textField.text !== port) { + port = textField.text } } } diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml index 7413df38..a30c17e7 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml @@ -97,12 +97,12 @@ PageType { Layout.topMargin: 40 headerText: qsTr("MTU") - textFieldText: clientMtu + textField.text: clientMtu textField.validator: IntValidator { bottom: 576; top: 65535 } textField.onEditingFinished: { - if (textFieldText !== clientMtu) { - clientMtu = textFieldText + if (textField.text !== clientMtu) { + clientMtu = textField.text } } checkEmptyText: true @@ -124,7 +124,7 @@ PageType { enabled: false headerText: qsTr("Port") - textFieldText: port + textField.text: port } } } diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml index f83d97fd..10523b74 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml @@ -90,11 +90,11 @@ PageType { enabled: delegateItem.isEnabled headerText: qsTr("VPN address subnet") - textFieldText: subnetAddress + textField.text: subnetAddress textField.onEditingFinished: { - if (textFieldText !== subnetAddress) { - subnetAddress = textFieldText + if (textField.text !== subnetAddress) { + subnetAddress = textField.text } } @@ -109,13 +109,13 @@ PageType { enabled: delegateItem.isEnabled headerText: qsTr("Port") - textFieldText: port + textField.text: port textField.maximumLength: 5 textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textField.text !== port) { + port = textField.text } } diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index 6d2ad3d1..90705d3e 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -86,18 +86,18 @@ PageType { Layout.topMargin: 32 headerText: qsTr("Disguised as traffic from") - textFieldText: site + textField.text: site textField.onEditingFinished: { - if (textFieldText !== site) { - var tmpText = textFieldText + if (textField.text !== site) { + var tmpText = textField.text tmpText = tmpText.toLocaleLowerCase() var indexHttps = tmpText.indexOf("https://") if (indexHttps === 0) { - tmpText = textFieldText.substring(8) + tmpText = textField.text.substring(8) } else { - site = textFieldText + site = textField.text } } } diff --git a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml index 5eee9a1e..1b77267a 100644 --- a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml +++ b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml @@ -211,9 +211,9 @@ PageType { port = tempPort username = tempUsername password = tempPassword - portTextField.textFieldText = port - usernameTextField.textFieldText = username - passwordTextField.textFieldText = password + portTextField.textField.text = port + usernameTextField.textField.text = username + passwordTextField.textField.text = password } } @@ -231,14 +231,14 @@ PageType { parentFlickable: fl headerText: qsTr("Port") - textFieldText: port + textField.text: port textField.maximumLength: 5 textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { - textFieldText = textField.text.replace(/^\s+|\s+$/g, '') - if (textFieldText !== port) { - port = textFieldText + textField.text = textField.text.replace(/^\s+|\s+$/g, '') + if (textField.text !== port) { + port = textField.text } } } @@ -251,14 +251,14 @@ PageType { parentFlickable: fl headerText: qsTr("Username") - textFieldPlaceholderText: "username" - textFieldText: username + textField.placeholderText: "username" + textField.text: username textField.maximumLength: 32 textField.onEditingFinished: { - textFieldText = textField.text.replace(/^\s+|\s+$/g, '') - if (textFieldText !== username) { - username = textFieldText + textField.text = textField.text.replace(/^\s+|\s+$/g, '') + if (textField.text !== username) { + username = textField.text } } } @@ -273,12 +273,12 @@ PageType { parentFlickable: fl headerText: qsTr("Password") - textFieldPlaceholderText: "password" - textFieldText: password + textField.placeholderText: "password" + textField.text: password textField.maximumLength: 32 textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal - buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") + buttonImageSource: textField.text !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" clickedFunc: function() { @@ -286,9 +286,9 @@ PageType { } textField.onFocusChanged: { - textFieldText = textField.text.replace(/^\s+|\s+$/g, '') - if (textFieldText !== password) { - password = textFieldText + textField.text = textField.text.replace(/^\s+|\s+$/g, '') + if (textField.text !== password) { + password = textField.text } } } @@ -309,19 +309,19 @@ PageType { portTextField.errorText = qsTr("The port must be in the range of 1 to 65535") return } - if (usernameTextField.textFieldText && passwordTextField.textFieldText === "") { + if (usernameTextField.textField.text && passwordTextField.textField.text === "") { passwordTextField.errorText = qsTr("Password cannot be empty") return - } else if (usernameTextField.textFieldText === "" && passwordTextField.textFieldText) { + } else if (usernameTextField.textField.text === "" && passwordTextField.textField.text) { usernameTextField.errorText = qsTr("Username cannot be empty") return } PageController.goToPage(PageEnum.PageSetupWizardInstalling) InstallController.updateContainer(Socks5ProxyConfigModel.getConfig()) - tempPort = portTextField.textFieldText - tempUsername = usernameTextField.textFieldText - tempPassword = passwordTextField.textFieldText + tempPort = portTextField.textField.text + tempUsername = usernameTextField.textField.text + tempPassword = passwordTextField.textField.text changeSettingsDrawer.closeTriggered() } } diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index c44e52ae..a47bb535 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -87,7 +87,6 @@ PageType { LabelWithButtonType { id: backup - visible: !SettingsController.isOnTv() Layout.fillWidth: true text: qsTr("Backup") @@ -99,9 +98,7 @@ PageType { } } - DividerType { - visible: !SettingsController.isOnTv() - } + DividerType {} LabelWithButtonType { id: about diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 2160692b..37327313 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/PageSettingsApiLanguageList.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml similarity index 57% rename from client/ui/qml/Pages2/PageSettingsApiLanguageList.qml rename to client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index 30968b38..43fbb160 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,85 @@ import "../Components" PageType { id: root - ListView { + 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) + } + } + + ListViewType { id: menuContent - property bool isFocusable: true + anchors.fill: parent - width: parent.width - height: parent.height - - clip: true - interactive: true 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: qsTr("Locations for connection") + + actionButtonFunction: function() { + PageController.showBusyIndicator(true) + let result = ApiSettingsController.getAccountInfo(false) + PageController.showBusyIndicator(false) + if (!result) { + return + } + + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) + } + } + } + delegate: ColumnLayout { id: content @@ -63,9 +128,10 @@ 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 } + PageController.showBusyIndicator(false) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiDevices.qml b/client/ui/qml/Pages2/PageSettingsApiDevices.qml new file mode 100644 index 00000000..5cc21d07 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApiDevices.qml @@ -0,0 +1,100 @@ +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 + + ListViewType { + id: listView + + anchors.fill: parent + anchors.topMargin: 20 + anchors.bottomMargin: 24 + + model: ApiDevicesModel + + header: ColumnLayout { + width: listView.width + + BackButtonType { + id: backButton + } + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Connected devices") + descriptionText: qsTr("To manage connected devices") + } + + WarningType { + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.fillWidth: true + + textString: qsTr("You can find the identifier on the Support tab or, for older versions of the app, " + + "by tapping '+' and then the three dots at the top of the page.") + + iconPath: "qrc:/images/controls/alert-circle.svg" + } + } + + delegate: ColumnLayout { + width: listView.width + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 6 + + text: osVersion + (isCurrentDevice ? qsTr(" (current device)") : "") + descriptionText: qsTr("Support tag: ") + "\n" + supportTag + "\n" + qsTr("Last updated: ") + lastUpdate + rightImageSource: "qrc:/images/controls/trash.svg" + + clickedFunction: function() { + var headerText = qsTr("Deactivate the subscription on selected device") + var descriptionText = qsTr("The next time the “Connect” button is pressed, the device will be activated again") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + Qt.callLater(deactivateExternalDevice, supportTag, countryCode) + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + DividerType {} + } + } + + function deactivateExternalDevice(supportTag, countryCode) { + PageController.showBusyIndicator(true) + if (ApiConfigsController.deactivateExternalDevice(supportTag, countryCode)) { + ApiSettingsController.getAccountInfo(true) + } + PageController.showBusyIndicator(false) + } +} diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml new file mode 100644 index 00000000..3651407b --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -0,0 +1,124 @@ +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 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("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#macos") + } + + QtObject { + id: android + + readonly property string title: qsTr("Android") + readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#android") + } + + QtObject { + id: androidTv + + readonly property string title: qsTr("AndroidTV") + readonly property string link: qsTr("https://docs.amnezia.org/ru/documentation/instructions/android_tv_connect/") + } + + QtObject { + id: ios + + readonly property string title: qsTr("iOS") + 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("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#linux") + } + + QtObject { + id: routers + + readonly property string title: qsTr("Routers") + readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#routers") + } + + property list instructionsModel: [ + windows, + macos, + android, + androidTv, + ios, + linux, + routers + ] + + ListViewType { + id: listView + + anchors.fill: parent + anchors.topMargin: 20 + anchors.bottomMargin: 24 + + model: instructionsModel + + header: ColumnLayout { + width: listView.width + + BackButtonType { + id: backButton + } + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("How to connect on another device") + descriptionText: qsTr("Instructions on the Amnezia website") + } + } + + delegate: ColumnLayout { + width: listView.width + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 6 + + text: title + rightImageSource: "qrc:/images/controls/external-link.svg" + + 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 00000000..a1cc1fb8 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -0,0 +1,214 @@ +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 { + Layout.fillWidth: true + Layout.topMargin: 6 + + text: countryName + descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : "" + descriptionColor: AmneziaStyle.color.vibrantRed + + leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" + rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg" + + clickedFunction: function() { + if (isIssued) { + moreOptionsDrawer.countryName = countryName + moreOptionsDrawer.countryCode = countryCode + moreOptionsDrawer.openTriggered() + } else { + 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") + + 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 {} + } + } + } + } + + 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(true) + } + + 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(true) + } + 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) + } + moreOptionsDrawer.closeTriggered() + } + 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 39207486..7d089639 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 @@ -15,107 +17,264 @@ import "../Components" PageType { id: root - FlickableType { - id: fl - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: content.height + property list labelsModel: [ + statusObject, + endDateObject, + deviceCountObject + ] - ColumnLayout { - id: content + QtObject { + id: statusObject - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + readonly property string title: qsTr("Subscription status") + readonly property string contentKey: "subscriptionStatus" + readonly property string objectImageSource: "qrc:/images/controls/info.svg" + } + QtObject { + id: endDateObject + + readonly property string title: qsTr("Valid until") + readonly property string contentKey: "endDate" + readonly property string objectImageSource: "qrc:/images/controls/history.svg" + } + + QtObject { + id: deviceCountObject + + readonly property string title: qsTr("Connected devices") + readonly property string contentKey: "connectedDevices" + readonly property string objectImageSource: "qrc:/images/controls/monitor.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) + } + } + + ListViewType { + id: listView + + anchors.fill: parent + + 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: ApiAccountInfoModel.data("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 spacing: 0 - LabelWithImageType { - Layout.fillWidth: true - Layout.margins: 16 + Connections { + target: ApiAccountInfoModel - imageSource: "qrc:/images/controls/map-pin.svg" - leftText: qsTr("For the region") - rightText: ApiServicesModel.getSelectedServiceData("region") + function onModelReset() { + delegateItem.rightText = ApiAccountInfoModel.data(contentKey) + } } LabelWithImageType { - Layout.fillWidth: true - Layout.margins: 16 - - imageSource: "qrc:/images/controls/tag.svg" - leftText: qsTr("Price") - rightText: ApiServicesModel.getSelectedServiceData("price") - } - - LabelWithImageType { - property bool showSubscriptionEndDate: ServersModel.getProcessedServerData("isCountrySelectionAvailable") + id: delegateItem Layout.fillWidth: true Layout.margins: 16 - imageSource: "qrc:/images/controls/history.svg" - leftText: showSubscriptionEndDate ? qsTr("Valid until") : qsTr("Work period") - rightText: showSubscriptionEndDate ? ApiServicesModel.getSelectedServiceData("endDate") - : ApiServicesModel.getSelectedServiceData("workPeriod") + imageSource: objectImageSource + leftText: title + rightText: ApiAccountInfoModel.data(contentKey) visible: rightText !== "" } + } - LabelWithImageType { - Layout.fillWidth: true - Layout.margins: 16 + footer: ColumnLayout { + id: footer - imageSource: "qrc:/images/controls/gauge.svg" - leftText: qsTr("Speed") - rightText: ApiServicesModel.getSelectedServiceData("speed") - } + width: listView.width + spacing: 0 - ParagraphTextType { - Layout.fillWidth: true + readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible") + + WarningType { + id: warning + + Layout.topMargin: 32 Layout.rightMargin: 16 Layout.leftMargin: 16 + Layout.fillWidth: true - 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()) - } + backGroundColor: AmneziaStyle.color.translucentRichBrown - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - } + textString: qsTr("Configurations have been updated for some countries. Download and install the updated configuration files") + + iconPath: "qrc:/images/controls/alert-circle.svg" + + visible: ApiAccountInfoModel.data("hasExpiredWorker") } LabelWithButtonType { - id: supportUuid + id: vpnKey + Layout.fillWidth: true + Layout.topMargin: warning.visible ? 16 : 32 - text: qsTr("Support tag") - descriptionText: SettingsController.getInstallationUuid() + visible: false //footer.isVisibleForAmneziaFree - descriptionOnTop: true - - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray + text: qsTr("Subscription key") + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() - } + shareConnectionDrawer.headerText = qsTr("Amnezia Premium subscription key") + + shareConnectionDrawer.openTriggered() + shareConnectionDrawer.isSelfHostedConfig = false; + shareConnectionDrawer.shareButtonText = qsTr("Save VPN key to file") + shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key") + + + PageController.showBusyIndicator(true) + + ApiConfigsController.prepareVpnKeyExport() + + PageController.showBusyIndicator(false) } } + DividerType { + visible: false //footer.isVisibleForAmneziaFree + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: warning.visible ? 16 : 32 + + visible: footer.isVisibleForAmneziaFree + + text: qsTr("Configuration files") + + descriptionText: qsTr("To connect a router or AmneziaWG application") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + ApiSettingsController.updateApiCountryModel() + PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs) + } + } + + DividerType { + visible: footer.isVisibleForAmneziaFree + } + + LabelWithButtonType { + Layout.fillWidth: true + + visible: footer.isVisibleForAmneziaFree + + text: qsTr("Connected devices") + + descriptionText: qsTr("To manage connected devices") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + ApiSettingsController.updateApiDevicesModel() + PageController.goToPage(PageEnum.PageSettingsApiDevices) + } + } + + DividerType { + visible: footer.isVisibleForAmneziaFree + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32 + + text: qsTr("Support") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsApiSupport) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("How to connect on another device") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsApiInstructions) + } + } + + DividerType {} + BasicButtonType { id: resetButton Layout.alignment: Qt.AlignHCenter @@ -141,20 +300,57 @@ 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) } } var noButtonFunction = function() { - if (!GC.isMobile()) { - removeButton.forceActiveFocus() - } } showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } + BasicButtonType { + id: revokeButton + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + visible: footer.isVisibleForAmneziaFree + + 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 descriptionText = qsTr("The next time the “Connect” button is pressed, the device will be activated again") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot deactivate subscription during active connection")) + } else { + PageController.showBusyIndicator(true) + if (ApiConfigsController.deactivateDevice()) { + ApiSettingsController.getAccountInfo(true) + } + PageController.showBusyIndicator(false) + } + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + BasicButtonType { id: removeButton Layout.alignment: Qt.AlignHCenter @@ -179,14 +375,13 @@ PageType { PageController.showNotificationMessage(qsTr("Cannot remove server during active connection")) } else { PageController.showBusyIndicator(true) - InstallController.removeProcessedServer() + if (ApiConfigsController.deactivateDevice()) { + InstallController.removeProcessedServer() + } PageController.showBusyIndicator(false) } } var noButtonFunction = function() { - if (!GC.isMobile()) { - removeButton.forceActiveFocus() - } } showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) @@ -194,4 +389,10 @@ PageType { } } } + + ShareConnectionDrawer { + id: shareConnectionDrawer + + anchors.fill: parent + } } diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml new file mode 100644 index 00000000..424e10c5 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -0,0 +1,127 @@ +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: telegram + + readonly property string title: qsTr("Telegram") + readonly property string description: "@" + ApiAccountInfoModel.getTelegramBotLink() + readonly property string link: "https://t.me/" + ApiAccountInfoModel.getTelegramBotLink() + } + + QtObject { + id: techSupport + + readonly property string title: qsTr("For technical support") + readonly property string description: qsTr("support@amnezia.org") + readonly property string link: "mailto:support@amnezia.org" + } + + QtObject { + id: paymentSupport + + readonly property string title: qsTr("For payment issues") + readonly property string description: qsTr("help@vpnpay.io") + readonly property string link: "mailto:help@vpnpay.io" + } + + QtObject { + id: site + + readonly property string title: qsTr("Site") + readonly property string description: qsTr("amnezia.org") + readonly property string link: LanguageModel.getCurrentSiteUrl() + } + + property list supportModel: [ + telegram, + techSupport, + paymentSupport, + site + ] + + ListViewType { + id: listView + + anchors.fill: parent + anchors.topMargin: 20 + anchors.bottomMargin: 24 + + model: supportModel + + header: ColumnLayout { + width: listView.width + + BackButtonType { + id: backButton + } + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Support") + descriptionText: qsTr("Our technical support specialists are ready to help you at any time") + } + } + + delegate: ColumnLayout { + width: listView.width + + LabelWithButtonType { + Layout.fillWidth: true + text: title + descriptionText: description + rightImageSource: "qrc:/images/controls/external-link.svg" + clickedFunction: function() { + Qt.openUrlExternally(link) + } + } + DividerType {} + } + + + footer: ColumnLayout { + width: listView.width + + 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/PageSettingsAppSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml index a78ae446..b6920a8f 100644 --- a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -252,7 +252,7 @@ PageType { Layout.fillWidth: true - textFieldPlaceholderText: qsTr("application name") + textField.placeholderText: qsTr("application name") buttonImageSource: "qrc:/images/controls/plus.svg" rightButtonClickedOnEnter: true diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 11e49cf9..d78c5aa8 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -67,7 +67,7 @@ PageType { Layout.fillWidth: true headerText: qsTr("Primary DNS") - textFieldText: SettingsController.primaryDns + textField.text: SettingsController.primaryDns textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressRegExp() } @@ -79,7 +79,7 @@ PageType { Layout.fillWidth: true headerText: qsTr("Secondary DNS") - textFieldText: SettingsController.secondaryDns + textField.text: SettingsController.secondaryDns textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressRegExp() } @@ -105,9 +105,9 @@ PageType { var yesButtonFunction = function() { SettingsController.primaryDns = "1.1.1.1" - primaryDns.textFieldText = SettingsController.primaryDns + primaryDns.textField.text = SettingsController.primaryDns SettingsController.secondaryDns = "1.0.0.1" - secondaryDns.textFieldText = SettingsController.secondaryDns + secondaryDns.textField.text = SettingsController.secondaryDns PageController.showNotificationMessage(qsTr("Settings have been reset")) } var noButtonFunction = function() { @@ -125,11 +125,11 @@ PageType { text: qsTr("Save") clickedFunc: function() { - if (primaryDns.textFieldText !== SettingsController.primaryDns) { - SettingsController.primaryDns = primaryDns.textFieldText + if (primaryDns.textField.text !== SettingsController.primaryDns) { + SettingsController.primaryDns = primaryDns.textField.text } - if (secondaryDns.textFieldText !== SettingsController.secondaryDns) { - SettingsController.secondaryDns = secondaryDns.textFieldText + if (secondaryDns.textField.text !== SettingsController.secondaryDns) { + SettingsController.secondaryDns = secondaryDns.textField.text } PageController.showNotificationMessage(qsTr("Settings saved")) } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 7d85e2b3..2c760e37 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -137,6 +137,8 @@ PageType { Layout.topMargin: -8 Layout.bottomMargin: -8 + visible: !GC.isMobile() + text: qsTr("Open logs folder") leftImageSource: "qrc:/images/controls/folder-open.svg" isSmallLeftImage: true diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index cd736d39..977e669e 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/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 3172d31b..d350ebef 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") - textFieldText: root.processedServer.name - textField.maximumLength: 30 - checkEmptyText: true - } - - BasicButtonType { - id: saveButton - - Layout.fillWidth: true - - text: qsTr("Save") - - clickedFunc: function() { - if (serverName.textFieldText === "") { - return - } - - if (serverName.textFieldText !== root.processedServer.name) { - ServersModel.setProcessedServerData("name", serverName.textFieldText); - } - 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 17337a48..554b6cbb 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -93,7 +93,19 @@ PageType { clickedFunction: function() { ServersModel.processedIndex = index - PageController.goToPage(PageEnum.PageSettingsServerInfo) + + if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { + PageController.showBusyIndicator(true) + let result = ApiSettingsController.getAccountInfo(false) + PageController.showBusyIndicator(false) + if (!result) { + return + } + + PageController.goToPage(PageEnum.PageSettingsApiServerInfo) + } else { + PageController.goToPage(PageEnum.PageSettingsServerInfo) + } } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 759ad9cd..f5978687 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -274,13 +274,13 @@ PageType { Layout.fillWidth: true rightButtonClickedOnEnter: true - textFieldPlaceholderText: qsTr("website or IP") + textField.placeholderText: qsTr("website or IP") buttonImageSource: "qrc:/images/controls/plus.svg" clickedFunc: function() { PageController.showBusyIndicator(true) - SitesController.addSite(textFieldText) - textFieldText = "" + SitesController.addSite(textField.text) + textField.text = "" PageController.showBusyIndicator(false) } } @@ -338,7 +338,6 @@ PageType { LabelWithButtonType { id: exportSitesButton - enabled: !SettingsController.isOnTv() Layout.fillWidth: true text: qsTr("Save site list") @@ -362,9 +361,7 @@ PageType { } } - DividerType { - enabled: !SettingsController.isOnTv() - } + DividerType {} } } diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml index 8c02195e..134e73b6 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 17d733d8..38a1da52 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -163,12 +163,12 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - visible: textKey.textFieldText !== "" + visible: textKey.textField.text !== "" text: qsTr("Continue") clickedFunc: function() { - if (ImportController.extractConfigFromData(textKey.textFieldText)) { + if (ImportController.extractConfigFromData(textKey.textField.text)) { PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } } @@ -217,6 +217,8 @@ PageType { Layout.alignment: Qt.AlignHCenter implicitHeight: 32 + visible: Qt.platform.os !== "ios" + defaultColor: AmneziaStyle.color.transparent hoveredColor: AmneziaStyle.color.translucentWhite pressedColor: AmneziaStyle.color.sheerWhite @@ -252,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) @@ -330,7 +332,7 @@ PageType { property string title: qsTr("I have nothing") property string description: qsTr("") property string imageSource: "qrc:/images/controls/help-circle.svg" - property bool isVisible: PageController.isStartPageVisible() + property bool isVisible: PageController.isStartPageVisible() && Qt.platform.os !== "ios" property var handler: function() { Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 20b85550..ca7e3a7c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -82,24 +82,19 @@ PageType { reuseItems: true delegate: ColumnLayout { - property alias textField: _textField.textField - width: listView.width TextFieldWithHeaderType { - id: _textField + id: delegate Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 - property bool hidePassword: hideText - headerText: title - textField.echoMode: hideText ? TextInput.Password : TextInput.Normal - buttonImageSource: imageSource - textFieldPlaceholderText: placeholderText - textField.text: textFieldText + textField.echoMode: hideContent ? TextInput.Password : TextInput.Normal + textField.placeholderText: placeholderContent + textField.text: textField.text rightButtonClickedOnEnter: true @@ -108,17 +103,12 @@ PageType { } textField.onFocusChanged: { - var _currentIndex = listView.currentIndex - var _currentItem = listView.itemAtIndex(_currentIndex).children[0] - listView.model[_currentIndex].textFieldText = _currentItem.textFieldText.replace(/^\s+|\s+$/g, '') + textField.text = textField.text.replace(/^\s+|\s+$/g, '') } textField.onTextChanged: { - var _currentIndex = listView.currentIndex - textFieldText = textField.text - - if (_currentIndex === vars.secretDataIndex) { - buttonImageSource = textFieldText !== "" ? (hideText ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" + if (hideContent) { + buttonImageSource = textField.text !== "" ? (hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" } } } @@ -143,9 +133,9 @@ PageType { } InstallController.setShouldCreateServer(true) - var _hostname = listView.itemAtIndex(vars.hostnameIndex).children[0].textFieldText - var _username = listView.itemAtIndex(vars.usernameIndex).children[0].textFieldText - var _secretData = listView.itemAtIndex(vars.secretDataIndex).children[0].textFieldText + var _hostname = listView.itemAtIndex(vars.hostnameIndex).children[0].textField.text + var _username = listView.itemAtIndex(vars.usernameIndex).children[0].textField.text + var _secretData = listView.itemAtIndex(vars.secretDataIndex).children[0].textField.text InstallController.setProcessedServerCredentials(_hostname, _username, _secretData) @@ -194,23 +184,23 @@ PageType { function isCredentialsFilled() { var hasEmptyField = false - var _hostname = listView.itemAtIndex(vars.hostnameIndex).children[0] - if (_hostname.textFieldText === "") { - _hostname.errorText = qsTr("Ip address cannot be empty") + var hostnameItem = listView.itemAtIndex(vars.hostnameIndex).children[0] + if (hostnameItem.textField.text === "") { + hostnameItem.errorText = qsTr("Ip address cannot be empty") hasEmptyField = true - } else if (!_hostname.textField.acceptableInput) { - _hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88") + } else if (!hostnameItem.textField.acceptableInput) { + hostnameItem.errorText = qsTr("Enter the address in the format 255.255.255.255:88") } - var _username = listView.itemAtIndex(vars.usernameIndex).children[0] - if (_username.textFieldText === "") { - _username.errorText = qsTr("Login cannot be empty") + var usernameItem = listView.itemAtIndex(vars.usernameIndex).children[0] + if (usernameItem.textField.text === "") { + usernameItem.errorText = qsTr("Login cannot be empty") hasEmptyField = true } - var _secretData = listView.itemAtIndex(vars.secretDataIndex).children[0] - if (_secretData.textFieldText === "") { - _secretData.errorText = qsTr("Password/private key cannot be empty") + var secretDataItem = listView.itemAtIndex(vars.secretDataIndex).children[0] + if (secretDataItem.textField.text === "") { + secretDataItem.errorText = qsTr("Password/private key cannot be empty") hasEmptyField = true } @@ -218,46 +208,37 @@ PageType { } property list inputFields: [ - hostname, - username, - secretData + hostnameObject, + usernameObject, + secretDataObject ] QtObject { - id: hostname + id: hostnameObject property string title: qsTr("Server IP address [:port]") - readonly property string placeholderText: qsTr("255.255.255.255:22") - property string textFieldText: "" - property bool hideText: false - property string imageSource: "" - readonly property var clickedHandler: function() { - console.debug(">>> Server IP address text field was clicked!!!") - clicked() - } - } - - QtObject { - id: username - - property string title: qsTr("SSH Username") - readonly property string placeholderText: "root" - property string textFieldText: "" - property bool hideText: false - property string imageSource: "" + readonly property string placeholderContent: qsTr("255.255.255.255:22") + property bool hideContent: false readonly property var clickedHandler: undefined } QtObject { - id: secretData + id: usernameObject + + property string title: qsTr("SSH Username") + readonly property string placeholderContent: "root" + property bool hideContent: false + readonly property var clickedHandler: undefined + } + + QtObject { + id: secretDataObject property string title: qsTr("Password or SSH private key") - readonly property string placeholderText: "" - property string textFieldText: "" - property bool hideText: true - property string imageSource: textFieldText !== "" ? (hideText ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" + readonly property string placeholderContent: "" + property bool hideContent: true readonly property var clickedHandler: function() { - hideText = !hideText + hideContent = !hideContent } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index eb6000c2..353eeb32 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -65,7 +65,7 @@ PageType { implicitWidth: parent.width headerTextMaximumLineCount: 10 - headerText: qsTr("What is the level of internet control in your region?") + headerText: qsTr("Choose Installation Type") } ButtonGroup { @@ -139,7 +139,8 @@ PageType { CardType { implicitWidth: parent.width - headerText: qsTr("Choose a VPN protocol") + headerText: qsTr("Manual") + bodyText: qsTr("Choose a VPN protocol") ButtonGroup.group: buttonGroup diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 0de4da6b..50d1ea81 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -257,7 +257,7 @@ PageType { } PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.install(dockerContainer, port.textFieldText, transportProtoSelector.currentIndex) + InstallController.install(dockerContainer, port.textField.text, transportProtoSelector.currentIndex) } } @@ -267,7 +267,7 @@ PageType { if (ProtocolProps.defaultPort(defaultContainerProto) < 0) { port.visible = false } else { - port.textFieldText = ProtocolProps.getPortForInstall(defaultContainerProto) + port.textField.text = ProtocolProps.getPortForInstall(defaultContainerProto) } transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 126a7c91..3cf154e4 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -51,7 +51,7 @@ PageType { Layout.leftMargin: 16 headerText: qsTr("Key") - textFieldPlaceholderText: "vpn://" + textField.placeholderText: "vpn://" buttonText: qsTr("Insert") clickedFunc: function() { @@ -75,7 +75,7 @@ PageType { text: qsTr("Continue") clickedFunc: function() { - if (ImportController.extractConfigFromData(textKey.textFieldText)) { + if (ImportController.extractConfigFromData(textKey.textField.text)) { PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index add4dc61..af208544 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -46,30 +46,29 @@ PageType { shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text shareConnectionDrawer.openTriggered() - shareConnectionDrawer.contentVisible = false PageController.showBusyIndicator(true) switch (type) { case PageShare.ConfigType.AmneziaConnection: { - ExportController.generateConnectionConfig(clientNameTextField.textFieldText); + ExportController.generateConnectionConfig(clientNameTextField.textField.text); break; } case PageShare.ConfigType.OpenVpn: { - ExportController.generateOpenVpnConfig(clientNameTextField.textFieldText) + ExportController.generateOpenVpnConfig(clientNameTextField.textField.text) shareConnectionDrawer.configCaption = qsTr("Save OpenVPN config") shareConnectionDrawer.configExtension = ".ovpn" shareConnectionDrawer.configFileName = "amnezia_for_openvpn" break } case PageShare.ConfigType.WireGuard: { - ExportController.generateWireGuardConfig(clientNameTextField.textFieldText) + ExportController.generateWireGuardConfig(clientNameTextField.textField.text) shareConnectionDrawer.configCaption = qsTr("Save WireGuard config") shareConnectionDrawer.configExtension = ".conf" shareConnectionDrawer.configFileName = "amnezia_for_wireguard" break } case PageShare.ConfigType.Awg: { - ExportController.generateAwgConfig(clientNameTextField.textFieldText) + ExportController.generateAwgConfig(clientNameTextField.textField.text) shareConnectionDrawer.configCaption = qsTr("Save AmneziaWG config") shareConnectionDrawer.configExtension = ".conf" shareConnectionDrawer.configFileName = "amnezia_for_awg" @@ -90,7 +89,7 @@ PageType { break } case PageShare.ConfigType.Xray: { - ExportController.generateXrayConfig(clientNameTextField.textFieldText) + ExportController.generateXrayConfig(clientNameTextField.textField.text) shareConnectionDrawer.configCaption = qsTr("Save XRay config") shareConnectionDrawer.configExtension = ".json" shareConnectionDrawer.configFileName = "amnezia_for_xray" @@ -296,7 +295,7 @@ PageType { visible: accessTypeSelector.currentIndex === 0 headerText: qsTr("User name") - textFieldText: "New client" + textField.text: "New client" textField.maximumLength: 20 checkEmptyText: true @@ -525,7 +524,7 @@ PageType { parentFlickable: a clickedFunc: function(){ - if (clientNameTextField.textFieldText !== "") { + if (clientNameTextField.textField.text !== "") { ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type) } } @@ -555,14 +554,14 @@ PageType { id: searchTextField Layout.fillWidth: true - textFieldPlaceholderText: qsTr("Search") + textField.placeholderText: qsTr("Search") Keys.onEscapePressed: { root.isSearchBarVisible = false } function navigateTo() { - if (searchTextField.textFieldText === "") { + if (searchTextField.textField.text === "") { root.isSearchBarVisible = false } } @@ -601,7 +600,7 @@ PageType { sourceModel: ClientManagementModel filters: RegExpFilter { roleName: "clientName" - pattern: ".*" + searchTextField.textFieldText + ".*" + pattern: ".*" + searchTextField.textField.text + ".*" caseSensitivity: Qt.CaseInsensitive } } @@ -765,7 +764,7 @@ PageType { id: clientNameEditor Layout.fillWidth: true headerText: qsTr("Client name") - textFieldText: clientName + textField.text: clientName textField.maximumLength: 20 checkEmptyText: true } @@ -778,14 +777,14 @@ PageType { text: qsTr("Save") clickedFunc: function() { - if (clientNameEditor.textFieldText === "") { + if (clientNameEditor.textField.text === "") { return } - if (clientNameEditor.textFieldText !== clientName) { + if (clientNameEditor.textField.text !== clientName) { PageController.showBusyIndicator(true) ExportController.renameClient(index, - clientNameEditor.textFieldText, + clientNameEditor.textField.text, ContainersModel.getProcessedContainerIndex(), ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index af409d72..70fd6292 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) } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 71009e28..0a21497d 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -132,27 +132,22 @@ PageType { PageController.showNotificationMessage(message) } - function onInstallServerFromApiFinished(message) { - PageController.showBusyIndicator(false) - if (!ConnectionController.isConnected) { - ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); - ServersModel.processedIndex = ServersModel.defaultIndex + function onRemoveProcessedServerFinished(finishedMessage) { + if (!ServersModel.getServersCount()) { + PageController.goToPageHome() + } else { + PageController.goToStartPage() + PageController.goToPage(PageEnum.PageSettingsServersList) } - - PageController.goToPageHome() - PageController.showNotificationMessage(message) + PageController.showNotificationMessage(finishedMessage) } - function onChangeApiCountryFinished(message) { - PageController.showBusyIndicator(false) + function onNoInstalledContainers() { + PageController.setTriggeredByConnectButton(true) - PageController.goToPageHome() - PageController.showNotificationMessage(message) - } - - function onReloadServerFromApiFinished(message) { - PageController.goToPageHome() - PageController.showNotificationMessage(message) + ServersModel.processedIndex = ServersModel.getDefaultServerIndex() + InstallController.setShouldCreateServer(false) + PageController.goToPage(PageEnum.PageSetupWizardEasy) } } @@ -165,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 { @@ -214,6 +201,38 @@ PageType { } } + Connections { + target: ApiSettingsController + + function onErrorOccurred(error) { + PageController.showErrorMessage(error) + } + } + + Connections { + target: ApiConfigsController + + function onInstallServerFromApiFinished(message) { + if (!ConnectionController.isConnected) { + ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); + ServersModel.processedIndex = ServersModel.defaultIndex + } + + PageController.goToPageHome() + PageController.showNotificationMessage(message) + } + + function onChangeApiCountryFinished(message) { + PageController.goToPageHome() + PageController.showNotificationMessage(message) + } + + function onReloadServerFromApiFinished(message) { + PageController.goToPageHome() + PageController.showNotificationMessage(message) + } + } + StackViewType { id: tabBarStackView objectName: "tabBarStackView" diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 8b73e62d..7cd5790b 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -176,12 +176,12 @@ Window { Connections { target: privateKeyPassphraseDrawer function onOpened() { - passphrase.textFieldText = "" + passphrase.textField.text = "" passphrase.textField.forceActiveFocus() } function onAboutToHide() { - if (passphrase.textFieldText !== "") { + if (passphrase.textField.text !== "") { PageController.showBusyIndicator(true) } } @@ -222,7 +222,7 @@ Window { clickedFunc: function() { privateKeyPassphraseDrawer.closeTriggered() - PageController.passphraseRequestDrawerClosed(passphrase.textFieldText) + PageController.passphraseRequestDrawerClosed(passphrase.textField.text) } } } diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index ac881bd7..042c51c7 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -215,10 +215,9 @@ ErrorCode VpnConnection::lastError() const void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration) { - qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is") + qDebug() << QString("Trying to connect to VPN, server index is %1, container is %2") .arg(serverIndex) - .arg(ContainerProps::containerToString(container)) - << m_settings->routeMode(); + .arg(ContainerProps::containerToString(container)); #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) if (!m_IpcClient) { m_IpcClient = new IpcClient(this); @@ -341,26 +340,26 @@ void VpnConnection::appendSplitTunnelingConfig() } } - Settings::RouteMode routeMode = Settings::RouteMode::VpnAllSites; + Settings::RouteMode sitesRouteMode = Settings::RouteMode::VpnAllSites; QJsonArray sitesJsonArray; if (m_settings->isSitesSplitTunnelingEnabled()) { - routeMode = m_settings->routeMode(); + sitesRouteMode = m_settings->routeMode(); if (allowSiteBasedSplitTunneling) { - auto sites = m_settings->getVpnIps(routeMode); + auto sites = m_settings->getVpnIps(sitesRouteMode); for (const auto &site : sites) { sitesJsonArray.append(site); } // Allow traffic to Amnezia DNS - if (routeMode == Settings::VpnOnlyForwardSites) { + if (sitesRouteMode == Settings::VpnOnlyForwardSites) { sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString()); sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString()); } } } - m_vpnConfiguration.insert(config_key::splitTunnelType, routeMode); + m_vpnConfiguration.insert(config_key::splitTunnelType, sitesRouteMode); m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray); Settings::AppsRouteMode appsRouteMode = Settings::AppsRouteMode::VpnAllApps; @@ -376,6 +375,13 @@ void VpnConnection::appendSplitTunnelingConfig() m_vpnConfiguration.insert(config_key::appSplitTunnelType, appsRouteMode); m_vpnConfiguration.insert(config_key::splitTunnelApps, appsJsonArray); + + qDebug() << QString("Site split tunneling is %1, route mode is %2") + .arg(m_settings->isSitesSplitTunnelingEnabled() ? "enabled" : "disabled") + .arg(sitesRouteMode); + qDebug() << QString("App split tunneling is %1, route mode is %2") + .arg(m_settings->isAppsSplitTunnelingEnabled() ? "enabled" : "disabled") + .arg(appsRouteMode); } #ifdef Q_OS_ANDROID From 780a0929d7cd9ad27f906ac755072c8d8ba7fff9 Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Sat, 1 Mar 2025 17:16:20 +0400 Subject: [PATCH 15/15] Revert "polishing 2" This reverts commit 64552d608057908edb2b166c69668e73f790b551. --- .gitmodules | 3 + CMakeLists.txt | 4 +- README.md | 6 +- README_RU.md | 6 +- client/3rd-prebuilt | 2 +- client/cmake/sources.cmake | 191 ------- client/core/api/apiDefs.h | 51 -- client/core/api/apiUtils.cpp | 87 --- client/core/api/apiUtils.h | 22 - client/core/controllers/coreController.cpp | 345 ----------- client/core/controllers/coreController.h | 136 ----- client/core/controllers/gatewayController.cpp | 303 ---------- client/core/controllers/gatewayController.h | 35 -- client/core/qrCodeUtils.cpp | 35 -- client/core/qrCodeUtils.h | 17 - client/images/controls/monitor.svg | 5 - client/server_scripts/prepare_host.sh | 2 +- .../controllers/api/apiConfigsController.cpp | 537 ------------------ .../ui/controllers/api/apiConfigsController.h | 74 --- .../controllers/api/apiSettingsController.cpp | 93 --- .../controllers/api/apiSettingsController.h | 37 -- client/ui/models/api/apiAccountInfoModel.cpp | 143 ----- client/ui/models/api/apiAccountInfoModel.h | 56 -- client/ui/models/api/apiCountryModel.cpp | 122 ---- client/ui/models/api/apiCountryModel.h | 63 -- client/ui/models/api/apiDevicesModel.cpp | 90 --- client/ui/models/api/apiDevicesModel.h | 52 -- client/ui/models/api/apiServicesModel.cpp | 264 --------- client/ui/models/api/apiServicesModel.h | 91 --- .../ui/qml/Components/RenameServerDrawer.qml | 55 -- client/ui/qml/Controls2/ListViewType.qml | 38 -- .../PageSettingsApiAvailableCountries.qml | 171 ------ .../ui/qml/Pages2/PageSettingsApiDevices.qml | 100 ---- .../Pages2/PageSettingsApiInstructions.qml | 124 ---- .../Pages2/PageSettingsApiNativeConfigs.qml | 214 ------- .../ui/qml/Pages2/PageSettingsApiSupport.qml | 127 ----- 36 files changed, 13 insertions(+), 3688 deletions(-) delete mode 100644 client/cmake/sources.cmake delete mode 100644 client/core/api/apiDefs.h delete mode 100644 client/core/api/apiUtils.cpp delete mode 100644 client/core/api/apiUtils.h delete mode 100644 client/core/controllers/coreController.cpp delete mode 100644 client/core/controllers/coreController.h delete mode 100644 client/core/controllers/gatewayController.cpp delete mode 100644 client/core/controllers/gatewayController.h delete mode 100644 client/core/qrCodeUtils.cpp delete mode 100644 client/core/qrCodeUtils.h delete mode 100644 client/images/controls/monitor.svg delete mode 100644 client/ui/controllers/api/apiConfigsController.cpp delete mode 100644 client/ui/controllers/api/apiConfigsController.h delete mode 100644 client/ui/controllers/api/apiSettingsController.cpp delete mode 100644 client/ui/controllers/api/apiSettingsController.h delete mode 100644 client/ui/models/api/apiAccountInfoModel.cpp delete mode 100644 client/ui/models/api/apiAccountInfoModel.h delete mode 100644 client/ui/models/api/apiCountryModel.cpp delete mode 100644 client/ui/models/api/apiCountryModel.h delete mode 100644 client/ui/models/api/apiDevicesModel.cpp delete mode 100644 client/ui/models/api/apiDevicesModel.h delete mode 100644 client/ui/models/api/apiServicesModel.cpp delete mode 100644 client/ui/models/api/apiServicesModel.h delete mode 100644 client/ui/qml/Components/RenameServerDrawer.qml delete mode 100644 client/ui/qml/Controls2/ListViewType.qml delete mode 100644 client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml delete mode 100644 client/ui/qml/Pages2/PageSettingsApiDevices.qml delete mode 100644 client/ui/qml/Pages2/PageSettingsApiInstructions.qml delete mode 100644 client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml delete mode 100644 client/ui/qml/Pages2/PageSettingsApiSupport.qml diff --git a/.gitmodules b/.gitmodules index decab9b7..3ceaa56e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ +[submodule "client/3rd/OpenVPNAdapter"] + path = client/3rd/OpenVPNAdapter + url = https://github.com/amnezia-vpn/OpenVPNAdapter.git [submodule "client/3rd/qtkeychain"] path = client/3rd/qtkeychain url = https://github.com/frankosterfeld/qtkeychain.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c602249..434b195f 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.4.2 +project(${PROJECT} VERSION 4.8.3.1 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 2079) +set(APP_ANDROID_VERSION_CODE 2074) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/README.md b/README.md index 992c3ad0..368bcfff 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ [![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org) -### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting) +### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/kldscp/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting) > [!TIP] -> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ). +> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/kldscp/amnezia.org). - + [All releases](https://github.com/amnezia-vpn/amnezia-client/releases) diff --git a/README_RU.md b/README_RU.md index f9ca7d12..45b506f2 100644 --- a/README_RU.md +++ b/README_RU.md @@ -10,12 +10,12 @@ [![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org) -### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting) +### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/kldscp/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting) > [!TIP] -> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org). +> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/kldscp/amnezia.org). - + [Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases) diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index e555c78b..ba580dc5 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit e555c78bcf44070d5c88bcca54480732c9164f18 +Subproject commit ba580dc5bd7784f7b1e110ff0365f3286e549a61 diff --git a/client/cmake/sources.cmake b/client/cmake/sources.cmake deleted file mode 100644 index c3af531a..00000000 --- a/client/cmake/sources.cmake +++ /dev/null @@ -1,191 +0,0 @@ -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/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 - ${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}/../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/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 - ${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/apiDefs.h b/client/core/api/apiDefs.h deleted file mode 100644 index 41dd80ba..00000000 --- a/client/core/api/apiDefs.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef APIDEFS_H -#define APIDEFS_H - -#include - -namespace apiDefs -{ - enum ConfigType { - AmneziaFreeV2 = 0, - 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"); - constexpr QLatin1String serviceType("service_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 -} - -#endif // APIDEFS_H diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp deleted file mode 100644 index 9f518b52..00000000 --- a/client/core/api/apiUtils.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "apiUtils.h" - -#include -#include - -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 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 (serviceType == servicePremium || stackType == stackPremium) { - return apiDefs::ConfigType::AmneziaPremiumV2; - } else if (serviceType == serviceFree || stackType == stackFree) { - return apiDefs::ConfigType::AmneziaFreeV3; - } - } - default: { - return apiDefs::ConfigType::SelfHosted; - } - }; -} - -apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigObject) -{ - return static_cast(serverConfigObject.value(apiDefs::key::configVersion).toInt()); -} - -amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply) -{ - const int httpStatusCodeConflict = 409; - const int httpStatusCodeNotFound = 404; - - 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; - } else if (httpStatusCode == httpStatusCodeNotFound) { - return amnezia::ErrorCode::ApiNotFoundError; - } - 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 deleted file mode 100644 index 82ac315b..00000000 --- a/client/core/api/apiUtils.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef APIUTILS_H -#define APIUTILS_H - -#include -#include - -#include "apiDefs.h" -#include "core/defs.h" - -namespace apiUtils -{ - bool isServerFromApi(const QJsonObject &serverConfigObject); - - bool isSubscriptionExpired(const QString &subscriptionEndDate); - - 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/coreController.cpp b/client/core/controllers/coreController.cpp deleted file mode 100644 index 82232c99..00000000 --- a/client/core/controllers/coreController.cpp +++ /dev/null @@ -1,345 +0,0 @@ -#include "coreController.h" - -#include - -#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(); - - initAndroidController(); - initAppleController(); - - 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()); - - m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.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_apiDevicesModel, 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() -{ - initErrorMessagesHandler(); - - initApiCountryModelUpdateHandler(); - initContainerModelUpdateHandler(); - initAdminConfigRevokedHandler(); - 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::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)); -} - -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::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 deleted file mode 100644 index 700504af..00000000 --- a/client/core/controllers/coreController.h +++ /dev/null @@ -1,136 +0,0 @@ -#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/apiCountryModel.h" -#include "ui/models/api/apiDevicesModel.h" -#include "ui/models/api/apiServicesModel.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 initErrorMessagesHandler(); - - void initApiCountryModelUpdateHandler(); - void initContainerModelUpdateHandler(); - void initAdminConfigRevokedHandler(); - 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; - QSharedPointer m_apiDevicesModel; - - 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/gatewayController.cpp b/client/core/controllers/gatewayController.cpp deleted file mode 100644 index 15776328..00000000 --- a/client/core/controllers/gatewayController.cpp +++ /dev/null @@ -1,303 +0,0 @@ -#include "gatewayController.h" - -#include -#include - -#include -#include -#include -#include - -#include "QBlockCipher.h" -#include "QRsa.h" - -#include "amnezia_application.h" -#include "core/api/apiUtils.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->networkManager()->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->networkManager()->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 = apiUtils::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->networkManager()->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, true, key, iv, salt)) { - auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) { - request.setUrl(url); - return amnApp->networkManager()->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 = apiUtils::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->networkManager()->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" << encryptedResponseBody; - 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 deleted file mode 100644 index 45d989f0..00000000 --- a/client/core/controllers/gatewayController.h +++ /dev/null @@ -1,35 +0,0 @@ -#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/qrCodeUtils.cpp b/client/core/qrCodeUtils.cpp deleted file mode 100644 index a18af172..00000000 --- a/client/core/qrCodeUtils.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "qrCodeUtils.h" - -#include -#include - -QList qrCodeUtils::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 << qrCodeUtils::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 qrCodeUtils::svgToBase64(const QString &image) -{ - return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().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 deleted file mode 100644 index cda0723b..00000000 --- a/client/core/qrCodeUtils.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef QRCODEUTILS_H -#define QRCODEUTILS_H - -#include - -#include "qrcodegen.hpp" - -namespace qrCodeUtils -{ - 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/images/controls/monitor.svg b/client/images/controls/monitor.svg deleted file mode 100644 index 1cdf57c2..00000000 --- a/client/images/controls/monitor.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/server_scripts/prepare_host.sh b/client/server_scripts/prepare_host.sh index 22a50e31..d06bdafb 100644 --- a/client/server_scripts/prepare_host.sh +++ b/client/server_scripts/prepare_host.sh @@ -1,4 +1,4 @@ -CUR_USER=$(whoami 2>/dev/null || echo ~ | sed 's/.*\///');\ +CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\ sudo mkdir -p $DOCKERFILE_FOLDER;\ sudo chown $CUR_USER $DOCKERFILE_FOLDER;\ if ! sudo docker network ls | grep -q amnezia-dns-net; then sudo docker network create \ diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp deleted file mode 100644 index d3c8747d..00000000 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ /dev/null @@ -1,537 +0,0 @@ -#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/qrCodeUtils.h" -#include "ui/controllers/systemController.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 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 QSharedPointer &apiServicesModel, - const std::shared_ptr &settings, QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_apiServicesModel(apiServicesModel), m_settings(settings) -{ -} - -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()); - 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/native_config"), apiPayload, responseBody); - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode); - return false; - } - - 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; -} - -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 && errorCode != ErrorCode::ApiNotFoundError) { - emit errorOccurred(errorCode); - return false; - } - 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(); - m_vpnKey = vpnKey; - - vpnKey.replace("vpn://", ""); - - m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(vpnKey.toUtf8()); - - 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); - - 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)); - newApiConfig.insert(apiDefs::key::vpnKey, apiConfig.value(apiDefs::key::vpnKey)); - - newServerConfig.insert(configKey::apiConfig, newApiConfig); - newServerConfig.insert(configKey::authData, authData); - // newServerConfig.insert( - - 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->networkManager()->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 = apiUtils::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::deactivateDevice() -{ - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); - - 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); - - 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 && errorCode != ErrorCode::ApiNotFoundError) { - emit errorOccurred(errorCode); - return false; - } - - serverConfigObject.remove(config_key::containers); - m_serversModel->editServer(serverConfigObject, serverIndex); - - return true; -} - -bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode) -{ - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); - - 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); - - 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); - apiPayload[configKey::uuid] = uuid; - - QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); - if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { - emit errorOccurred(errorCode); - return false; - } - - if (uuid == m_settings->getInstallationUuid(true)) { - serverConfigObject.remove(config_key::containers); - m_serversModel->editServer(serverConfigObject, serverIndex); - } - - 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; - 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; -} - -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; -} - -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 deleted file mode 100644 index 2fe981e4..00000000 --- a/client/ui/controllers/api/apiConfigsController.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef APICONFIGSCONTROLLER_H -#define APICONFIGSCONTROLLER_H - -#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 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) - 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(); - bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, - bool reloadServiceConfig = false); - bool updateServiceFromTelegram(const int serverIndex); - bool deactivateDevice(); - bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode); - - 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: - struct ApiPayloadData - { - OpenVpnConfigurator::ConnectionData certRequest; - - QString wireGuardClientPrivKey; - QString wireGuardClientPubKey; - }; - - 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(); - QString getVpnKey(); - - QList m_qrCodes; - QString m_vpnKey; - - QSharedPointer m_serversModel; - QSharedPointer m_apiServicesModel; - std::shared_ptr m_settings; -}; - -#endif // APICONFIGSCONTROLLER_H diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp deleted file mode 100644 index 737bfd1a..00000000 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "apiSettingsController.h" - -#include -#include - -#include "core/api/apiUtils.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 QSharedPointer &apiCountryModel, - const QSharedPointer &apiDevicesModel, - const std::shared_ptr &settings, QObject *parent) - : QObject(parent), - m_serversModel(serversModel), - m_apiAccountInfoModel(apiAccountInfoModel), - m_apiCountryModel(apiCountryModel), - m_apiDevicesModel(apiDevicesModel), - m_settings(settings) -{ -} - -ApiSettingsController::~ApiSettingsController() -{ -} - -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(); - 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; - - 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(); - m_apiAccountInfoModel->updateModel(accountInfo, serverConfig); - - if (reload) { - updateApiCountryModel(); - updateApiDevicesModel(); - } - - return true; -} - -void ApiSettingsController::updateApiCountryModel() -{ - m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), ""); - m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo()); -} - -void ApiSettingsController::updateApiDevicesModel() -{ - m_apiDevicesModel->updateModel(m_apiAccountInfoModel->getIssuedConfigsInfo()); -} diff --git a/client/ui/controllers/api/apiSettingsController.h b/client/ui/controllers/api/apiSettingsController.h deleted file mode 100644 index afe9a570..00000000 --- a/client/ui/controllers/api/apiSettingsController.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef APISETTINGSCONTROLLER_H -#define APISETTINGSCONTROLLER_H - -#include - -#include "ui/models/api/apiAccountInfoModel.h" -#include "ui/models/api/apiCountryModel.h" -#include "ui/models/api/apiDevicesModel.h" -#include "ui/models/servers_model.h" - -class ApiSettingsController : public QObject -{ - Q_OBJECT -public: - ApiSettingsController(const QSharedPointer &serversModel, const QSharedPointer &apiAccountInfoModel, - const QSharedPointer &apiCountryModel, const QSharedPointer &apiDevicesModel, - const std::shared_ptr &settings, QObject *parent = nullptr); - ~ApiSettingsController(); - -public slots: - bool getAccountInfo(bool reload); - void updateApiCountryModel(); - void updateApiDevicesModel(); - -signals: - void errorOccurred(ErrorCode errorCode); - -private: - QSharedPointer m_serversModel; - QSharedPointer m_apiAccountInfoModel; - QSharedPointer m_apiCountryModel; - QSharedPointer m_apiDevicesModel; - - std::shared_ptr m_settings; -}; - -#endif // APISETTINGSCONTROLLER_H diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp deleted file mode 100644 index cdb9dfe8..00000000 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "apiAccountInfoModel.h" - -#include - -#include "core/api/apiUtils.h" -#include "logger.h" - -namespace -{ - Logger logger("AccountInfoModel"); -} - -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: { - 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: { - 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."); - } - } - case IsComponentVisibleRole: { - return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2; - } - case HasExpiredWorkerRole: { - for (int i = 0; i < m_issuedConfigsInfo.size(); i++) { - QJsonObject issuedConfigObject = m_issuedConfigsInfo.at(i).toObject(); - - auto lastDownloaded = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::lastDownloaded).toString()); - auto workerLastUpdated = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString()); - - if (lastDownloaded < workerLastUpdated) { - return true; - } - } - return false; - } - } - - return QVariant(); -} - -void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig) -{ - beginResetModel(); - - AccountInfoData accountInfoData; - - m_availableCountries = accountInfoObject.value(apiDefs::key::availableCountries).toArray(); - m_issuedConfigsInfo = accountInfoObject.value(apiDefs::key::issuedConfigs).toArray(); - - 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); - - 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 {}; -} - -QJsonArray ApiAccountInfoModel::getAvailableCountries() -{ - return m_availableCountries; -} - -QJsonArray ApiAccountInfoModel::getIssuedConfigsInfo() -{ - return m_issuedConfigsInfo; -} - -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; - roles[SubscriptionStatusRole] = "subscriptionStatus"; - roles[EndDateRole] = "endDate"; - roles[ConnectedDevicesRole] = "connectedDevices"; - roles[ServiceDescriptionRole] = "serviceDescription"; - roles[IsComponentVisibleRole] = "isComponentVisible"; - roles[HasExpiredWorkerRole] = "hasExpiredWorker"; - - return roles; -} diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h deleted file mode 100644 index 44eb7ee6..00000000 --- a/client/ui/models/api/apiAccountInfoModel.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef APIACCOUNTINFOMODEL_H -#define APIACCOUNTINFOMODEL_H - -#include -#include -#include - -#include "core/api/apiDefs.h" - -class ApiAccountInfoModel : public QAbstractListModel -{ - Q_OBJECT - -public: - enum Roles { - SubscriptionStatusRole = Qt::UserRole + 1, - ConnectedDevicesRole, - ServiceDescriptionRole, - EndDateRole, - IsComponentVisibleRole, - HasExpiredWorkerRole - }; - - 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); - - QJsonArray getAvailableCountries(); - QJsonArray getIssuedConfigsInfo(); - QString getTelegramBotLink(); - -protected: - QHash roleNames() const override; - -private: - struct AccountInfoData - { - QString subscriptionEndDate; - int activeDeviceCount; - int maxDeviceCount; - - apiDefs::ConfigType configType; - }; - - 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 deleted file mode 100644 index 12f4658e..00000000 --- a/client/ui/models/api/apiCountryModel.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "apiCountryModel.h" - -#include - -#include "core/api/apiDefs.h" -#include "logger.h" - -namespace -{ - Logger logger("ApiCountryModel"); - - constexpr QLatin1String countryConfig("country_config"); -} - -ApiCountryModel::ApiCountryModel(QObject *parent) : QAbstractListModel(parent) -{ -} - -int ApiCountryModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - return m_countries.size(); -} - -QVariant ApiCountryModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) - return QVariant(); - - CountryInfo countryInfo = m_countries.at(index.row()); - IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.value(countryInfo.countryCode); - bool isIssued = issuedConfigInfo.sourceType == countryConfig; - - switch (role) { - case CountryCodeRole: { - return countryInfo.countryCode; - } - case CountryNameRole: { - return countryInfo.countryName; - } - case CountryImageCodeRole: { - return countryInfo.countryCode.toUpper(); - } - case IsIssuedRole: { - return isIssued; - } - case IsWorkerExpiredRole: { - return issuedConfigInfo.lastDownloaded < issuedConfigInfo.workerLastUpdated; - } - } - - return QVariant(); -} - -void ApiCountryModel::updateModel(const QJsonArray &countries, const QString ¤tCountryCode) -{ - beginResetModel(); - - 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); - } - m_countries.push_back(countryInfo); - } - - endResetModel(); -} - -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(); - 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(); -} - -int ApiCountryModel::getCurrentIndex() -{ - return m_currentIndex; -} - -void ApiCountryModel::setCurrentIndex(const int i) -{ - m_currentIndex = i; - emit currentIndexChanged(m_currentIndex); -} - -QHash ApiCountryModel::roleNames() const -{ - QHash roles; - roles[CountryNameRole] = "countryName"; - roles[CountryCodeRole] = "countryCode"; - roles[CountryImageCodeRole] = "countryImageCode"; - roles[IsIssuedRole] = "isIssued"; - roles[IsWorkerExpiredRole] = "isWorkerExpired"; - return roles; -} diff --git a/client/ui/models/api/apiCountryModel.h b/client/ui/models/api/apiCountryModel.h deleted file mode 100644 index 08ac3685..00000000 --- a/client/ui/models/api/apiCountryModel.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef APICOUNTRYMODEL_H -#define APICOUNTRYMODEL_H - -#include -#include -#include - -class ApiCountryModel : public QAbstractListModel -{ - Q_OBJECT - -public: - enum Roles { - CountryNameRole = Qt::UserRole + 1, - CountryCodeRole, - CountryImageCodeRole, - IsIssuedRole, - IsWorkerExpiredRole - }; - - explicit ApiCountryModel(QObject *parent = nullptr); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - Q_PROPERTY(int currentIndex READ getCurrentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) - -public slots: - void updateModel(const QJsonArray &countries, const QString ¤tCountryCode); - void updateIssuedConfigsInfo(const QJsonArray &issuedConfigs); - - int getCurrentIndex(); - void setCurrentIndex(const int i); - -signals: - void currentIndexChanged(const int index); - -protected: - QHash roleNames() const override; - -private: - 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; -}; - -#endif // APICOUNTRYMODEL_H diff --git a/client/ui/models/api/apiDevicesModel.cpp b/client/ui/models/api/apiDevicesModel.cpp deleted file mode 100644 index 6c0d60d0..00000000 --- a/client/ui/models/api/apiDevicesModel.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "apiDevicesModel.h" - -#include - -#include "core/api/apiDefs.h" -#include "logger.h" - -namespace -{ - Logger logger("ApiDevicesModel"); - - constexpr QLatin1String gatewayAccount("gateway_account"); -} - -ApiDevicesModel::ApiDevicesModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) -{ -} - -int ApiDevicesModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - return m_issuedConfigs.size(); -} - -QVariant ApiDevicesModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) - return QVariant(); - - IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.at(index.row()); - - switch (role) { - case OsVersionRole: { - return issuedConfigInfo.osVersion; - } - case SupportTagRole: { - return issuedConfigInfo.installationUuid; - } - case CountryCodeRole: { - return issuedConfigInfo.countryCode; - } - case LastUpdateRole: { - return QDateTime::fromString(issuedConfigInfo.lastDownloaded, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); - } - case IsCurrentDeviceRole: { - return issuedConfigInfo.installationUuid == m_settings->getInstallationUuid(false); - } - } - - return QVariant(); -} - -void ApiDevicesModel::updateModel(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() != gatewayAccount) { - 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(); - issuedConfigInfo.sourceType = issuedConfigObject.value(apiDefs::key::sourceType).toString(); - issuedConfigInfo.osVersion = issuedConfigObject.value(apiDefs::key::osVersion).toString(); - - issuedConfigInfo.countryName = issuedConfigObject.value(apiDefs::key::serverCountryName).toString(); - issuedConfigInfo.countryCode = issuedConfigObject.value(apiDefs::key::serverCountryCode).toString(); - - m_issuedConfigs.push_back(issuedConfigInfo); - } - - endResetModel(); -} - -QHash ApiDevicesModel::roleNames() const -{ - QHash roles; - roles[OsVersionRole] = "osVersion"; - roles[SupportTagRole] = "supportTag"; - roles[CountryCodeRole] = "countryCode"; - roles[LastUpdateRole] = "lastUpdate"; - roles[IsCurrentDeviceRole] = "isCurrentDevice"; - return roles; -} diff --git a/client/ui/models/api/apiDevicesModel.h b/client/ui/models/api/apiDevicesModel.h deleted file mode 100644 index e6a59dba..00000000 --- a/client/ui/models/api/apiDevicesModel.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef APIDEVICESMODEL_H -#define APIDEVICESMODEL_H - -#include -#include -#include - -#include "settings.h" - -class ApiDevicesModel : public QAbstractListModel -{ - Q_OBJECT - -public: - enum Roles { - OsVersionRole = Qt::UserRole + 1, - SupportTagRole, - CountryCodeRole, - LastUpdateRole, - IsCurrentDeviceRole - }; - - explicit ApiDevicesModel(std::shared_ptr settings, 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 QJsonArray &issuedConfigs); - -protected: - QHash roleNames() const override; - -private: - struct IssuedConfigInfo - { - QString installationUuid; - QString workerLastUpdated; - QString lastDownloaded; - QString sourceType; - QString osVersion; - - QString countryName; - QString countryCode; - }; - - QVector m_issuedConfigs; - - std::shared_ptr m_settings; -}; -#endif // APIDEVICESMODEL_H diff --git a/client/ui/models/api/apiServicesModel.cpp b/client/ui/models/api/apiServicesModel.cpp deleted file mode 100644 index f1880e4d..00000000 --- a/client/ui/models/api/apiServicesModel.cpp +++ /dev/null @@ -1,264 +0,0 @@ -#include "apiServicesModel.h" - -#include - -#include "logger.h" - -namespace -{ - Logger logger("ApiServicesModel"); - - namespace configKey - { - constexpr char userCountryCode[] = "user_country_code"; - constexpr char services[] = "services"; - constexpr char serviceInfo[] = "service_info"; - constexpr char serviceType[] = "service_type"; - constexpr char serviceProtocol[] = "service_protocol"; - - constexpr char name[] = "name"; - constexpr char price[] = "price"; - constexpr char speed[] = "speed"; - constexpr char timelimit[] = "timelimit"; - constexpr char region[] = "region"; - - constexpr char availableCountries[] = "available_countries"; - - constexpr char storeEndpoint[] = "store_endpoint"; - - constexpr char isAvailable[] = "is_available"; - - constexpr char subscription[] = "subscription"; - constexpr char endDate[] = "end_date"; - } - - namespace serviceType - { - constexpr char amneziaFree[] = "amnezia-free"; - constexpr char amneziaPremium[] = "amnezia-premium"; - } -} - -ApiServicesModel::ApiServicesModel(QObject *parent) : QAbstractListModel(parent) -{ -} - -int ApiServicesModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - return m_services.size(); -} - -QVariant ApiServicesModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) - return QVariant(); - - auto apiServiceData = m_services.at(index.row()); - auto serviceType = apiServiceData.type; - auto isServiceAvailable = apiServiceData.isServiceAvailable; - - switch (role) { - case NameRole: { - return apiServiceData.serviceInfo.name; - } - case CardDescriptionRole: { - auto speed = apiServiceData.serviceInfo.speed; - if (serviceType == serviceType::amneziaPremium) { - return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. " - "Works for any sites with no restrictions. Speed up to %1 MBit/s. Unlimited traffic.") - .arg(speed); - } else if (serviceType == serviceType::amneziaFree) { - QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan."); - if (!isServiceAvailable) { - description += tr("

Not available in your region. If you have VPN enabled, disable it, " - "return to the previous screen, and try again."); - } - return description; - } - } - case ServiceDescriptionRole: { - if (serviceType == serviceType::amneziaPremium) { - return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. " - "Works for any sites with no restrictions."); - } else { - return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan."); - } - } - case IsServiceAvailableRole: { - if (serviceType == serviceType::amneziaFree) { - if (!isServiceAvailable) { - return false; - } - } - return true; - } - case SpeedRole: { - return tr("%1 MBit/s").arg(apiServiceData.serviceInfo.speed); - } - case TimeLimitRole: { - auto timeLimit = apiServiceData.serviceInfo.timeLimit; - if (timeLimit == "0") { - return ""; - } - return tr("%1 days").arg(timeLimit); - } - case RegionRole: { - return apiServiceData.serviceInfo.region; - } - case FeaturesRole: { - if (serviceType == serviceType::amneziaPremium) { - return tr(""); - } else { - return tr("VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. " - "Other sites will be opened from your real IP address, " - "more details on the website."); - } - } - case PriceRole: { - auto price = apiServiceData.serviceInfo.price; - if (price == "free") { - return tr("Free"); - } - return tr("%1 $/month").arg(price); - } - case EndDateRole: { - return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); - } - } - - return QVariant(); -} - -void ApiServicesModel::updateModel(const QJsonObject &data) -{ - beginResetModel(); - - m_services.clear(); - - m_countryCode = data.value(configKey::userCountryCode).toString(); - auto services = data.value(configKey::services).toArray(); - - if (services.isEmpty()) { - m_services.push_back(getApiServicesData(data)); - m_selectedServiceIndex = 0; - } else { - for (const auto &service : services) { - auto serviceObject = service.toObject(); - m_services.push_back(getApiServicesData(serviceObject)); - } - } - - endResetModel(); -} - -void ApiServicesModel::setServiceIndex(const int index) -{ - m_selectedServiceIndex = index; -} - -QJsonObject ApiServicesModel::getSelectedServiceInfo() -{ - auto service = m_services.at(m_selectedServiceIndex); - return service.serviceInfo.object; -} - -QString ApiServicesModel::getSelectedServiceType() -{ - auto service = m_services.at(m_selectedServiceIndex); - return service.type; -} - -QString ApiServicesModel::getSelectedServiceProtocol() -{ - auto service = m_services.at(m_selectedServiceIndex); - return service.protocol; -} - -QString ApiServicesModel::getSelectedServiceName() -{ - auto service = m_services.at(m_selectedServiceIndex); - return service.serviceInfo.name; -} - -QJsonArray ApiServicesModel::getSelectedServiceCountries() -{ - auto service = m_services.at(m_selectedServiceIndex); - return service.availableCountries; -} - -QString ApiServicesModel::getCountryCode() -{ - return m_countryCode; -} - -QString ApiServicesModel::getStoreEndpoint() -{ - auto service = m_services.at(m_selectedServiceIndex); - return service.storeEndpoint; -} - -QVariant ApiServicesModel::getSelectedServiceData(const QString roleString) -{ - QModelIndex modelIndex = index(m_selectedServiceIndex); - auto roles = roleNames(); - for (auto it = roles.begin(); it != roles.end(); it++) { - if (QString(it.value()) == roleString) { - return data(modelIndex, it.key()); - } - } - - return {}; -} - -QHash ApiServicesModel::roleNames() const -{ - QHash roles; - roles[NameRole] = "name"; - roles[CardDescriptionRole] = "cardDescription"; - roles[ServiceDescriptionRole] = "serviceDescription"; - roles[IsServiceAvailableRole] = "isServiceAvailable"; - roles[SpeedRole] = "speed"; - roles[TimeLimitRole] = "timeLimit"; - roles[RegionRole] = "region"; - roles[FeaturesRole] = "features"; - roles[PriceRole] = "price"; - roles[EndDateRole] = "endDate"; - - return roles; -} - -ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJsonObject &data) -{ - auto serviceInfo = data.value(configKey::serviceInfo).toObject(); - auto serviceType = data.value(configKey::serviceType).toString(); - auto serviceProtocol = data.value(configKey::serviceProtocol).toString(); - auto availableCountries = data.value(configKey::availableCountries).toArray(); - - auto subscriptionObject = data.value(configKey::subscription).toObject(); - - ApiServicesData serviceData; - serviceData.serviceInfo.name = serviceInfo.value(configKey::name).toString(); - serviceData.serviceInfo.price = serviceInfo.value(configKey::price).toString(); - serviceData.serviceInfo.region = serviceInfo.value(configKey::region).toString(); - serviceData.serviceInfo.speed = serviceInfo.value(configKey::speed).toString(); - serviceData.serviceInfo.timeLimit = serviceInfo.value(configKey::timelimit).toString(); - - serviceData.type = serviceType; - serviceData.protocol = serviceProtocol; - - serviceData.storeEndpoint = data.value(configKey::storeEndpoint).toString(); - - if (data.value(configKey::isAvailable).isBool()) { - serviceData.isServiceAvailable = data.value(configKey::isAvailable).toBool(); - } else { - serviceData.isServiceAvailable = true; - } - - serviceData.serviceInfo.object = serviceInfo; - serviceData.availableCountries = availableCountries; - - serviceData.subscription.endDate = subscriptionObject.value(configKey::endDate).toString(); - - return serviceData; -} diff --git a/client/ui/models/api/apiServicesModel.h b/client/ui/models/api/apiServicesModel.h deleted file mode 100644 index c96a49ab..00000000 --- a/client/ui/models/api/apiServicesModel.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef APISERVICESMODEL_H -#define APISERVICESMODEL_H - -#include -#include -#include - -class ApiServicesModel : public QAbstractListModel -{ - Q_OBJECT - -public: - enum Roles { - NameRole = Qt::UserRole + 1, - CardDescriptionRole, - ServiceDescriptionRole, - IsServiceAvailableRole, - SpeedRole, - TimeLimitRole, - RegionRole, - FeaturesRole, - PriceRole, - EndDateRole - }; - - explicit ApiServicesModel(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 &data); - - void setServiceIndex(const int index); - - QJsonObject getSelectedServiceInfo(); - QString getSelectedServiceType(); - QString getSelectedServiceProtocol(); - QString getSelectedServiceName(); - QJsonArray getSelectedServiceCountries(); - - QString getCountryCode(); - - QString getStoreEndpoint(); - - QVariant getSelectedServiceData(const QString roleString); - -protected: - QHash roleNames() const override; - -private: - struct ServiceInfo - { - QString name; - QString speed; - QString timeLimit; - QString region; - QString price; - - QJsonObject object; - }; - - struct Subscription - { - QString endDate; - }; - - struct ApiServicesData - { - bool isServiceAvailable; - - QString type; - QString protocol; - QString storeEndpoint; - - ServiceInfo serviceInfo; - Subscription subscription; - - QJsonArray availableCountries; - }; - - ApiServicesData getApiServicesData(const QJsonObject &data); - - QString m_countryCode; - QVector m_services; - - int m_selectedServiceIndex; -}; - -#endif // APISERVICESMODEL_H diff --git a/client/ui/qml/Components/RenameServerDrawer.qml b/client/ui/qml/Components/RenameServerDrawer.qml deleted file mode 100644 index d65b9bba..00000000 --- a/client/ui/qml/Components/RenameServerDrawer.qml +++ /dev/null @@ -1,55 +0,0 @@ -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/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewType.qml deleted file mode 100644 index 0de43d77..00000000 --- a/client/ui/qml/Controls2/ListViewType.qml +++ /dev/null @@ -1,38 +0,0 @@ -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/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml deleted file mode 100644 index 43fbb160..00000000 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ /dev/null @@ -1,171 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Dialogs - -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 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) - } - } - - ListViewType { - id: menuContent - - anchors.fill: parent - - 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: qsTr("Locations for connection") - - actionButtonFunction: function() { - PageController.showBusyIndicator(true) - let result = ApiSettingsController.getAccountInfo(false) - PageController.showBusyIndicator(false) - if (!result) { - return - } - - PageController.goToPage(PageEnum.PageSettingsApiServerInfo) - } - } - } - - delegate: ColumnLayout { - id: content - - width: menuContent.width - height: content.implicitHeight - - RowLayout { - VerticalRadioButton { - id: containerRadioButton - - Layout.fillWidth: true - Layout.leftMargin: 16 - - text: countryName - - ButtonGroup.group: containersRadioButtonGroup - - imageSource: "qrc:/images/controls/download.svg" - - checked: index === ApiCountryModel.currentIndex - checkable: !ConnectionController.isConnected - - onClicked: { - if (ConnectionController.isConnected) { - PageController.showNotificationMessage(qsTr("Unable change server location while there is an active connection")) - return - } - - if (index !== ApiCountryModel.currentIndex) { - PageController.showBusyIndicator(true) - var prevIndex = ApiCountryModel.currentIndex - ApiCountryModel.currentIndex = index - if (!ApiConfigsController.updateServiceFromGateway(ServersModel.defaultIndex, countryCode, countryName)) { - ApiCountryModel.currentIndex = prevIndex - } - PageController.showBusyIndicator(false) - } - } - - MouseArea { - anchors.fill: containerRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false - } - - Keys.onEnterPressed: { - if (checkable) { - checked = true - } - containerRadioButton.clicked() - } - Keys.onReturnPressed: { - if (checkable) { - checked = true - } - containerRadioButton.clicked() - } - } - - Image { - Layout.rightMargin: 32 - Layout.alignment: Qt.AlignRight - - source: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" - } - } - - DividerType { - Layout.fillWidth: true - } - } - } -} diff --git a/client/ui/qml/Pages2/PageSettingsApiDevices.qml b/client/ui/qml/Pages2/PageSettingsApiDevices.qml deleted file mode 100644 index 5cc21d07..00000000 --- a/client/ui/qml/Pages2/PageSettingsApiDevices.qml +++ /dev/null @@ -1,100 +0,0 @@ -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 - - ListViewType { - id: listView - - anchors.fill: parent - anchors.topMargin: 20 - anchors.bottomMargin: 24 - - model: ApiDevicesModel - - header: ColumnLayout { - width: listView.width - - BackButtonType { - id: backButton - } - - HeaderType { - id: header - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - - headerText: qsTr("Connected devices") - descriptionText: qsTr("To manage connected devices") - } - - WarningType { - Layout.topMargin: 16 - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.fillWidth: true - - textString: qsTr("You can find the identifier on the Support tab or, for older versions of the app, " - + "by tapping '+' and then the three dots at the top of the page.") - - iconPath: "qrc:/images/controls/alert-circle.svg" - } - } - - delegate: ColumnLayout { - width: listView.width - - LabelWithButtonType { - Layout.fillWidth: true - Layout.topMargin: 6 - - text: osVersion + (isCurrentDevice ? qsTr(" (current device)") : "") - descriptionText: qsTr("Support tag: ") + "\n" + supportTag + "\n" + qsTr("Last updated: ") + lastUpdate - rightImageSource: "qrc:/images/controls/trash.svg" - - clickedFunction: function() { - var headerText = qsTr("Deactivate the subscription on selected device") - var descriptionText = qsTr("The next time the “Connect” button is pressed, the device will be activated again") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - Qt.callLater(deactivateExternalDevice, supportTag, countryCode) - } - var noButtonFunction = function() { - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - - DividerType {} - } - } - - function deactivateExternalDevice(supportTag, countryCode) { - PageController.showBusyIndicator(true) - if (ApiConfigsController.deactivateExternalDevice(supportTag, countryCode)) { - ApiSettingsController.getAccountInfo(true) - } - PageController.showBusyIndicator(false) - } -} diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml deleted file mode 100644 index 3651407b..00000000 --- a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml +++ /dev/null @@ -1,124 +0,0 @@ -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 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("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#macos") - } - - QtObject { - id: android - - readonly property string title: qsTr("Android") - readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#android") - } - - QtObject { - id: androidTv - - readonly property string title: qsTr("AndroidTV") - readonly property string link: qsTr("https://docs.amnezia.org/ru/documentation/instructions/android_tv_connect/") - } - - QtObject { - id: ios - - readonly property string title: qsTr("iOS") - 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("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#linux") - } - - QtObject { - id: routers - - readonly property string title: qsTr("Routers") - readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#routers") - } - - property list instructionsModel: [ - windows, - macos, - android, - androidTv, - ios, - linux, - routers - ] - - ListViewType { - id: listView - - anchors.fill: parent - anchors.topMargin: 20 - anchors.bottomMargin: 24 - - model: instructionsModel - - header: ColumnLayout { - width: listView.width - - BackButtonType { - id: backButton - } - - HeaderType { - id: header - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - - headerText: qsTr("How to connect on another device") - descriptionText: qsTr("Instructions on the Amnezia website") - } - } - - delegate: ColumnLayout { - width: listView.width - - LabelWithButtonType { - Layout.fillWidth: true - Layout.topMargin: 6 - - text: title - rightImageSource: "qrc:/images/controls/external-link.svg" - - clickedFunction: function() { - Qt.openUrlExternally(link) - } - } - - DividerType {} - } - } -} diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml deleted file mode 100644 index a1cc1fb8..00000000 --- a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml +++ /dev/null @@ -1,214 +0,0 @@ -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 { - Layout.fillWidth: true - Layout.topMargin: 6 - - text: countryName - descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : "" - descriptionColor: AmneziaStyle.color.vibrantRed - - leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" - rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg" - - clickedFunction: function() { - if (isIssued) { - moreOptionsDrawer.countryName = countryName - moreOptionsDrawer.countryCode = countryCode - moreOptionsDrawer.openTriggered() - } else { - 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") - - 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 {} - } - } - } - } - - 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(true) - } - - 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(true) - } - 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) - } - moreOptionsDrawer.closeTriggered() - } - var noButtonFunction = function() { - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } -} diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml deleted file mode 100644 index 424e10c5..00000000 --- a/client/ui/qml/Pages2/PageSettingsApiSupport.qml +++ /dev/null @@ -1,127 +0,0 @@ -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: telegram - - readonly property string title: qsTr("Telegram") - readonly property string description: "@" + ApiAccountInfoModel.getTelegramBotLink() - readonly property string link: "https://t.me/" + ApiAccountInfoModel.getTelegramBotLink() - } - - QtObject { - id: techSupport - - readonly property string title: qsTr("For technical support") - readonly property string description: qsTr("support@amnezia.org") - readonly property string link: "mailto:support@amnezia.org" - } - - QtObject { - id: paymentSupport - - readonly property string title: qsTr("For payment issues") - readonly property string description: qsTr("help@vpnpay.io") - readonly property string link: "mailto:help@vpnpay.io" - } - - QtObject { - id: site - - readonly property string title: qsTr("Site") - readonly property string description: qsTr("amnezia.org") - readonly property string link: LanguageModel.getCurrentSiteUrl() - } - - property list supportModel: [ - telegram, - techSupport, - paymentSupport, - site - ] - - ListViewType { - id: listView - - anchors.fill: parent - anchors.topMargin: 20 - anchors.bottomMargin: 24 - - model: supportModel - - header: ColumnLayout { - width: listView.width - - BackButtonType { - id: backButton - } - - HeaderType { - id: header - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - - headerText: qsTr("Support") - descriptionText: qsTr("Our technical support specialists are ready to help you at any time") - } - } - - delegate: ColumnLayout { - width: listView.width - - LabelWithButtonType { - Layout.fillWidth: true - text: title - descriptionText: description - rightImageSource: "qrc:/images/controls/external-link.svg" - clickedFunction: function() { - Qt.openUrlExternally(link) - } - } - DividerType {} - } - - - footer: ColumnLayout { - width: listView.width - - 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() - } - } - } - } - } -}