Compare commits

...
Sign in to create a new pull request.

17 commits

Author SHA1 Message Date
lunardunno
780a0929d7 Revert "polishing 2"
This reverts commit 64552d6080.
2025-03-01 17:18:13 +04:00
lunardunno
059257fc58
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 <nethiuswork@gmail.com>

* 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 <nethiuswork@gmail.com>
Co-authored-by: pokamest <pokamest@gmail.com>
Co-authored-by: Mykola Baibuz <mykola.baibuz@gmail.com>
Co-authored-by: Yaroslav Yashin <yaroslav.yashin@gmail.com>
Co-authored-by: KsZnak <ksu@amnezia.org>
Co-authored-by: Cyril Anisimov <CyAn84@gmail.com>
2025-03-01 16:08:52 +04:00
lunardunno
64552d6080
polishing 2
extra space removed
2025-03-01 14:48:08 +04:00
lunardunno
0637c0f596
polishing
extra space removed
2025-03-01 14:26:05 +04:00
lunardunno
d5a9d602fd
Merge pull request #1278 from amnezia-vpn/checking_sudo_permissions
Checking server user permissions to use sudo
2025-01-16 01:23:12 +04:00
lunardunno
464aa4ceae
Merge branch 'check_sudo_permissions' into checking_sudo_permissions 2025-01-16 01:18:42 +04:00
lunardunno
db83555a03
Checking server user permissions to use sudo
Checking server user permissions to use sudo using a package manager.
2025-01-16 01:14:42 +04:00
lunardunno
7b2a4ea922
improved script readability 2024-12-10 20:14:29 +04:00
lunardunno
795ccaa080
added timestamp removal for sudo 2024-12-10 06:13:08 +04:00
lunardunno
ce4cb28e74
improved script readability 2024-12-10 05:44:55 +04:00
lunardunno
c9423dd9af
improved script readability 2024-12-09 15:31:58 +04:00
lunardunno
055c8f7b66
splitting the sudo check script
The sudo command check script is split to check permissions correctly
2024-12-09 08:51:55 +04:00
lunardunno
2b85dafa15
сhanged extended description
Changed extended description for: not allowed in sudoers.
2024-12-09 08:31:19 +04:00
lunardunno
45fb4b0982
exception for root
Exception for root if sudo package is not installed
2024-12-08 14:31:23 +04:00
lunardunno
62497024f9
script simplification
Simplifying the script for later adding an exception for root to the server controller
2024-12-08 14:22:10 +04:00
lunardunno
076b076cd9
Verifying the server user to work with sudo (#1254)
* 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
2024-12-01 13:51:03 +04:00
lunardunno
85fa1ad8b1
added check for the root user and the wheel group 2024-12-01 12:22:14 +04:00
90 changed files with 1022 additions and 2668 deletions

@ -1 +0,0 @@
Subproject commit 7c821a8d5c1ad5ad94e0763b4f25a875b5a6fe1b

View file

@ -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) qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
# -- i18n end # -- 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}) set(IS_CI ${CI})
if(IS_CI) if(IS_CI)
message("Detected CI env") message("Detected CI env")
@ -110,8 +105,8 @@ if(IS_CI)
endif() endif()
endif() endif()
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake) include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
include_directories( include_directories(
${CMAKE_CURRENT_LIST_DIR}/../ipc ${CMAKE_CURRENT_LIST_DIR}/../ipc
@ -120,167 +115,22 @@ include_directories(
${CMAKE_CURRENT_BINARY_DIR} ${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)
include_directories(mozilla/shared) include_directories(mozilla/shared)
include_directories(mozilla/models) include_directories(mozilla/models)
if(NOT IOS) configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
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
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG") target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
endif() 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) if(WIN32)
configure_file( configure_file(
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in ${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc ${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} set(LIBS ${LIBS}
user32 user32
rasapi32 rasapi32
@ -324,30 +174,6 @@ endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build") message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP) 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() endif()
if(ANDROID) if(ANDROID)

View file

@ -2,6 +2,8 @@
#include <QClipboard> #include <QClipboard>
#include <QFontDatabase> #include <QFontDatabase>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMimeData> #include <QMimeData>
#include <QQuickItem> #include <QQuickItem>
#include <QQuickStyle> #include <QQuickStyle>
@ -10,26 +12,16 @@
#include <QTextDocument> #include <QTextDocument>
#include <QTimer> #include <QTimer>
#include <QTranslator> #include <QTranslator>
#include <QLocalSocket>
#include <QLocalServer>
#include "logger.h" #include "logger.h"
#include "ui/controllers/pageController.h"
#include "ui/models/installedAppsModel.h" #include "ui/models/installedAppsModel.h"
#include "version.h" #include "version.h"
#include "platforms/ios/QRCodeReaderBase.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" #include "protocols/qml_register_protocols.h"
#if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h"
#include <AmneziaVPN-Swift.h>
#endif
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv) AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
{ {
setQuitOnLastWindowClosed(false); setQuitOnLastWindowClosed(false);
@ -84,79 +76,12 @@ void AmneziaApplication::init()
m_vpnConnection->moveToThread(&m_vpnConnectionThread); m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start(); m_vpnConnectionThread.start();
initModels(); m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
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<void (ConnectionController::*)()>(&ConnectionController::openConnection));
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
#endif
m_engine->addImportPath("qrc:/ui/qml/Modules/"); m_engine->addImportPath("qrc:/ui/qml/Modules/");
m_engine->load(url); m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
m_coreController->setQmlRoot();
bool enabled = m_settings->isSaveLogs(); bool enabled = m_settings->isSaveLogs();
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
@ -168,13 +93,13 @@ void AmneziaApplication::init()
#endif #endif
Logger::setServiceLogsEnabled(enabled); Logger::setServiceLogsEnabled(enabled);
#ifdef Q_OS_WIN #ifdef Q_OS_WIN //TODO
if (m_parser.isSet("a")) if (m_parser.isSet("a"))
m_pageController->showOnStartup(); m_coreController->pageController()->showOnStartup();
else else
emit m_pageController->raiseMainWindow(); emit m_coreController->pageController()->raiseMainWindow();
#else #else
m_pageController->showOnStartup(); m_coreController->pageController()->showOnStartup();
#endif #endif
// Android TextArea clipboard workaround // Android TextArea clipboard workaround
@ -231,33 +156,6 @@ void AmneziaApplication::loadFonts()
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf"); 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() bool AmneziaApplication::parseCommands()
{ {
m_parser.setApplicationDescription(APPLICATION_NAME); m_parser.setApplicationDescription(APPLICATION_NAME);
@ -282,7 +180,8 @@ bool AmneziaApplication::parseCommands()
} }
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void AmneziaApplication::startLocalServer() { void AmneziaApplication::startLocalServer()
{
const QString serverName("AmneziaVPNInstance"); const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName); QLocalServer::removeServer(serverName);
@ -294,7 +193,7 @@ void AmneziaApplication::startLocalServer() {
QLocalSocket *clientConnection = server->nextPendingConnection(); QLocalSocket *clientConnection = server->nextPendingConnection();
clientConnection->deleteLater(); clientConnection->deleteLater();
} }
emit m_pageController->raiseMainWindow(); emit m_coreController->pageController()->raiseMainWindow(); //TODO
}); });
} }
#endif #endif
@ -304,163 +203,12 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
return m_engine; return m_engine;
} }
void AmneziaApplication::initModels() QNetworkAccessManager *AmneziaApplication::networkManager()
{ {
m_containersModel.reset(new ContainersModel(this)); return m_nam;
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()); });
} }
void AmneziaApplication::initControllers() QClipboard *AmneziaApplication::getClipboard()
{ {
m_connectionController.reset( return this->clipboard();
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<const QString &>(&ConnectionController::connectionErrorOccurred), this,
[this](const QString &errorMessage) {
emit m_pageController->showErrorMessage(errorMessage);
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_connectionController.get(), qOverload<ErrorCode>(&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<ErrorCode>(&InstallController::installationErrorOccurred), this,
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
static_cast<Qt::ConnectionType>(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<ErrorCode>(&InstallController::installationErrorOccurred), this,
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
static_cast<Qt::ConnectionType>(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());
} }

View file

@ -11,44 +11,12 @@
#else #else
#include <QApplication> #include <QApplication>
#endif #endif
#include <QClipboard>
#include "core/controllers/coreController.h"
#include "settings.h" #include "settings.h"
#include "vpnconnection.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<AmneziaApplication *>(QCoreApplication::instance())) #define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
@ -67,8 +35,6 @@ public:
void init(); void init();
void registerTypes(); void registerTypes();
void loadFonts(); void loadFonts();
void loadTranslator();
void updateTranslator(const QLocale &locale);
bool parseCommands(); bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
@ -76,68 +42,24 @@ public:
#endif #endif
QQmlApplicationEngine *qmlEngine() const; QQmlApplicationEngine *qmlEngine() const;
QNetworkAccessManager *manager() { return m_nam; } QNetworkAccessManager *networkManager();
QClipboard *getClipboard();
signals:
void translationsUpdated();
private: private:
void initModels();
void initControllers();
QQmlApplicationEngine *m_engine {}; QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
QScopedPointer<CoreController> m_coreController;
QSharedPointer<ContainerProps> m_containerProps; QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps; QSharedPointer<ProtocolProps> m_protocolProps;
QSharedPointer<QTranslator> m_translator;
QCommandLineParser m_parser; QCommandLineParser m_parser;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
QSharedPointer<ApiServicesModel> m_apiServicesModel;
QSharedPointer<ApiCountryModel> m_apiCountryModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QScopedPointer<AwgConfigModel> m_awgConfigModel;
#ifdef Q_OS_WINDOWS
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
#endif
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
QSharedPointer<VpnConnection> m_vpnConnection; QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread; QThread m_vpnConnectionThread;
#ifndef Q_OS_ANDROID
QScopedPointer<NotificationHandler> m_notificationHandler;
#endif
QScopedPointer<ConnectionController> m_connectionController;
QScopedPointer<FocusController> m_focusController;
QScopedPointer<PageController> m_pageController;
QScopedPointer<InstallController> m_installController;
QScopedPointer<ImportController> m_importController;
QScopedPointer<ExportController> m_exportController;
QScopedPointer<SettingsController> m_settingsController;
QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SystemController> m_systemController;
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
QNetworkAccessManager *m_nam; QNetworkAccessManager *m_nam;
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
}; };
#endif // AMNEZIA_APPLICATION_H #endif // AMNEZIA_APPLICATION_H

View file

@ -76,11 +76,7 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks" XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
XCODE_EMBED_APP_EXTENSIONS networkextension XCODE_EMBED_APP_EXTENSIONS networkextension
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
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"
) )
set_target_properties(${PROJECT} PROPERTIES set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0" XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
@ -126,9 +122,9 @@ add_subdirectory(ios/networkextension)
add_dependencies(${PROJECT} networkextension) add_dependencies(${PROJECT} networkextension)
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS 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) 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/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework") target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")

View file

@ -110,22 +110,19 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its " 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.") }, "own security protocol with SSL/TLS for key exchange.") },
{ DockerContainer::ShadowSocks, { DockerContainer::ShadowSocks,
QObject::tr("Shadowsocks - masks VPN traffic, making it similar to normal web traffic, but it " QObject::tr("Shadowsocks masks VPN traffic, making it resemble normal web traffic, but it may still be detected by certain analysis systems.") },
"may be recognized by analysis systems in some highly censored regions.") },
{ DockerContainer::Cloak, { DockerContainer::Cloak,
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against " 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 " "active-probing detection. It is very resistant to detection, but offers low speed.") },
"of censorship.") },
{ DockerContainer::WireGuard, { DockerContainer::WireGuard,
QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
"consumption. Recommended for regions with low levels of censorship.") }, "consumption.") },
{ DockerContainer::Awg, { DockerContainer::Awg,
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, " QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
"but very resistant to blockages. " "It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
"Recommended for regions with high levels of censorship.") },
{ DockerContainer::Xray, { DockerContainer::Xray,
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. " QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") }, "It is highly resistant to detection and offers high speed.") },
{ DockerContainer::Ipsec, { DockerContainer::Ipsec,
QObject::tr("IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after " 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.") }, "signal loss. It has native support on the latest versions of Android and iOS.") },
@ -156,7 +153,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Available in the AmneziaVPN across all platforms\n" "* Available in the AmneziaVPN across all platforms\n"
"* Normal power consumption on mobile devices\n" "* Normal power consumption on mobile devices\n"
"* Flexible customisation to suit user needs to work with different operating systems and 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" "* Recognised by DPI systems and therefore susceptible to blocking\n"
"* Can operate over both TCP and UDP network protocols.") }, "* Can operate over both TCP and UDP network protocols.") },
{ DockerContainer::ShadowSocks, { DockerContainer::ShadowSocks,
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. " QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
@ -169,28 +166,26 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Works over TCP network protocol.") }, "* Works over TCP network protocol.") },
{ DockerContainer::Cloak, { DockerContainer::Cloak,
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for " 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 " "OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
"and the server.\n\n" "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, " "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 " "and also protects the VPN from detection by Active Probing. This makes it very resistant to "
"being detected\n\n" "being detected\n\n"
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. " "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 " "If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
"invisible to analysis systems.\n\n" "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" "* Available in the AmneziaVPN across all platforms\n"
"* High power consumption on mobile devices\n" "* High power consumption on mobile devices\n"
"* Flexible settings\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") }, "* Works over TCP network protocol, 443 port.\n") },
{ DockerContainer::WireGuard, { DockerContainer::WireGuard,
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n" 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 " "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" "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, " "Unlike some other VPN protocols that employ obfuscation techniques, "
"the consistent signature patterns of WireGuard packets can be more easily identified and " "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" "thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
@ -213,18 +208,18 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Available in the AmneziaVPN across all platforms\n" "* Available in the AmneziaVPN across all platforms\n"
"* Low power consumption\n" "* Low power consumption\n"
"* Minimum number of settings\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.") }, "* Works over UDP network protocol.") },
{ DockerContainer::Xray, { DockerContainer::Xray,
QObject::tr("The REALITY protocol, a pioneering development by the creators of 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" "is designed to provide the highest level of protection against detection through its innovative approach to security and privacy.\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, " "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" "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, " "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" "legitimate sites without the need for specific configurations. \n"
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, " "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. " "REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security. "
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.") "This makes REALITY a robust solution for maintaining internet freedom.")
}, },
{ DockerContainer::Ipsec, { DockerContainer::Ipsec,
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n" 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) bool ContainerProps::isEasySetupContainer(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg: return true; case DockerContainer::Awg: return true;
// case DockerContainer::Cloak: return true;
default: return false; default: return false;
} }
} }
@ -342,9 +335,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
QString ContainerProps::easySetupHeader(DockerContainer container) QString ContainerProps::easySetupHeader(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::WireGuard: return tr("Low"); case DockerContainer::Awg: return tr("Automatic");
case DockerContainer::Awg: return tr("High");
// case DockerContainer::Cloak: return tr("Extreme");
default: return ""; default: return "";
} }
} }
@ -352,10 +343,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
QString ContainerProps::easySetupDescription(DockerContainer container) QString ContainerProps::easySetupDescription(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy."); case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. "
case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases."); "It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
// case DockerContainer::Cloak:
// return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
default: return ""; default: return "";
} }
} }
@ -363,9 +352,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
int ContainerProps::easySetupOrder(DockerContainer container) int ContainerProps::easySetupOrder(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::WireGuard: return 3; case DockerContainer::Awg: return 1;
case DockerContainer::Awg: return 2;
// case DockerContainer::Cloak: return 1;
default: return 0; default: return 0;
} }
} }

View file

@ -1,509 +0,0 @@
#include "apiController.h"
#include <algorithm>
#include <random>
#include <QEventLoop>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QtConcurrent>
#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<QSslError> &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<void>(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("<key>", "<key>\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<QSslError> 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<QSslError> &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<QSslError> &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<QSslError> sslErrors;
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &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<QSslError> &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<QSslError> sslErrors;
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &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<QSslError> &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;
}

View file

@ -1,50 +0,0 @@
#ifndef APICONTROLLER_H
#define APICONTROLLER_H
#include <QObject>
#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

View file

@ -757,10 +757,6 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container) ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
{ {
if (credentials.userName == "root") {
return ErrorCode::NoError;
}
QString stdOut; QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) { auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n"; stdOut += data + "\n";
@ -774,8 +770,14 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo); const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); 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 ErrorCode::ServerUserNotInSudo;
if (credentials.userName != "root" && 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; return error;
} }

View file

@ -77,8 +77,7 @@ ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isA
} }
QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig, QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
const QJsonObject &containerConfig, const DockerContainer container, const QJsonObject &containerConfig, const DockerContainer container)
ErrorCode &errorCode)
{ {
QJsonObject vpnConfiguration {}; QJsonObject vpnConfiguration {};
@ -103,7 +102,8 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) { if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) {
// add mtu for old configs // add mtu for old configs
if (vpnConfigData[config_key::mtu].toString().isEmpty()) { if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
vpnConfigData[config_key::mtu] = container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu; vpnConfigData[config_key::mtu] =
container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
} }
} }

View file

@ -12,7 +12,8 @@ class VpnConfigurationsController : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit VpnConfigurationsController(const std::shared_ptr<Settings> &settings, QSharedPointer<ServerController> serverController, QObject *parent = nullptr); explicit VpnConfigurationsController(const std::shared_ptr<Settings> &settings, QSharedPointer<ServerController> serverController,
QObject *parent = nullptr);
public slots: public slots:
ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container, ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container,
@ -21,7 +22,7 @@ public slots:
const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol, const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol,
QString &protocolConfigString); QString &protocolConfigString);
QJsonObject createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig, QJsonObject createVpnConfiguration(const QPair<QString, QString> &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); static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut);
signals: signals:

View file

@ -6,9 +6,6 @@
namespace amnezia namespace amnezia
{ {
constexpr const qint16 qrMagicCode = 1984;
struct ServerCredentials struct ServerCredentials
{ {
QString hostName; QString hostName;
@ -47,6 +44,7 @@ namespace amnezia
InternalError = 101, InternalError = 101,
NotImplementedError = 102, NotImplementedError = 102,
AmneziaServiceNotRunning = 103, AmneziaServiceNotRunning = 103,
NotSupportedOnThisPlatform = 104,
// Server errors // Server errors
ServerCheckFailed = 200, ServerCheckFailed = 200,
@ -56,6 +54,9 @@ namespace amnezia
ServerCancelInstallation = 204, ServerCancelInstallation = 204,
ServerUserNotInSudo = 205, ServerUserNotInSudo = 205,
ServerPacketManagerError = 206, ServerPacketManagerError = 206,
SudoPackageIsNotPreinstalled = 207,
ServerUserNotAllowedInSudoers = 208,
ServerUserPasswordRequired = 209,
// Ssh connection errors // Ssh connection errors
SshRequestDeniedError = 300, SshRequestDeniedError = 300,
@ -97,6 +98,7 @@ namespace amnezia
// import and install errors // import and install errors
ImportInvalidConfigError = 900, ImportInvalidConfigError = 900,
ImportOpenConfigError = 901, ImportOpenConfigError = 901,
NoInstalledContainersError = 902,
// Android errors // Android errors
AndroidError = 1000, AndroidError = 1000,
@ -110,6 +112,8 @@ namespace amnezia
ApiMissingAgwPublicKey = 1105, ApiMissingAgwPublicKey = 1105,
ApiConfigDecryptionError = 1106, ApiConfigDecryptionError = 1106,
ApiServicesMissingError = 1107, ApiServicesMissingError = 1107,
ApiConfigLimitError = 1108,
ApiNotFoundError = 1109,
// QFile errors // QFile errors
OpenError = 1200, OpenError = 1200,

View file

@ -1,9 +0,0 @@
#ifndef APIENUMS_H
#define APIENUMS_H
enum ApiConfigSources {
Telegram = 1,
AmneziaGateway
};
#endif // APIENUMS_H

View file

@ -12,6 +12,7 @@ QString errorString(ErrorCode code) {
case(ErrorCode::UnknownError): errorMessage = QObject::tr("Unknown error"); break; case(ErrorCode::UnknownError): errorMessage = QObject::tr("Unknown error"); break;
case(ErrorCode::NotImplementedError): errorMessage = QObject::tr("Function not implemented"); 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::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 // Server errors
case(ErrorCode::ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break; case(ErrorCode::ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break;
@ -19,8 +20,11 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break; 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::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
case(ErrorCode::ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); 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::ServerUserNotInSudo): errorMessage = QObject::tr("The user is not a member of the sudo group"); break;
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); 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("Action not allowed in sudoers"); break;
case(ErrorCode::ServerUserPasswordRequired): errorMessage = QObject::tr("The user's password is required"); break;
// Libssh errors // Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break; case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
@ -51,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::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::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 // Android errors
case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break; case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
@ -64,6 +69,8 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break; 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::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break;
case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); 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 // QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;

View file

@ -5,12 +5,12 @@ IpcClient *IpcClient::m_instance = nullptr;
IpcClient::IpcClient(QObject *parent) : QObject(parent) IpcClient::IpcClient(QObject *parent) : QObject(parent)
{ {
} }
IpcClient::~IpcClient() IpcClient::~IpcClient()
{ {
if (m_localSocket) m_localSocket->close(); if (m_localSocket)
m_localSocket->close();
} }
bool IpcClient::isSocketConnected() const bool IpcClient::isSocketConnected() const
@ -25,13 +25,15 @@ IpcClient *IpcClient::Instance()
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface() QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
{ {
if (!Instance()) return nullptr; if (!Instance())
return nullptr;
return Instance()->m_ipcClient; return Instance()->m_ipcClient;
} }
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks() QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
{ {
if (!Instance()) return nullptr; if (!Instance())
return nullptr;
return Instance()->m_Tun2SocksClient; return Instance()->m_Tun2SocksClient;
} }
@ -42,15 +44,28 @@ bool IpcClient::init(IpcClient *instance)
Instance()->m_localSocket = new QLocalSocket(Instance()); Instance()->m_localSocket = new QLocalSocket(Instance());
connect(Instance()->m_localSocket.data(), &QLocalSocket::connected, &Instance()->m_ClientNode, []() { connect(Instance()->m_localSocket.data(), &QLocalSocket::connected, &Instance()->m_ClientNode, []() {
Instance()->m_ClientNode.addClientSideConnection(Instance()->m_localSocket.data()); Instance()->m_ClientNode.addClientSideConnection(Instance()->m_localSocket.data());
auto cliNode = Instance()->m_ClientNode.acquire<IpcInterfaceReplica>();
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<IpcInterfaceReplica>());
Instance()->m_ipcClient->waitForSource(1000); Instance()->m_ipcClient->waitForSource(1000);
if (!Instance()->m_ipcClient->isReplicaValid()) { if (!Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient replica is not connected!"; qWarning() << "IpcClient replica is not connected!";
} }
Instance()->m_Tun2SocksClient.reset(Instance()->m_ClientNode.acquire<IpcProcessTun2SocksReplica>()); auto t2sNode = Instance()->m_ClientNode.acquire<IpcProcessTun2SocksReplica>();
t2sNode->waitForSource(5000);
Instance()->m_Tun2SocksClient.reset(t2sNode);
if (!Instance()->m_Tun2SocksClient) {
qWarning() << "IpcClient::m_Tun2SocksClient is not ready!";
}
Instance()->m_Tun2SocksClient->waitForSource(1000); Instance()->m_Tun2SocksClient->waitForSource(1000);
if (!Instance()->m_Tun2SocksClient->isReplicaValid()) { if (!Instance()->m_Tun2SocksClient->isReplicaValid()) {
@ -58,9 +73,8 @@ bool IpcClient::init(IpcClient *instance)
} }
}); });
connect(Instance()->m_localSocket, &QLocalSocket::disconnected, [instance](){ connect(Instance()->m_localSocket, &QLocalSocket::disconnected,
instance->m_isSocketConnected = false; [instance]() { instance->m_isSocketConnected = false; });
});
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl()); Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
Instance()->m_localSocket->waitForConnected(); Instance()->m_localSocket->waitForConnected();
@ -100,18 +114,15 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
pd->ipcProcess.reset(priv); pd->ipcProcess.reset(priv);
if (!pd->ipcProcess) { if (!pd->ipcProcess) {
qWarning() << "Acquire PrivilegedProcess failed"; qWarning() << "Acquire PrivilegedProcess failed";
} } else {
else {
pd->ipcProcess->waitForSource(1000); pd->ipcProcess->waitForSource(1000);
if (!pd->ipcProcess->isReplicaValid()) { if (!pd->ipcProcess->isReplicaValid()) {
qWarning() << "PrivilegedProcess replica is not connected!"; qWarning() << "PrivilegedProcess replica is not connected!";
} }
QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(), [pd](){ QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(),
pd->replicaNode->deleteLater(); [pd]() { pd->replicaNode->deleteLater(); });
});
} }
}); });
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid)); pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
pd->localSocket->waitForConnected(); pd->localSocket->waitForConnected();
@ -119,5 +130,3 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess); auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return processReplica; return processReplica;
} }

View file

@ -5,6 +5,7 @@
#include <QRegExp> #include <QRegExp>
#include <QString> #include <QString>
#include <QHostAddress> #include <QHostAddress>
#include <QNetworkReply>
class NetworkUtilities : public QObject class NetworkUtilities : public QObject
@ -30,7 +31,6 @@ public:
static QString ipAddressFromIpWithSubnet(const QString ip); static QString ipAddressFromIpWithSubnet(const QString ip);
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr); static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
}; };
#endif // NETWORKUTILITIES_H #endif // NETWORKUTILITIES_H

View file

@ -27,12 +27,7 @@ set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks" XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
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"
) )
set_target_properties(networkextension PROPERTIES set_target_properties(networkextension PROPERTIES

View file

@ -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 ../../

View file

@ -1,4 +1,5 @@
import HevSocks5Tunnel import HevSocks5Tunnel
import NetworkExtension
public enum Socks5Tunnel { public enum Socks5Tunnel {

View file

@ -238,7 +238,7 @@ ErrorCode Ikev2Protocol::start()
"-CipherTransformConstants GCMAES128 " "-CipherTransformConstants GCMAES128 "
"-EncryptionMethod AES256 " "-EncryptionMethod AES256 "
"-IntegrityCheckMethod SHA256 " "-IntegrityCheckMethod SHA256 "
"-PfsGroup None " "-PfsGroup PFS2048 "
"-DHGroup Group14 " "-DHGroup Group14 "
"-PassThru -Force\"") "-PassThru -Force\"")
.arg(tunnelName()); .arg(tunnelName());

View file

@ -1,16 +1,14 @@
#include "xrayprotocol.h" #include "xrayprotocol.h"
#include "utilities.h"
#include "core/networkUtilities.h"
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QNetworkInterface> #include <QNetworkInterface>
#include "core/networkUtilities.h"
#include "utilities.h"
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent): XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
VpnProtocol(configuration, parent)
{ {
readXrayConfiguration(configuration); readXrayConfiguration(configuration);
m_routeGateway = NetworkUtilities::getGatewayAndIface(); m_routeGateway = NetworkUtilities::getGatewayAndIface();
@ -45,10 +43,7 @@ ErrorCode XrayProtocol::start()
QStringList args = QStringList() << "-c" << m_xrayCfgFile.fileName() << "-format=json"; QStringList args = QStringList() << "-c" << m_xrayCfgFile.fileName() << "-format=json";
qDebug().noquote() << "XrayProtocol::start()" qDebug().noquote() << "XrayProtocol::start()" << xrayExecPath() << args.join(" ");
<< xrayExecPath() << args.join(" ");
m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels); m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
m_xrayProcess.setProgram(xrayExecPath()); m_xrayProcess.setProgram(xrayExecPath());
@ -66,7 +61,8 @@ ErrorCode XrayProtocol::start()
#endif #endif
}); });
connect(&m_xrayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) { connect(&m_xrayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
[this](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus; qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus;
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) { if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
@ -82,10 +78,9 @@ ErrorCode XrayProtocol::start()
setConnectionState(Vpn::ConnectionState::Connecting); setConnectionState(Vpn::ConnectionState::Connecting);
QThread::msleep(1000); QThread::msleep(1000);
return startTun2Sock(); return startTun2Sock();
} else
return ErrorCode::XrayExecutableMissing;
} }
else return ErrorCode::XrayExecutableMissing;
}
ErrorCode XrayProtocol::startTun2Sock() ErrorCode XrayProtocol::startTun2Sock()
{ {
@ -98,11 +93,9 @@ ErrorCode XrayProtocol::startTun2Sock()
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this, connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; }); [&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, [&](int vpnState) {
[&](int vpnState) {
qDebug() << "PrivilegedProcess setConnectionState " << vpnState; qDebug() << "PrivilegedProcess setConnectionState " << vpnState;
if (vpnState == Vpn::ConnectionState::Connected) if (vpnState == Vpn::ConnectionState::Connected) {
{
setConnectionState(Vpn::ConnectionState::Connecting); setConnectionState(Vpn::ConnectionState::Connecting);
QList<QHostAddress> dnsAddr; QList<QHostAddress> dnsAddr;
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString())); dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString()));
@ -127,7 +120,7 @@ ErrorCode XrayProtocol::startTun2Sock()
IpcClient::Interface()->enableKillSwitch(m_configData, 0); IpcClient::Interface()->enableKillSwitch(m_configData, 0);
} }
#endif #endif
if (m_routeMode == 0) { if (m_routeMode == Settings::RouteMode::VpnAllSites) {
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1"); 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_vpnGateway, QStringList() << "128.0.0.0/1");
IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress); IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress);
@ -137,8 +130,7 @@ ErrorCode XrayProtocol::startTun2Sock()
IpcClient::Interface()->updateResolvers("tun2", dnsAddr); IpcClient::Interface()->updateResolvers("tun2", dnsAddr);
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces(); QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) { for (int i = 0; i < netInterfaces.size(); i++) {
for (int j = 0; j < netInterfaces.at(i).addressEntries().size(); j++) for (int j = 0; j < netInterfaces.at(i).addressEntries().size(); j++) {
{
// killSwitch toggle // killSwitch toggle
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
@ -204,7 +196,7 @@ void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration)
m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt(); m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt();
m_remoteHost = configuration.value(amnezia::config_key::hostName).toString(); m_remoteHost = configuration.value(amnezia::config_key::hostName).toString();
m_remoteAddress = NetworkUtilities::getIPAddress(m_remoteHost); m_remoteAddress = NetworkUtilities::getIPAddress(m_remoteHost);
m_routeMode = configuration.value(amnezia::config_key::splitTunnelType).toInt(); m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString(); m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString();
m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString(); m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString();
} }

View file

@ -1,9 +1,11 @@
#ifndef XRAYPROTOCOL_H #ifndef XRAYPROTOCOL_H
#define XRAYPROTOCOL_H #define XRAYPROTOCOL_H
#include "openvpnprotocol.h"
#include "QProcess" #include "QProcess"
#include "containers/containers_defs.h" #include "containers/containers_defs.h"
#include "openvpnprotocol.h"
#include "settings.h"
class XrayProtocol : public VpnProtocol class XrayProtocol : public VpnProtocol
{ {
@ -24,11 +26,12 @@ protected:
private: private:
static QString xrayExecPath(); static QString xrayExecPath();
static QString tun2SocksExecPath(); static QString tun2SocksExecPath();
private: private:
int m_localPort; int m_localPort;
QString m_remoteHost; QString m_remoteHost;
QString m_remoteAddress; QString m_remoteAddress;
int m_routeMode; Settings::RouteMode m_routeMode;
QJsonObject m_configData; QJsonObject m_configData;
QString m_primaryDNS; QString m_primaryDNS;
QString m_secondaryDNS; QString m_secondaryDNS;
@ -37,7 +40,6 @@ private:
QSharedPointer<IpcProcessTun2SocksReplica> m_t2sProcess; QSharedPointer<IpcProcessTun2SocksReplica> m_t2sProcess;
#endif #endif
QTemporaryFile m_xrayCfgFile; QTemporaryFile m_xrayCfgFile;
}; };
#endif // XRAYPROTOCOL_H #endif // XRAYPROTOCOL_H

View file

@ -192,7 +192,7 @@
<file>ui/qml/Pages2/PageServiceTorWebsiteSettings.qml</file> <file>ui/qml/Pages2/PageServiceTorWebsiteSettings.qml</file>
<file>ui/qml/Pages2/PageSettings.qml</file> <file>ui/qml/Pages2/PageSettings.qml</file>
<file>ui/qml/Pages2/PageSettingsAbout.qml</file> <file>ui/qml/Pages2/PageSettingsAbout.qml</file>
<file>ui/qml/Pages2/PageSettingsApiLanguageList.qml</file> <file>ui/qml/Pages2/PageSettingsApiAvailableCountries.qml</file>
<file>ui/qml/Pages2/PageSettingsApiServerInfo.qml</file> <file>ui/qml/Pages2/PageSettingsApiServerInfo.qml</file>
<file>ui/qml/Pages2/PageSettingsApplication.qml</file> <file>ui/qml/Pages2/PageSettingsApplication.qml</file>
<file>ui/qml/Pages2/PageSettingsAppSplitTunneling.qml</file> <file>ui/qml/Pages2/PageSettingsAppSplitTunneling.qml</file>
@ -224,6 +224,13 @@
<file>ui/qml/Pages2/PageShare.qml</file> <file>ui/qml/Pages2/PageShare.qml</file>
<file>ui/qml/Pages2/PageShareFullAccess.qml</file> <file>ui/qml/Pages2/PageShareFullAccess.qml</file>
<file>ui/qml/Pages2/PageStart.qml</file> <file>ui/qml/Pages2/PageStart.qml</file>
<file>ui/qml/Components/RenameServerDrawer.qml</file>
<file>ui/qml/Controls2/ListViewType.qml</file>
<file>ui/qml/Pages2/PageSettingsApiSupport.qml</file>
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
<file>images/controls/monitor.svg</file>
</qresource> </qresource>
<qresource prefix="/countriesFlags"> <qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file> <file>images/flagKit/ZW.svg</file>

View file

@ -15,6 +15,12 @@
using namespace QKeychain; 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) SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent)
: QObject { parent }, m_settings(organization, application, parent), encryptedKeys({ "Servers/serversList" }) : 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 // check if value is not encrypted, v. < 2.0.x
retVal = m_settings.value(key); retVal = m_settings.value(key);
if (retVal.isValid()) { 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()) { if (getEncKey().isEmpty() || getEncIv().isEmpty()) {
qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty"; qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty";

View file

@ -8,10 +8,6 @@
#include "keychain.h" #include "keychain.h"
constexpr const char *settingsKeyTag = "settingsKeyTag";
constexpr const char *settingsIvTag = "settingsIvTag";
constexpr const char *keyChainName = "AmneziaVPN-Keychain";
class SecureQSettings : public QObject class SecureQSettings : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -44,7 +40,7 @@ public:
private: private:
QSettings m_settings; QSettings m_settings;
mutable QMap<QString, QVariant> m_cache; mutable QHash<QString, QVariant> m_cache;
QStringList encryptedKeys; // encode only key listed here QStringList encryptedKeys; // encode only key listed here
// only this fields need for backup // only this fields need for backup

View file

@ -1,2 +1,11 @@
CUR_USER=$(whoami);\ if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
groups $CUR_USER 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

View file

@ -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";\ 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 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 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;\ 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";\ 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;\ 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;\ sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl enable --now docker; sleep 5;\ sleep 5; sudo systemctl enable --now docker; sleep 5;\
fi;\ 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 \ if [ "$(systemctl is-active docker)" != "active" ]; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\ sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl start docker; sleep 5;\ sleep 5; sudo systemctl start docker; sleep 5;\

View file

@ -33,14 +33,14 @@ conn shared
right=%any right=%any
encapsulation=yes encapsulation=yes
authby=secret authby=secret
pfs=no pfs=yes
rekey=no rekey=no
keyingtries=5 keyingtries=5
dpddelay=30 dpddelay=30
dpdtimeout=120 dpdtimeout=120
dpdaction=clear dpdaction=clear
ikev2=never 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 phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes256-sha2_512,aes128-sha2,aes256-sha2
ikelifetime=24h ikelifetime=24h
salifetime=24h salifetime=24h
@ -244,9 +244,9 @@ conn ikev2-cp
auto=add auto=add
ikev2=insist ikev2=insist
rekey=no rekey=no
pfs=no pfs=yes
ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1 ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1,aes256-sha2;modp2048,aes128-sha1;modp2048
phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes128-sha2,aes256-sha2 phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes256-sha2_512,aes128-sha2,aes256-sha2
ikelifetime=24h ikelifetime=24h
salifetime=24h salifetime=24h
encapsulation=yes encapsulation=yes

View file

@ -1,4 +1,4 @@
CUR_USER=$(whoami);\ CUR_USER=$(whoami 2> /dev/null || echo ~ | sed 's/.*\///');\
sudo mkdir -p $DOCKERFILE_FOLDER;\ sudo mkdir -p $DOCKERFILE_FOLDER;\
sudo chown $CUR_USER $DOCKERFILE_FOLDER;\ sudo chown $CUR_USER $DOCKERFILE_FOLDER;\
if ! sudo docker network ls | grep -q amnezia-dns-net; then sudo docker network create \ if ! sudo docker network ls | grep -q amnezia-dns-net; then sudo docker network create \

View file

@ -3334,8 +3334,8 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation>ليس لدي المستخدم الصلحيات لأستخدام sudo</translation> <translation>المستخدم ليس عضوًا في مجموعة sudo</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>
@ -3399,7 +3399,7 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation>خطأ في الخادم: خطأ في مدير الحزم</translation> <translation>خطأ في الخادم: خطأ في مدير الحزم</translation>
</message> </message>
<message> <message>

View file

@ -3468,8 +3468,8 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation>The user does not have permission to use sudo</translation> <translation>کاربر عضو گروه sudo نیست</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>
@ -3590,8 +3590,8 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation>Server error: Packet manager error</translation> <translation>خطای سرور: خطای مدیر بسته</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="34"/> <location filename="../core/errorstrings.cpp" line="34"/>

View file

@ -3434,13 +3434,13 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation> sudo ि </translation> <translation> sudo </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation> ि: ि</translation> <translation> ि: ि</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>

View file

@ -3330,8 +3330,8 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation> sudo ကက</translation> <translation> sudo </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>
@ -3395,8 +3395,8 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation> မှု: Packet Manager </translation> <translation> - Package manager </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="34"/> <location filename="../core/errorstrings.cpp" line="34"/>

View file

@ -3604,12 +3604,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation>У пользователя нет прав на использование sudo</translation> <translation>Пользователь не входит в группу sudo</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation>Ошибка сервера: ошибка менеджера пакетов</translation> <translation>Ошибка сервера: ошибка менеджера пакетов</translation>
</message> </message>
<message> <message>

View file

@ -3700,13 +3700,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation>The user does not have permission to use sudo</translation> <translation>Користувач не входить до групи sudo</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation type="unfinished"></translation> <translation>Помилка сервера: помилка менеджера пакетів</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>

View file

@ -3433,8 +3433,8 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation>صارف کو sudo استعمال کرنے کی اجازت نہیں ہے</translation> <translation>صارف sudo گروپ کا رکن نہیں ہے</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>
@ -3498,7 +3498,7 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation>سرور خطا: پیکیج منیجر خطا</translation> <translation>سرور خطا: پیکیج منیجر خطا</translation>
</message> </message>
<message> <message>

View file

@ -3675,13 +3675,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation>root权限</translation> <translation> sudo </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>

View file

@ -5,10 +5,8 @@
#else #else
#include <QApplication> #include <QApplication>
#endif #endif
#include <QtConcurrent>
#include "core/controllers/vpnConfigurationController.h" #include "core/controllers/vpnConfigurationController.h"
#include "core/enums/apiEnums.h"
#include "version.h" #include "version.h"
ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &serversModel, ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &serversModel,
@ -27,7 +25,7 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
connect(this, &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); 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::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; m_state = Vpn::ConnectionState::Disconnected;
} }
@ -35,8 +33,7 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
void ConnectionController::openConnection() void ConnectionController::openConnection()
{ {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #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); emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning);
return; return;
} }
@ -44,26 +41,24 @@ void ConnectionController::openConnection()
int serverIndex = m_serversModel->getDefaultServerIndex(); int serverIndex = m_serversModel->getDefaultServerIndex();
QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); 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<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
if (configVersion == ApiConfigSources::Telegram if (!m_containersModel->isSupportedByCurrentPlatform(container)) {
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { emit connectionErrorOccurred(ErrorCode::NotSupportedOnThisPlatform);
emit updateApiConfigFromTelegram(); return;
} 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();
} }
QSharedPointer<ServerController> 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() void ConnectionController::closeConnection()
@ -167,7 +162,7 @@ void ConnectionController::toggleConnection()
} else if (isConnected()) { } else if (isConnected()) {
closeConnection(); closeConnection();
} else { } else {
openConnection(); emit prepareConfig();
} }
} }
@ -180,98 +175,3 @@ bool ConnectionController::isConnected() const
{ {
return m_isConnected; 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<DockerContainer>(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> 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> serverController)
{
QFutureWatcher<ErrorCode> watcher;
if (serverController.isNull()) {
serverController.reset(new ServerController(m_settings));
}
QFuture<ErrorCode> 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<ErrorCode>::finished, &wait, &QEventLoop::quit);
watcher.setFuture(future);
wait.exec();
return watcher.result();
}

View file

@ -40,30 +40,20 @@ public slots:
void onTranslationsUpdated(); void onTranslationsUpdated();
ErrorCode updateProtocolConfig(const DockerContainer container, const ServerCredentials &credentials, QJsonObject &containerConfig,
QSharedPointer<ServerController> serverController = nullptr);
signals: signals:
void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration); void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration);
void disconnectFromVpn(); void disconnectFromVpn();
void connectionStateChanged(); void connectionStateChanged();
void connectionErrorOccurred(const QString &errorMessage);
void connectionErrorOccurred(ErrorCode errorCode); void connectionErrorOccurred(ErrorCode errorCode);
void reconnectWithUpdatedContainer(const QString &message); void reconnectWithUpdatedContainer(const QString &message);
void noInstalledContainers();
void connectButtonClicked(); void connectButtonClicked();
void preparingConfig(); void preparingConfig();
void prepareConfig();
void updateApiConfigFromGateway();
void updateApiConfigFromTelegram();
void configFromApiUpdated();
private: private:
Vpn::ConnectionState getCurrentConnectionState(); Vpn::ConnectionState getCurrentConnectionState();
bool isProtocolConfigExists(const QJsonObject &containerConfig, const DockerContainer container);
void continueConnection(); void continueConnection();

View file

@ -9,8 +9,8 @@
#include <QStandardPaths> #include <QStandardPaths>
#include "core/controllers/vpnConfigurationController.h" #include "core/controllers/vpnConfigurationController.h"
#include "core/qrCodeUtils.h"
#include "systemController.h" #include "systemController.h"
#include "qrcodegen.hpp"
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel, ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<ClientManagementModel> &clientManagementModel, const QSharedPointer<ClientManagementModel> &clientManagementModel,
@ -50,7 +50,7 @@ void ExportController::generateFullAccessConfig()
compressedConfig = qCompress(compressedConfig, 8); compressedConfig = qCompress(compressedConfig, 8);
m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)));
m_qrCodes = generateQrCodeImageSeries(compressedConfig); m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(compressedConfig);
emit exportConfigChanged(); emit exportConfigChanged();
} }
@ -92,7 +92,7 @@ void ExportController::generateConnectionConfig(const QString &clientName)
compressedConfig = qCompress(compressedConfig, 8); compressedConfig = qCompress(compressedConfig, 8);
m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)));
m_qrCodes = generateQrCodeImageSeries(compressedConfig); m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(compressedConfig);
emit exportConfigChanged(); emit exportConfigChanged();
} }
@ -149,7 +149,7 @@ void ExportController::generateOpenVpnConfig(const QString &clientName)
m_config.append(line + "\n"); m_config.append(line + "\n");
} }
m_qrCodes = generateQrCodeImageSeries(m_config.toUtf8()); m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8());
emit exportConfigChanged(); emit exportConfigChanged();
} }
@ -167,8 +167,8 @@ void ExportController::generateWireGuardConfig(const QString &clientName)
m_config.append(line + "\n"); m_config.append(line + "\n");
} }
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW); auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8());
m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1))); m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
emit exportConfigChanged(); emit exportConfigChanged();
} }
@ -187,8 +187,8 @@ void ExportController::generateAwgConfig(const QString &clientName)
m_config.append(line + "\n"); m_config.append(line + "\n");
} }
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW); auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8());
m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1))); m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
emit exportConfigChanged(); emit exportConfigChanged();
} }
@ -221,8 +221,8 @@ void ExportController::generateShadowSocksConfig()
m_nativeConfigString = "ss://" + m_nativeConfigString.toUtf8().toBase64(); m_nativeConfigString = "ss://" + m_nativeConfigString.toUtf8().toBase64();
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_nativeConfigString.toUtf8(), qrcodegen::QrCode::Ecc::LOW); auto qr = qrCodeUtils::generateQrCode(m_nativeConfigString.toUtf8());
m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1))); m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
emit exportConfigChanged(); emit exportConfigChanged();
} }
@ -312,32 +312,6 @@ void ExportController::renameClient(const int row, const QString &clientName, co
} }
} }
QList<QString> ExportController::generateQrCodeImageSeries(const QByteArray &data)
{
double k = 850;
quint8 chunksCount = std::ceil(data.size() / k);
QList<QString> 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() int ExportController::getQrCodesCount()
{ {
return m_qrCodes.size(); return m_qrCodes.size();

View file

@ -50,9 +50,6 @@ signals:
void saveFile(const QString &fileName, const QString &data); void saveFile(const QString &fileName, const QString &data);
private: private:
QList<QString> generateQrCodeImageSeries(const QByteArray &data);
QString svgToBase64(const QString &image);
int getQrCodesCount(); int getQrCodesCount();
void clearPreviousConfig(); void clearPreviousConfig();

View file

@ -7,7 +7,10 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <QUrlQuery> #include <QUrlQuery>
#include "core/api/apiDefs.h"
#include "core/api/apiUtils.h"
#include "core/errorstrings.h" #include "core/errorstrings.h"
#include "core/qrCodeUtils.h"
#include "core/serialization/serialization.h" #include "core/serialization/serialization.h"
#include "systemController.h" #include "systemController.h"
#include "utilities.h" #include "utilities.h"
@ -45,7 +48,8 @@ namespace
if (config.contains(backupPattern)) { if (config.contains(backupPattern)) {
return ConfigTypes::Backup; 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(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
&& config.contains(amneziaConfigPatternPassword))) { && config.contains(amneziaConfigPatternPassword))) {
return ConfigTypes::Amnezia; return ConfigTypes::Amnezia;
@ -149,11 +153,11 @@ bool ImportController::extractConfigFromData(QString data)
m_configType = checkConfigFormat(config); m_configType = checkConfigFormat(config);
if (m_configType == ConfigTypes::Invalid) { if (m_configType == ConfigTypes::Invalid) {
data.replace("vpn://", ""); config.replace("vpn://", "");
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); QByteArray ba = QByteArray::fromBase64(config.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
QByteArray ba_uncompressed = qUncompress(ba); QByteArray baUncompressed = qUncompress(ba);
if (!ba_uncompressed.isEmpty()) { if (!baUncompressed.isEmpty()) {
ba = ba_uncompressed; ba = baUncompressed;
} }
config = ba; config = ba;
@ -180,6 +184,13 @@ bool ImportController::extractConfigFromData(QString data)
} }
case ConfigTypes::Amnezia: { case ConfigTypes::Amnezia: {
m_config = QJsonDocument::fromJson(config.toUtf8()).object(); 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); processAmneziaConfig(m_config);
if (!m_config.empty()) { if (!m_config.empty()) {
checkForMaliciousStrings(m_config); checkForMaliciousStrings(m_config);
@ -217,6 +228,21 @@ bool ImportController::extractConfigFromQr(const QByteArray &data)
return true; 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; return false;
} }
@ -569,7 +595,7 @@ bool ImportController::parseQrCodeChunk(const QString &code)
qint16 magic; qint16 magic;
s >> magic; s >> magic;
if (magic == amnezia::qrMagicCode) { if (magic == qrCodeUtils::qrMagicCode) {
quint8 chunksCount; quint8 chunksCount;
s >> chunksCount; s >> chunksCount;
if (m_totalQrCodeChunksCount != chunksCount) { if (m_totalQrCodeChunksCount != chunksCount) {
@ -680,7 +706,8 @@ void ImportController::processAmneziaConfig(QJsonObject &config)
} }
QJsonObject jsonConfig = QJsonDocument::fromJson(protocolConfig.toUtf8()).object(); 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()); containerConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());

View file

@ -6,8 +6,8 @@
#include <QJsonObject> #include <QJsonObject>
#include <QRandomGenerator> #include <QRandomGenerator>
#include <QStandardPaths> #include <QStandardPaths>
#include <QtConcurrent>
#include "core/controllers/apiController.h"
#include "core/controllers/serverController.h" #include "core/controllers/serverController.h"
#include "core/controllers/vpnConfigurationController.h" #include "core/controllers/vpnConfigurationController.h"
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
@ -15,6 +15,7 @@
#include "ui/models/protocols/awgConfigModel.h" #include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h"
#include "utilities.h" #include "utilities.h"
#include "core/api/apiUtils.h"
namespace namespace
{ {
@ -39,14 +40,12 @@ namespace
InstallController::InstallController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel, InstallController::InstallController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<ProtocolsModel> &protocolsModel, const QSharedPointer<ProtocolsModel> &protocolsModel,
const QSharedPointer<ClientManagementModel> &clientManagementModel, const QSharedPointer<ClientManagementModel> &clientManagementModel,
const QSharedPointer<ApiServicesModel> &apiServicesModel, const std::shared_ptr<Settings> &settings, const std::shared_ptr<Settings> &settings, QObject *parent)
QObject *parent)
: QObject(parent), : QObject(parent),
m_serversModel(serversModel), m_serversModel(serversModel),
m_containersModel(containersModel), m_containersModel(containersModel),
m_protocolModel(protocolsModel), m_protocolModel(protocolsModel),
m_clientManagementModel(clientManagementModel), m_clientManagementModel(clientManagementModel),
m_apiServicesModel(apiServicesModel),
m_settings(settings) m_settings(settings)
{ {
} }
@ -773,109 +772,79 @@ void InstallController::addEmptyServer()
emit installServerFinished(tr("Server added successfully")); 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; if (apiUtils::isServerFromApi(serverConfigObject)) {
ErrorCode errorCode = apiController.getServicesList(responseBody);
if (errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(errorCode);
return false;
}
QJsonObject data = QJsonDocument::fromJson(responseBody).object();
m_apiServicesModel->updateModel(data);
return true; return true;
} }
bool InstallController::installServiceFromApi() if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
{ emit noInstalledContainers();
if (m_serversModel->isServerFromApiAlreadyExists(m_apiServicesModel->getCountryCode(), m_apiServicesModel->getSelectedServiceType(),
m_apiServicesModel->getSelectedServiceProtocol())) {
emit installationErrorOccurred(ErrorCode::ApiConfigAlreadyAdded);
return false; return false;
} }
ApiController apiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv()); DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
QJsonObject serverConfig;
ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(), if (container == DockerContainer::None) {
m_apiServicesModel->getSelectedServiceType(), emit installationErrorOccurred(ErrorCode::NoInstalledContainersError);
m_apiServicesModel->getSelectedServiceProtocol(), "", QJsonObject(), serverConfig);
if (errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(errorCode);
return false; return false;
} }
auto serviceInfo = m_apiServicesModel->getSelectedServiceInfo(); QSharedPointer<ServerController> serverController(new ServerController(m_settings));
QJsonObject apiConfig = serverConfig.value(configKey::apiConfig).toObject(); VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
apiConfig.insert(configKey::serviceInfo, serviceInfo);
apiConfig.insert(configKey::userCountryCode, m_apiServicesModel->getCountryCode());
apiConfig.insert(configKey::serviceType, m_apiServicesModel->getSelectedServiceType());
apiConfig.insert(configKey::serviceProtocol, m_apiServicesModel->getSelectedServiceProtocol());
serverConfig.insert(configKey::apiConfig, apiConfig); QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
m_serversModel->addServer(serverConfig); QFutureWatcher<ErrorCode> watcher;
emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName()));
return true;
}
bool InstallController::updateServiceFromApi(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, QFuture<ErrorCode> future = QtConcurrent::run([this, container, &credentials, &containerConfig, &serverController]() {
bool reloadServiceConfig) ErrorCode errorCode = ErrorCode::NoError;
{
ApiController apiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv());
auto serverConfig = m_serversModel->getServerConfig(serverIndex); auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) {
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
auto authData = serverConfig.value(configKey::authData).toObject(); QString protocolConfig =
containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString();
QJsonObject newServerConfig; if (protocolConfig.isEmpty()) {
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; 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; return true;
};
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);
void InstallController::updateServiceFromTelegram(const int serverIndex) errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig,
{ QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
ApiController *apiController = new ApiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv()); if (errorCode != ErrorCode::NoError) {
return errorCode;
auto serverConfig = m_serversModel->getServerConfig(serverIndex); }
}
apiController->updateServerConfigFromApi(m_settings->getInstallationUuid(true), serverIndex, serverConfig); return errorCode;
connect(apiController, &ApiController::finished, this, [this, apiController](const QJsonObject &config, const int serverIndex) {
m_serversModel->editServer(config, serverIndex);
emit updateServerFromApiFinished();
apiController->deleteLater();
}); });
connect(apiController, &ApiController::errorOccurred, this, [this, apiController](ErrorCode errorCode) {
QEventLoop wait;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
watcher.setFuture(future);
wait.exec();
ErrorCode errorCode = watcher.result();
if (errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(errorCode); emit installationErrorOccurred(errorCode);
apiController->deleteLater(); return false;
}); }
return true;
} }
bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig, bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig,

View file

@ -10,7 +10,6 @@
#include "ui/models/containers_model.h" #include "ui/models/containers_model.h"
#include "ui/models/protocols_model.h" #include "ui/models/protocols_model.h"
#include "ui/models/servers_model.h" #include "ui/models/servers_model.h"
#include "ui/models/apiServicesModel.h"
class InstallController : public QObject class InstallController : public QObject
{ {
@ -19,7 +18,6 @@ public:
explicit InstallController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel, explicit InstallController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<ProtocolsModel> &protocolsModel, const QSharedPointer<ProtocolsModel> &protocolsModel,
const QSharedPointer<ClientManagementModel> &clientManagementModel, const QSharedPointer<ClientManagementModel> &clientManagementModel,
const QSharedPointer<ApiServicesModel> &apiServicesModel,
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr); const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
~InstallController(); ~InstallController();
@ -52,21 +50,13 @@ public slots:
void addEmptyServer(); void addEmptyServer();
bool fillAvailableServices(); bool isConfigValid();
bool installServiceFromApi();
bool updateServiceFromApi(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, bool reloadServiceConfig = false);
void updateServiceFromTelegram(const int serverIndex);
signals: signals:
void installContainerFinished(const QString &finishMessage, bool isServiceInstall); void installContainerFinished(const QString &finishMessage, bool isServiceInstall);
void installServerFinished(const QString &finishMessage); void installServerFinished(const QString &finishMessage);
void installServerFromApiFinished(const QString &message);
void updateContainerFinished(const QString &message); void updateContainerFinished(const QString &message);
void updateServerFromApiFinished();
void changeApiCountryFinished(const QString &message);
void reloadServerFromApiFinished(const QString &message);
void scanServerFinished(bool isInstalledContainerFound); void scanServerFinished(bool isInstalledContainerFound);
@ -91,6 +81,8 @@ signals:
void cachedProfileCleared(const QString &message); void cachedProfileCleared(const QString &message);
void apiConfigRemoved(const QString &message); void apiConfigRemoved(const QString &message);
void noInstalledContainers();
private: private:
void installServer(const DockerContainer container, const QMap<DockerContainer, QJsonObject> &installedContainers, void installServer(const DockerContainer container, const QMap<DockerContainer, QJsonObject> &installedContainers,
const ServerCredentials &serverCredentials, const QSharedPointer<ServerController> &serverController, const ServerCredentials &serverCredentials, const QSharedPointer<ServerController> &serverController,
@ -108,7 +100,6 @@ private:
QSharedPointer<ContainersModel> m_containersModel; QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ProtocolsModel> m_protocolModel; QSharedPointer<ProtocolsModel> m_protocolModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel; QSharedPointer<ClientManagementModel> m_clientManagementModel;
QSharedPointer<ApiServicesModel> m_apiServicesModel;
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;

View file

@ -31,6 +31,12 @@ namespace PageLoader
PageSettingsLogging, PageSettingsLogging,
PageSettingsSplitTunneling, PageSettingsSplitTunneling,
PageSettingsAppSplitTunneling, PageSettingsAppSplitTunneling,
PageSettingsApiServerInfo,
PageSettingsApiAvailableCountries,
PageSettingsApiSupport,
PageSettingsApiInstructions,
PageSettingsApiNativeConfigs,
PageSettingsApiDevices,
PageServiceSftpSettings, PageServiceSftpSettings,
PageServiceTorWebsiteSettings, PageServiceTorWebsiteSettings,

View file

@ -1,84 +0,0 @@
#include "apiCountryModel.h"
#include <QJsonObject>
#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<int>(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 &currentCountryCode)
{
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<int, QByteArray> ApiCountryModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[CountryNameRole] = "countryName";
roles[CountryCodeRole] = "countryCode";
roles[CountryImageCodeRole] = "countryImageCode";
return roles;
}

View file

@ -1,43 +0,0 @@
#ifndef APICOUNTRYMODEL_H
#define APICOUNTRYMODEL_H
#include <QAbstractListModel>
#include <QJsonArray>
class ApiCountryModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
CountryNameRole = Qt::UserRole + 1,
CountryCodeRole,
CountryImageCodeRole
};
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 &data, const QString &currentCountryCode);
int getCurrentIndex();
void setCurrentIndex(const int i);
signals:
void currentIndexChanged(const int index);
protected:
QHash<int, QByteArray> roleNames() const override;
private:
QJsonArray m_countries;
int m_currentIndex;
};
#endif // APICOUNTRYMODEL_H

View file

@ -1,272 +0,0 @@
#include "apiServicesModel.h"
#include <QJsonObject>
#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<int>(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("Classic VPN for comfortable work, downloading large files and watching videos. "
"Works for any sites. Speed up to %1 MBit/s")
.arg(speed);
} else if (serviceType == serviceType::amneziaFree) {
QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. ");
if (!isServiceAvailable) {
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, "
"return to the previous screen, and try again.</a>");
}
return description;
}
}
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.");
} else {
return tr("Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorship");
}
}
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, "
"<a href=\"%1/free\" style=\"color: #FBB26A;\">more details on the website.</a>");
}
}
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();
#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));
}
}
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<int, QByteArray> ApiServicesModel::roleNames() const
{
QHash<int, QByteArray> 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 = serviceInfo.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;
}

View file

@ -1,91 +0,0 @@
#ifndef APISERVICESMODEL_H
#define APISERVICESMODEL_H
#include <QAbstractListModel>
#include <QJsonArray>
#include <QJsonObject>
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<int, QByteArray> 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<ApiServicesData> m_services;
int m_selectedServiceIndex;
};
#endif // APISERVICESMODEL_H

View file

@ -108,7 +108,7 @@ QString LanguageModel::getCurrentSiteUrl()
{ {
auto language = static_cast<LanguageSettings::AvailableLanguageEnum>(getCurrentLanguageIndex()); auto language = static_cast<LanguageSettings::AvailableLanguageEnum>(getCurrentLanguageIndex());
switch (language) { 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"; default: return "https://amnezia.org";
} }
} }

View file

@ -1,7 +1,7 @@
#include "servers_model.h" #include "servers_model.h"
#include "core/api/apiDefs.h"
#include "core/controllers/serverController.h" #include "core/controllers/serverController.h"
#include "core/enums/apiEnums.h"
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
@ -132,10 +132,10 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
return serverHasInstalledContainers(index.row()); return serverHasInstalledContainers(index.row());
} }
case IsServerFromTelegramApiRole: { case IsServerFromTelegramApiRole: {
return server.value(config_key::configVersion).toInt() == ApiConfigSources::Telegram; return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::Telegram;
} }
case IsServerFromGatewayApiRole: { case IsServerFromGatewayApiRole: {
return server.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway; return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway;
} }
case ApiConfigRole: { case ApiConfigRole: {
return apiConfig; return apiConfig;
@ -261,7 +261,7 @@ void ServersModel::setProcessedServerIndex(const int index)
updateContainersModel(); updateContainersModel();
if (data(index, IsServerFromGatewayApiRole).toBool()) { if (data(index, IsServerFromGatewayApiRole).toBool()) {
if (data(index, IsCountrySelectionAvailableRole).toBool()) { if (data(index, IsCountrySelectionAvailableRole).toBool()) {
emit updateApiLanguageModel(); emit updateApiCountryModel();
} }
emit updateApiServicesModel(); emit updateApiServicesModel();
} }

View file

@ -140,7 +140,7 @@ signals:
void defaultServerContainersUpdated(const QJsonArray &containers); void defaultServerContainersUpdated(const QJsonArray &containers);
void defaultServerDefaultContainerChanged(const int containerIndex); void defaultServerDefaultContainerChanged(const int containerIndex);
void updateApiLanguageModel(); void updateApiCountryModel();
void updateApiServicesModel(); void updateApiServicesModel();
private: private:

View file

@ -135,7 +135,7 @@ DrawerType2 {
backgroundColor: AmneziaStyle.color.slateGray backgroundColor: AmneziaStyle.color.slateGray
textFieldPlaceholderText: qsTr("application name") textField.placeholderText: qsTr("application name")
} }
BasicButtonType { BasicButtonType {

View file

@ -110,7 +110,24 @@ ListView {
onClicked: function() { onClicked: function() {
ServersModel.processedIndex = index ServersModel.processedIndex = index
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) PageController.goToPage(PageEnum.PageSettingsServerInfo)
}
drawer.closeTriggered() drawer.closeTriggered()
} }
} }

View file

@ -22,7 +22,9 @@ DrawerType2 {
property string headerText property string headerText
property string configContentHeaderText 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 configExtension: ".vpn"
property string configCaption: qsTr("Save AmneziaVPN config") property string configCaption: qsTr("Save AmneziaVPN config")
@ -71,8 +73,6 @@ DrawerType2 {
header: ColumnLayout { header: ColumnLayout {
width: listView.width width: listView.width
visible: root.contentVisible
BasicButtonType { BasicButtonType {
id: shareButton id: shareButton
Layout.fillWidth: true Layout.fillWidth: true
@ -80,7 +80,7 @@ DrawerType2 {
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
text: qsTr("Share") text: root.shareButtonText
leftImageSource: "qrc:/images/controls/share-2.svg" leftImageSource: "qrc:/images/controls/share-2.svg"
clickedFunc: function() { clickedFunc: function() {
@ -116,7 +116,7 @@ DrawerType2 {
textColor: AmneziaStyle.color.paleGray textColor: AmneziaStyle.color.paleGray
borderWidth: 1 borderWidth: 1
text: qsTr("Copy") text: root.copyButtonText
leftImageSource: "qrc:/images/controls/copy.svg" leftImageSource: "qrc:/images/controls/copy.svg"
Keys.onReturnPressed: { copyConfigTextButton.clicked() } Keys.onReturnPressed: { copyConfigTextButton.clicked() }
@ -153,6 +153,8 @@ DrawerType2 {
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
visible: root.isSelfHostedConfig
defaultColor: AmneziaStyle.color.transparent defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite pressedColor: AmneziaStyle.color.sheerWhite
@ -283,6 +285,8 @@ DrawerType2 {
delegate: ColumnLayout { delegate: ColumnLayout {
width: listView.width width: listView.width
property bool isQrCodeVisible: root.isSelfHostedConfig ? ExportController.qrCodesCount > 0 : ApiConfigsController.qrCodesCount > 0
Rectangle { Rectangle {
id: qrCodeContainer id: qrCodeContainer
@ -292,7 +296,7 @@ DrawerType2 {
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
visible: ExportController.qrCodesCount > 0 visible: isQrCodeVisible
color: "white" color: "white"
@ -300,7 +304,8 @@ DrawerType2 {
anchors.fill: parent anchors.fill: parent
smooth: false smooth: false
source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : "" source: root.isSelfHostedConfig ? (isQrCodeVisible ? ExportController.qrCodes[0] : "") :
(isQrCodeVisible ? ApiConfigsController.qrCodes[0] : "")
property bool isFocusable: true property bool isFocusable: true
@ -331,15 +336,17 @@ DrawerType2 {
Timer { Timer {
property int index: 0 property int index: 0
interval: 1000 interval: 1000
running: ExportController.qrCodesCount > 0 running: isQrCodeVisible
repeat: true repeat: true
onTriggered: { onTriggered: {
if (ExportController.qrCodesCount > 0) { if (isQrCodeVisible) {
index++ index++
if (index >= ExportController.qrCodesCount) { let qrCodesCount = root.isSelfHostedConfig ? ExportController.qrCodesCount : ApiConfigsController.qrCodesCount
if (index >= qrCodesCount) {
index = 0 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.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
visible: ExportController.qrCodesCount > 0 visible: isQrCodeVisible
horizontalAlignment: Text.AlignHCenter 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\"") 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\"")

View file

@ -22,11 +22,9 @@ Item {
property var clickedFunc property var clickedFunc
property alias textField: textField property alias textField: textField
property alias textFieldText: textField.text
property string textFieldTextColor: AmneziaStyle.color.paleGray property string textFieldTextColor: AmneziaStyle.color.paleGray
property string textFieldTextDisabledColor: AmneziaStyle.color.mutedGray property string textFieldTextDisabledColor: AmneziaStyle.color.mutedGray
property string textFieldPlaceholderText
property bool textFieldEditable: true property bool textFieldEditable: true
property string borderColor: AmneziaStyle.color.slateGray property string borderColor: AmneziaStyle.color.slateGray
@ -101,7 +99,6 @@ Item {
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText
placeholderText: root.textFieldPlaceholderText
placeholderTextColor: AmneziaStyle.color.charcoalGray placeholderTextColor: AmneziaStyle.color.charcoalGray
selectionColor: AmneziaStyle.color.richBrown selectionColor: AmneziaStyle.color.richBrown
@ -129,8 +126,8 @@ Item {
} }
onActiveFocusChanged: { onActiveFocusChanged: {
if (checkEmptyText && textFieldText === "") { if (root.checkEmptyText && text === "") {
errorText = qsTr("The field can't be empty") root.errorText = qsTr("The field can't be empty")
} }
} }

View file

@ -27,5 +27,6 @@ QtObject {
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8) 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 cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
readonly property color pearlGray: '#EAEAEC' readonly property color pearlGray: '#EAEAEC'
readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
} }
} }

View file

@ -66,18 +66,18 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: qsTr("Gateway endpoint") 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() { clickedFunc: function() {
SettingsController.resetGatewayEndpoint() SettingsController.resetGatewayEndpoint()
} }
textField.onEditingFinished: { textField.onEditingFinished: {
textFieldText = textField.text.replace(/^\s+|\s+$/g, '') textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (textFieldText !== SettingsController.gatewayEndpoint) { if (textField.text !== SettingsController.gatewayEndpoint) {
SettingsController.gatewayEndpoint = textFieldText SettingsController.gatewayEndpoint = textField.text
} }
} }
} }

View file

@ -297,11 +297,27 @@ PageType {
onClicked: { onClicked: {
ServersModel.processedIndex = ServersModel.defaultIndex ServersModel.processedIndex = ServersModel.defaultIndex
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) PageController.goToPage(PageEnum.PageSettingsServerInfo)
} }
} }
} }
} }
}
ColumnLayout { ColumnLayout {
id: serversMenuHeader id: serversMenuHeader

View file

@ -103,12 +103,12 @@ PageType {
Layout.topMargin: 40 Layout.topMargin: 40
headerText: qsTr("MTU") headerText: qsTr("MTU")
textFieldText: clientMtu textField.text: clientMtu
textField.validator: IntValidator { bottom: 576; top: 65535 } textField.validator: IntValidator { bottom: 576; top: 65535 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== clientMtu) { if (textField.text !== clientMtu) {
clientMtu = textFieldText clientMtu = textField.text
} }
} }
checkEmptyText: true checkEmptyText: true
@ -121,12 +121,12 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: "Jc - Junk packet count" headerText: "Jc - Junk packet count"
textFieldText: clientJunkPacketCount textField.text: clientJunkPacketCount
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== clientJunkPacketCount) { if (textField.text !== clientJunkPacketCount) {
clientJunkPacketCount = textFieldText clientJunkPacketCount = textField.text
} }
} }
@ -141,12 +141,12 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: "Jmin - Junk packet minimum size" headerText: "Jmin - Junk packet minimum size"
textFieldText: clientJunkPacketMinSize textField.text: clientJunkPacketMinSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== clientJunkPacketMinSize) { if (textField.text !== clientJunkPacketMinSize) {
clientJunkPacketMinSize = textFieldText clientJunkPacketMinSize = textField.text
} }
} }
@ -161,12 +161,12 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: "Jmax - Junk packet maximum size" headerText: "Jmax - Junk packet maximum size"
textFieldText: clientJunkPacketMaxSize textField.text: clientJunkPacketMaxSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== clientJunkPacketMaxSize) { if (textField.text !== clientJunkPacketMaxSize) {
clientJunkPacketMaxSize = textFieldText clientJunkPacketMaxSize = textField.text
} }
} }
@ -189,7 +189,7 @@ PageType {
enabled: false enabled: false
headerText: qsTr("Port") headerText: qsTr("Port")
textFieldText: port textField.text: port
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -200,7 +200,7 @@ PageType {
enabled: false enabled: false
headerText: "S1 - Init packet junk size" headerText: "S1 - Init packet junk size"
textFieldText: serverInitPacketJunkSize textField.text: serverInitPacketJunkSize
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -211,7 +211,7 @@ PageType {
enabled: false enabled: false
headerText: "S2 - Response packet junk size" headerText: "S2 - Response packet junk size"
textFieldText: serverResponsePacketJunkSize textField.text: serverResponsePacketJunkSize
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -222,7 +222,7 @@ PageType {
enabled: false enabled: false
headerText: "H1 - Init packet magic header" headerText: "H1 - Init packet magic header"
textFieldText: serverInitPacketMagicHeader textField.text: serverInitPacketMagicHeader
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -233,7 +233,7 @@ PageType {
enabled: false enabled: false
headerText: "H2 - Response packet magic header" headerText: "H2 - Response packet magic header"
textFieldText: serverResponsePacketMagicHeader textField.text: serverResponsePacketMagicHeader
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -244,7 +244,7 @@ PageType {
enabled: false enabled: false
headerText: "H3 - Underload packet magic header" headerText: "H3 - Underload packet magic header"
textFieldText: serverUnderloadPacketMagicHeader textField.text: serverUnderloadPacketMagicHeader
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -255,7 +255,7 @@ PageType {
enabled: false enabled: false
headerText: "H4 - Transport packet magic header" headerText: "H4 - Transport packet magic header"
textFieldText: serverTransportPacketMagicHeader textField.text: serverTransportPacketMagicHeader
} }
} }
} }

View file

@ -106,11 +106,11 @@ PageType {
enabled: delegateItem.isEnabled enabled: delegateItem.isEnabled
headerText: qsTr("VPN address subnet") headerText: qsTr("VPN address subnet")
textFieldText: subnetAddress textField.text: subnetAddress
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== subnetAddress) { if (textField.text !== subnetAddress) {
subnetAddress = textFieldText subnetAddress = textField.text
} }
} }
@ -125,13 +125,13 @@ PageType {
enabled: delegateItem.isEnabled enabled: delegateItem.isEnabled
headerText: qsTr("Port") headerText: qsTr("Port")
textFieldText: port textField.text: port
textField.maximumLength: 5 textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 } textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== port) { if (textField.text !== port) {
port = textFieldText port = textField.text
} }
} }
@ -144,16 +144,16 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: qsTr("Jc - Junk packet count") headerText: qsTr("Jc - Junk packet count")
textFieldText: serverJunkPacketCount textField.text: serverJunkPacketCount
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText === "") { if (textField.text === "") {
textFieldText = "0" textField.text = "0"
} }
if (textFieldText !== serverJunkPacketCount) { if (textField.text !== serverJunkPacketCount) {
serverJunkPacketCount = textFieldText serverJunkPacketCount = textField.text
} }
} }
@ -166,12 +166,12 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: qsTr("Jmin - Junk packet minimum size") headerText: qsTr("Jmin - Junk packet minimum size")
textFieldText: serverJunkPacketMinSize textField.text: serverJunkPacketMinSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverJunkPacketMinSize) { if (textField.text !== serverJunkPacketMinSize) {
serverJunkPacketMinSize = textFieldText serverJunkPacketMinSize = textField.text
} }
} }
@ -184,12 +184,12 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: qsTr("Jmax - Junk packet maximum size") headerText: qsTr("Jmax - Junk packet maximum size")
textFieldText: serverJunkPacketMaxSize textField.text: serverJunkPacketMaxSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverJunkPacketMaxSize) { if (textField.text !== serverJunkPacketMaxSize) {
serverJunkPacketMaxSize = textFieldText serverJunkPacketMaxSize = textField.text
} }
} }
@ -202,12 +202,12 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: qsTr("S1 - Init packet junk size") headerText: qsTr("S1 - Init packet junk size")
textFieldText: serverInitPacketJunkSize textField.text: serverInitPacketJunkSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverInitPacketJunkSize) { if (textField.text !== serverInitPacketJunkSize) {
serverInitPacketJunkSize = textFieldText serverInitPacketJunkSize = textField.text
} }
} }
@ -226,12 +226,12 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: qsTr("S2 - Response packet junk size") headerText: qsTr("S2 - Response packet junk size")
textFieldText: serverResponsePacketJunkSize textField.text: serverResponsePacketJunkSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverResponsePacketJunkSize) { if (textField.text !== serverResponsePacketJunkSize) {
serverResponsePacketJunkSize = textFieldText serverResponsePacketJunkSize = textField.text
} }
} }
@ -250,12 +250,12 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: qsTr("H1 - Init packet magic header") headerText: qsTr("H1 - Init packet magic header")
textFieldText: serverInitPacketMagicHeader textField.text: serverInitPacketMagicHeader
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverInitPacketMagicHeader) { if (textField.text !== serverInitPacketMagicHeader) {
serverInitPacketMagicHeader = textFieldText serverInitPacketMagicHeader = textField.text
} }
} }
@ -268,12 +268,12 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: qsTr("H2 - Response packet magic header") headerText: qsTr("H2 - Response packet magic header")
textFieldText: serverResponsePacketMagicHeader textField.text: serverResponsePacketMagicHeader
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverResponsePacketMagicHeader) { if (textField.text !== serverResponsePacketMagicHeader) {
serverResponsePacketMagicHeader = textFieldText serverResponsePacketMagicHeader = textField.text
} }
} }
@ -286,12 +286,12 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: qsTr("H4 - Transport packet magic header") headerText: qsTr("H4 - Transport packet magic header")
textFieldText: serverTransportPacketMagicHeader textField.text: serverTransportPacketMagicHeader
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverTransportPacketMagicHeader) { if (textField.text !== serverTransportPacketMagicHeader) {
serverTransportPacketMagicHeader = textFieldText serverTransportPacketMagicHeader = textField.text
} }
} }
@ -304,12 +304,12 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: qsTr("H3 - Underload packet magic header") headerText: qsTr("H3 - Underload packet magic header")
textFieldText: serverUnderloadPacketMagicHeader textField.text: serverUnderloadPacketMagicHeader
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverUnderloadPacketMagicHeader) { if (textField.text !== serverUnderloadPacketMagicHeader) {
serverUnderloadPacketMagicHeader = textFieldText serverUnderloadPacketMagicHeader = textField.text
} }
} }

View file

@ -89,18 +89,18 @@ PageType {
Layout.topMargin: 32 Layout.topMargin: 32
headerText: qsTr("Disguised as traffic from") headerText: qsTr("Disguised as traffic from")
textFieldText: site textField.text: site
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== site) { if (textField.text !== site) {
var tmpText = textFieldText var tmpText = textField.text
tmpText = tmpText.toLocaleLowerCase() tmpText = tmpText.toLocaleLowerCase()
var indexHttps = tmpText.indexOf("https://") var indexHttps = tmpText.indexOf("https://")
if (indexHttps === 0) { if (indexHttps === 0) {
tmpText = textFieldText.substring(8) tmpText = textField.text.substring(8)
} else { } else {
site = textFieldText site = textField.text
} }
} }
} }
@ -113,13 +113,13 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
headerText: qsTr("Port") headerText: qsTr("Port")
textFieldText: port textField.text: port
textField.maximumLength: 5 textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 } textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== port) { if (textField.text !== port) {
port = textFieldText port = textField.text
} }
} }
} }

View file

@ -88,13 +88,13 @@ PageType {
Layout.topMargin: 32 Layout.topMargin: 32
headerText: qsTr("VPN address subnet") headerText: qsTr("VPN address subnet")
textFieldText: subnetAddress textField.text: subnetAddress
parentFlickable: fl parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== subnetAddress) { if (textField.text !== subnetAddress) {
subnetAddress = textFieldText subnetAddress = textField.text
} }
} }
} }
@ -137,13 +137,13 @@ PageType {
enabled: isPortEditable enabled: isPortEditable
headerText: qsTr("Port") headerText: qsTr("Port")
textFieldText: port textField.text: port
textField.maximumLength: 5 textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 } textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== port) { if (textField.text !== port) {
port = textFieldText port = textField.text
} }
} }
} }

View file

@ -93,13 +93,13 @@ PageType {
enabled: isPortEditable enabled: isPortEditable
headerText: qsTr("Port") headerText: qsTr("Port")
textFieldText: port textField.text: port
textField.maximumLength: 5 textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 } textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== port) { if (textField.text !== port) {
port = textFieldText port = textField.text
} }
} }
} }

View file

@ -97,12 +97,12 @@ PageType {
Layout.topMargin: 40 Layout.topMargin: 40
headerText: qsTr("MTU") headerText: qsTr("MTU")
textFieldText: clientMtu textField.text: clientMtu
textField.validator: IntValidator { bottom: 576; top: 65535 } textField.validator: IntValidator { bottom: 576; top: 65535 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== clientMtu) { if (textField.text !== clientMtu) {
clientMtu = textFieldText clientMtu = textField.text
} }
} }
checkEmptyText: true checkEmptyText: true
@ -124,7 +124,7 @@ PageType {
enabled: false enabled: false
headerText: qsTr("Port") headerText: qsTr("Port")
textFieldText: port textField.text: port
} }
} }
} }

View file

@ -90,11 +90,11 @@ PageType {
enabled: delegateItem.isEnabled enabled: delegateItem.isEnabled
headerText: qsTr("VPN address subnet") headerText: qsTr("VPN address subnet")
textFieldText: subnetAddress textField.text: subnetAddress
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== subnetAddress) { if (textField.text !== subnetAddress) {
subnetAddress = textFieldText subnetAddress = textField.text
} }
} }
@ -109,13 +109,13 @@ PageType {
enabled: delegateItem.isEnabled enabled: delegateItem.isEnabled
headerText: qsTr("Port") headerText: qsTr("Port")
textFieldText: port textField.text: port
textField.maximumLength: 5 textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 } textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== port) { if (textField.text !== port) {
port = textFieldText port = textField.text
} }
} }

View file

@ -86,18 +86,18 @@ PageType {
Layout.topMargin: 32 Layout.topMargin: 32
headerText: qsTr("Disguised as traffic from") headerText: qsTr("Disguised as traffic from")
textFieldText: site textField.text: site
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== site) { if (textField.text !== site) {
var tmpText = textFieldText var tmpText = textField.text
tmpText = tmpText.toLocaleLowerCase() tmpText = tmpText.toLocaleLowerCase()
var indexHttps = tmpText.indexOf("https://") var indexHttps = tmpText.indexOf("https://")
if (indexHttps === 0) { if (indexHttps === 0) {
tmpText = textFieldText.substring(8) tmpText = textField.text.substring(8)
} else { } else {
site = textFieldText site = textField.text
} }
} }
} }

View file

@ -211,9 +211,9 @@ PageType {
port = tempPort port = tempPort
username = tempUsername username = tempUsername
password = tempPassword password = tempPassword
portTextField.textFieldText = port portTextField.textField.text = port
usernameTextField.textFieldText = username usernameTextField.textField.text = username
passwordTextField.textFieldText = password passwordTextField.textField.text = password
} }
} }
@ -231,14 +231,14 @@ PageType {
parentFlickable: fl parentFlickable: fl
headerText: qsTr("Port") headerText: qsTr("Port")
textFieldText: port textField.text: port
textField.maximumLength: 5 textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 } textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: { textField.onEditingFinished: {
textFieldText = textField.text.replace(/^\s+|\s+$/g, '') textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (textFieldText !== port) { if (textField.text !== port) {
port = textFieldText port = textField.text
} }
} }
} }
@ -251,14 +251,14 @@ PageType {
parentFlickable: fl parentFlickable: fl
headerText: qsTr("Username") headerText: qsTr("Username")
textFieldPlaceholderText: "username" textField.placeholderText: "username"
textFieldText: username textField.text: username
textField.maximumLength: 32 textField.maximumLength: 32
textField.onEditingFinished: { textField.onEditingFinished: {
textFieldText = textField.text.replace(/^\s+|\s+$/g, '') textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (textFieldText !== username) { if (textField.text !== username) {
username = textFieldText username = textField.text
} }
} }
} }
@ -273,12 +273,12 @@ PageType {
parentFlickable: fl parentFlickable: fl
headerText: qsTr("Password") headerText: qsTr("Password")
textFieldPlaceholderText: "password" textField.placeholderText: "password"
textFieldText: password textField.text: password
textField.maximumLength: 32 textField.maximumLength: 32
textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal 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() { clickedFunc: function() {
@ -286,9 +286,9 @@ PageType {
} }
textField.onFocusChanged: { textField.onFocusChanged: {
textFieldText = textField.text.replace(/^\s+|\s+$/g, '') textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (textFieldText !== password) { if (textField.text !== password) {
password = textFieldText password = textField.text
} }
} }
} }
@ -309,19 +309,19 @@ PageType {
portTextField.errorText = qsTr("The port must be in the range of 1 to 65535") portTextField.errorText = qsTr("The port must be in the range of 1 to 65535")
return return
} }
if (usernameTextField.textFieldText && passwordTextField.textFieldText === "") { if (usernameTextField.textField.text && passwordTextField.textField.text === "") {
passwordTextField.errorText = qsTr("Password cannot be empty") passwordTextField.errorText = qsTr("Password cannot be empty")
return return
} else if (usernameTextField.textFieldText === "" && passwordTextField.textFieldText) { } else if (usernameTextField.textField.text === "" && passwordTextField.textField.text) {
usernameTextField.errorText = qsTr("Username cannot be empty") usernameTextField.errorText = qsTr("Username cannot be empty")
return return
} }
PageController.goToPage(PageEnum.PageSetupWizardInstalling) PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(Socks5ProxyConfigModel.getConfig()) InstallController.updateContainer(Socks5ProxyConfigModel.getConfig())
tempPort = portTextField.textFieldText tempPort = portTextField.textField.text
tempUsername = usernameTextField.textFieldText tempUsername = usernameTextField.textField.text
tempPassword = passwordTextField.textFieldText tempPassword = passwordTextField.textField.text
changeSettingsDrawer.closeTriggered() changeSettingsDrawer.closeTriggered()
} }
} }

View file

@ -87,7 +87,6 @@ PageType {
LabelWithButtonType { LabelWithButtonType {
id: backup id: backup
visible: !SettingsController.isOnTv()
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Backup") text: qsTr("Backup")
@ -99,9 +98,7 @@ PageType {
} }
} }
DividerType { DividerType {}
visible: !SettingsController.isOnTv()
}
LabelWithButtonType { LabelWithButtonType {
id: about id: about

View file

@ -47,8 +47,7 @@ PageType {
readonly property string description: qsTr("For reviews and bug reports") readonly property string description: qsTr("For reviews and bug reports")
readonly property string imageSource: "qrc:/images/controls/mail.svg" readonly property string imageSource: "qrc:/images/controls/mail.svg"
readonly property var handler: function() { readonly property var handler: function() {
GC.copyToClipBoard(title) Qt.openUrlExternally(qsTr("mailto:support@amnezia.org"))
PageController.showNotificationMessage(qsTr("Copied"))
} }
} }

View file

@ -1,105 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
ListView {
id: menuContent
property bool isFocusable: true
width: parent.width
height: parent.height
clip: true
interactive: true
model: ApiCountryModel
ButtonGroup {
id: containersRadioButtonGroup
}
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 (!InstallController.updateServiceFromApi(ServersModel.defaultIndex, countryCode, countryName)) {
ApiCountryModel.currentIndex = prevIndex
}
}
}
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
}
}
}
}

View file

@ -3,6 +3,8 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Dialogs import QtQuick.Dialogs
import SortFilterProxyModel 0.2
import PageEnum 1.0 import PageEnum 1.0
import Style 1.0 import Style 1.0
@ -15,107 +17,264 @@ import "../Components"
PageType { PageType {
id: root id: root
FlickableType { property list<QtObject> labelsModel: [
id: fl statusObject,
anchors.top: parent.top endDateObject,
anchors.bottom: parent.bottom deviceCountObject
contentHeight: content.height ]
ColumnLayout { QtObject {
id: content id: statusObject
anchors.top: parent.top readonly property string title: qsTr("Subscription status")
anchors.left: parent.left readonly property string contentKey: "subscriptionStatus"
anchors.right: parent.right 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 spacing: 0
LabelWithImageType { Connections {
Layout.fillWidth: true target: ApiAccountInfoModel
Layout.margins: 16
imageSource: "qrc:/images/controls/map-pin.svg" function onModelReset() {
leftText: qsTr("For the region") delegateItem.rightText = ApiAccountInfoModel.data(contentKey)
rightText: ApiServicesModel.getSelectedServiceData("region") }
} }
LabelWithImageType { LabelWithImageType {
Layout.fillWidth: true id: delegateItem
Layout.margins: 16
imageSource: "qrc:/images/controls/tag.svg"
leftText: qsTr("Price")
rightText: ApiServicesModel.getSelectedServiceData("price")
}
LabelWithImageType {
property bool showSubscriptionEndDate: ServersModel.getProcessedServerData("isCountrySelectionAvailable")
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 16 Layout.margins: 16
imageSource: "qrc:/images/controls/history.svg" imageSource: objectImageSource
leftText: showSubscriptionEndDate ? qsTr("Valid until") : qsTr("Work period") leftText: title
rightText: showSubscriptionEndDate ? ApiServicesModel.getSelectedServiceData("endDate") rightText: ApiAccountInfoModel.data(contentKey)
: ApiServicesModel.getSelectedServiceData("workPeriod")
visible: rightText !== "" visible: rightText !== ""
} }
LabelWithImageType {
Layout.fillWidth: true
Layout.margins: 16
imageSource: "qrc:/images/controls/gauge.svg"
leftText: qsTr("Speed")
rightText: ApiServicesModel.getSelectedServiceData("speed")
} }
ParagraphTextType { footer: ColumnLayout {
Layout.fillWidth: true id: footer
width: listView.width
spacing: 0
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
WarningType {
id: warning
Layout.topMargin: 32
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.fillWidth: true
onLinkActivated: function(link) { backGroundColor: AmneziaStyle.color.translucentRichBrown
Qt.openUrlExternally(link)
}
textFormat: Text.RichText
text: {
var text = ApiServicesModel.getSelectedServiceData("features")
if (text === undefined) {
return ""
}
return text.replace("%1", LanguageModel.getCurrentSiteUrl())
}
MouseArea { textString: qsTr("Configurations have been updated for some countries. Download and install the updated configuration files")
anchors.fill: parent
acceptedButtons: Qt.NoButton iconPath: "qrc:/images/controls/alert-circle.svg"
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} visible: ApiAccountInfoModel.data("hasExpiredWorker")
} }
LabelWithButtonType { LabelWithButtonType {
id: supportUuid id: vpnKey
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: warning.visible ? 16 : 32
text: qsTr("Support tag") visible: false //footer.isVisibleForAmneziaFree
descriptionText: SettingsController.getInstallationUuid()
descriptionOnTop: true text: qsTr("Subscription key")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
rightImageSource: "qrc:/images/controls/copy.svg"
rightImageColor: AmneziaStyle.color.paleGray
clickedFunction: function() { clickedFunction: function() {
GC.copyToClipBoard(descriptionText) shareConnectionDrawer.headerText = qsTr("Amnezia Premium subscription key")
PageController.showNotificationMessage(qsTr("Copied"))
if (!GC.isMobile()) { shareConnectionDrawer.openTriggered()
this.rightButton.forceActiveFocus() 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 { BasicButtonType {
id: resetButton id: resetButton
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -141,20 +300,57 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot reload API config during active connection")) PageController.showNotificationMessage(qsTr("Cannot reload API config during active connection"))
} else { } else {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
InstallController.updateServiceFromApi(ServersModel.processedIndex, "", "", true) ApiConfigsController.updateServiceFromGateway(ServersModel.processedIndex, "", "", true)
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
} }
} }
var noButtonFunction = function() { var noButtonFunction = function() {
if (!GC.isMobile()) {
removeButton.forceActiveFocus()
}
} }
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) 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 { BasicButtonType {
id: removeButton id: removeButton
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -179,14 +375,13 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot remove server during active connection")) PageController.showNotificationMessage(qsTr("Cannot remove server during active connection"))
} else { } else {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
if (ApiConfigsController.deactivateDevice()) {
InstallController.removeProcessedServer() InstallController.removeProcessedServer()
}
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
} }
} }
var noButtonFunction = function() { var noButtonFunction = function() {
if (!GC.isMobile()) {
removeButton.forceActiveFocus()
}
} }
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
@ -194,4 +389,10 @@ PageType {
} }
} }
} }
ShareConnectionDrawer {
id: shareConnectionDrawer
anchors.fill: parent
}
} }

View file

@ -252,7 +252,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
textFieldPlaceholderText: qsTr("application name") textField.placeholderText: qsTr("application name")
buttonImageSource: "qrc:/images/controls/plus.svg" buttonImageSource: "qrc:/images/controls/plus.svg"
rightButtonClickedOnEnter: true rightButtonClickedOnEnter: true

View file

@ -67,7 +67,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
headerText: qsTr("Primary DNS") headerText: qsTr("Primary DNS")
textFieldText: SettingsController.primaryDns textField.text: SettingsController.primaryDns
textField.validator: RegularExpressionValidator { textField.validator: RegularExpressionValidator {
regularExpression: InstallController.ipAddressRegExp() regularExpression: InstallController.ipAddressRegExp()
} }
@ -79,7 +79,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
headerText: qsTr("Secondary DNS") headerText: qsTr("Secondary DNS")
textFieldText: SettingsController.secondaryDns textField.text: SettingsController.secondaryDns
textField.validator: RegularExpressionValidator { textField.validator: RegularExpressionValidator {
regularExpression: InstallController.ipAddressRegExp() regularExpression: InstallController.ipAddressRegExp()
} }
@ -105,9 +105,9 @@ PageType {
var yesButtonFunction = function() { var yesButtonFunction = function() {
SettingsController.primaryDns = "1.1.1.1" SettingsController.primaryDns = "1.1.1.1"
primaryDns.textFieldText = SettingsController.primaryDns primaryDns.textField.text = SettingsController.primaryDns
SettingsController.secondaryDns = "1.0.0.1" SettingsController.secondaryDns = "1.0.0.1"
secondaryDns.textFieldText = SettingsController.secondaryDns secondaryDns.textField.text = SettingsController.secondaryDns
PageController.showNotificationMessage(qsTr("Settings have been reset")) PageController.showNotificationMessage(qsTr("Settings have been reset"))
} }
var noButtonFunction = function() { var noButtonFunction = function() {
@ -125,11 +125,11 @@ PageType {
text: qsTr("Save") text: qsTr("Save")
clickedFunc: function() { clickedFunc: function() {
if (primaryDns.textFieldText !== SettingsController.primaryDns) { if (primaryDns.textField.text !== SettingsController.primaryDns) {
SettingsController.primaryDns = primaryDns.textFieldText SettingsController.primaryDns = primaryDns.textField.text
} }
if (secondaryDns.textFieldText !== SettingsController.secondaryDns) { if (secondaryDns.textField.text !== SettingsController.secondaryDns) {
SettingsController.secondaryDns = secondaryDns.textFieldText SettingsController.secondaryDns = secondaryDns.textField.text
} }
PageController.showNotificationMessage(qsTr("Settings saved")) PageController.showNotificationMessage(qsTr("Settings saved"))
} }

View file

@ -137,6 +137,8 @@ PageType {
Layout.topMargin: -8 Layout.topMargin: -8
Layout.bottomMargin: -8 Layout.bottomMargin: -8
visible: !GC.isMobile()
text: qsTr("Open logs folder") text: qsTr("Open logs folder")
leftImageSource: "qrc:/images/controls/folder-open.svg" leftImageSource: "qrc:/images/controls/folder-open.svg"
isSmallLeftImage: true isSmallLeftImage: true

View file

@ -36,16 +36,6 @@ PageType {
PageController.showErrorMessage(message) PageController.showErrorMessage(message)
} }
function onRemoveProcessedServerFinished(finishedMessage) {
if (!ServersModel.getServersCount()) {
PageController.goToPageHome()
} else {
PageController.goToStartPage()
PageController.goToPage(PageEnum.PageSettingsServersList)
}
PageController.showNotificationMessage(finishedMessage)
}
function onRebootProcessedServerFinished(finishedMessage) { function onRebootProcessedServerFinished(finishedMessage) {
PageController.showNotificationMessage(finishedMessage) PageController.showNotificationMessage(finishedMessage)
} }

View file

@ -22,8 +22,6 @@ PageType {
readonly property int pageSettingsServerProtocols: 0 readonly property int pageSettingsServerProtocols: 0
readonly property int pageSettingsServerServices: 1 readonly property int pageSettingsServerServices: 1
readonly property int pageSettingsServerData: 2 readonly property int pageSettingsServerData: 2
readonly property int pageSettingsApiServerInfo: 3
readonly property int pageSettingsApiLanguageList: 4
property var processedServer property var processedServer
@ -71,15 +69,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
objectName: "backButton" objectName: "backButton"
backButtonFunction: function() {
if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo &&
root.processedServer.isCountrySelectionAvailable) {
nestedStackView.currentIndex = root.pageSettingsApiLanguageList
} else {
PageController.closePage()
}
}
} }
HeaderType { HeaderType {
@ -91,18 +80,11 @@ PageType {
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.bottomMargin: 10 Layout.bottomMargin: 10
actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg" actionButtonImage: "qrc:/images/controls/edit-3.svg"
: "qrc:/images/controls/edit-3.svg"
headerText: root.processedServer.name headerText: root.processedServer.name
descriptionText: { descriptionText: {
if (root.processedServer.isServerFromGatewayApi) { if (root.processedServer.isServerFromTelegramApi) {
if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) {
return qsTr("Subscription is valid until ") + ApiServicesModel.getSelectedServiceData("endDate")
} else {
return ApiServicesModel.getSelectedServiceData("serviceDescription")
}
} else if (root.processedServer.isServerFromTelegramApi) {
return root.processedServer.serverDescription return root.processedServer.serverDescription
} else if (root.processedServer.hasWriteAccess) { } else if (root.processedServer.hasWriteAccess) {
return root.processedServer.credentialsLogin + " · " + root.processedServer.hostName return root.processedServer.credentialsLogin + " · " + root.processedServer.hostName
@ -112,60 +94,19 @@ PageType {
} }
actionButtonFunction: function() { actionButtonFunction: function() {
if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) {
nestedStackView.currentIndex = root.pageSettingsApiServerInfo
} else {
serverNameEditDrawer.openTriggered() serverNameEditDrawer.openTriggered()
} }
} }
}
DrawerType2 { RenameServerDrawer {
id: serverNameEditDrawer id: serverNameEditDrawer
objectName: "serverNameEditDrawer"
parent: root parent: root
anchors.fill: parent anchors.fill: parent
expandedHeight: root.height * 0.35 expandedHeight: root.height * 0.35
expandedStateContent: ColumnLayout { serverNameText: root.processedServer.name
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()
}
}
}
} }
TabBar { TabBar {
@ -181,8 +122,6 @@ PageType {
color: AmneziaStyle.color.transparent color: AmneziaStyle.color.transparent
} }
visible: !ServersModel.getProcessedServerData("isServerFromGatewayApi")
TabButtonType { TabButtonType {
id: protocolsTab id: protocolsTab
@ -221,9 +160,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
currentIndex: ServersModel.getProcessedServerData("isServerFromGatewayApi") ? currentIndex: tabBar.currentIndex
(ServersModel.getProcessedServerData("isCountrySelectionAvailable") ?
root.pageSettingsApiLanguageList : root.pageSettingsApiServerInfo) : tabBar.currentIndex
PageSettingsServerProtocols { PageSettingsServerProtocols {
id: protocolsPage id: protocolsPage
@ -239,16 +176,6 @@ PageType {
id: dataPage id: dataPage
stackView: root.stackView stackView: root.stackView
} }
PageSettingsApiServerInfo {
id: apiInfoPage
stackView: root.stackView
}
PageSettingsApiLanguageList {
id: apiLanguageListPage
stackView: root.stackView
}
} }
} }
} }

View file

@ -93,9 +93,21 @@ PageType {
clickedFunction: function() { clickedFunction: function() {
ServersModel.processedIndex = index ServersModel.processedIndex = index
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) PageController.goToPage(PageEnum.PageSettingsServerInfo)
} }
} }
}
DividerType {} DividerType {}
} }

View file

@ -274,13 +274,13 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
rightButtonClickedOnEnter: true rightButtonClickedOnEnter: true
textFieldPlaceholderText: qsTr("website or IP") textField.placeholderText: qsTr("website or IP")
buttonImageSource: "qrc:/images/controls/plus.svg" buttonImageSource: "qrc:/images/controls/plus.svg"
clickedFunc: function() { clickedFunc: function() {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
SitesController.addSite(textFieldText) SitesController.addSite(textField.text)
textFieldText = "" textField.text = ""
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
} }
} }
@ -338,7 +338,6 @@ PageType {
LabelWithButtonType { LabelWithButtonType {
id: exportSitesButton id: exportSitesButton
enabled: !SettingsController.isOnTv()
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Save site list") text: qsTr("Save site list")
@ -362,9 +361,7 @@ PageType {
} }
} }
DividerType { DividerType {}
enabled: !SettingsController.isOnTv()
}
} }
} }

View file

@ -138,7 +138,7 @@ PageType {
PageController.closePage() PageController.closePage()
} else { } else {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
InstallController.installServiceFromApi() ApiConfigsController.importServiceFromGateway()
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
} }
} }

View file

@ -163,12 +163,12 @@ PageType {
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
visible: textKey.textFieldText !== "" visible: textKey.textField.text !== ""
text: qsTr("Continue") text: qsTr("Continue")
clickedFunc: function() { clickedFunc: function() {
if (ImportController.extractConfigFromData(textKey.textFieldText)) { if (ImportController.extractConfigFromData(textKey.textField.text)) {
PageController.goToPage(PageEnum.PageSetupWizardViewConfig) PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
} }
} }
@ -217,6 +217,8 @@ PageType {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
implicitHeight: 32 implicitHeight: 32
visible: Qt.platform.os !== "ios"
defaultColor: AmneziaStyle.color.transparent defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite pressedColor: AmneziaStyle.color.sheerWhite
@ -252,7 +254,7 @@ PageType {
property bool isVisible: true property bool isVisible: true
property var handler: function() { property var handler: function() {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
var result = InstallController.fillAvailableServices() var result = ApiConfigsController.fillAvailableServices()
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
if (result) { if (result) {
PageController.goToPage(PageEnum.PageSetupWizardApiServicesList) PageController.goToPage(PageEnum.PageSetupWizardApiServicesList)
@ -330,7 +332,7 @@ PageType {
property string title: qsTr("I have nothing") property string title: qsTr("I have nothing")
property string description: qsTr("") property string description: qsTr("")
property string imageSource: "qrc:/images/controls/help-circle.svg" 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() { property var handler: function() {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
} }

View file

@ -82,24 +82,19 @@ PageType {
reuseItems: true reuseItems: true
delegate: ColumnLayout { delegate: ColumnLayout {
property alias textField: _textField.textField
width: listView.width width: listView.width
TextFieldWithHeaderType { TextFieldWithHeaderType {
id: _textField id: delegate
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
property bool hidePassword: hideText
headerText: title headerText: title
textField.echoMode: hideText ? TextInput.Password : TextInput.Normal textField.echoMode: hideContent ? TextInput.Password : TextInput.Normal
buttonImageSource: imageSource textField.placeholderText: placeholderContent
textFieldPlaceholderText: placeholderText textField.text: textField.text
textField.text: textFieldText
rightButtonClickedOnEnter: true rightButtonClickedOnEnter: true
@ -108,17 +103,12 @@ PageType {
} }
textField.onFocusChanged: { textField.onFocusChanged: {
var _currentIndex = listView.currentIndex textField.text = textField.text.replace(/^\s+|\s+$/g, '')
var _currentItem = listView.itemAtIndex(_currentIndex).children[0]
listView.model[_currentIndex].textFieldText = _currentItem.textFieldText.replace(/^\s+|\s+$/g, '')
} }
textField.onTextChanged: { textField.onTextChanged: {
var _currentIndex = listView.currentIndex if (hideContent) {
textFieldText = textField.text buttonImageSource = textField.text !== "" ? (hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : ""
if (_currentIndex === vars.secretDataIndex) {
buttonImageSource = textFieldText !== "" ? (hideText ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : ""
} }
} }
} }
@ -143,9 +133,9 @@ PageType {
} }
InstallController.setShouldCreateServer(true) InstallController.setShouldCreateServer(true)
var _hostname = listView.itemAtIndex(vars.hostnameIndex).children[0].textFieldText var _hostname = listView.itemAtIndex(vars.hostnameIndex).children[0].textField.text
var _username = listView.itemAtIndex(vars.usernameIndex).children[0].textFieldText var _username = listView.itemAtIndex(vars.usernameIndex).children[0].textField.text
var _secretData = listView.itemAtIndex(vars.secretDataIndex).children[0].textFieldText var _secretData = listView.itemAtIndex(vars.secretDataIndex).children[0].textField.text
InstallController.setProcessedServerCredentials(_hostname, _username, _secretData) InstallController.setProcessedServerCredentials(_hostname, _username, _secretData)
@ -194,23 +184,23 @@ PageType {
function isCredentialsFilled() { function isCredentialsFilled() {
var hasEmptyField = false var hasEmptyField = false
var _hostname = listView.itemAtIndex(vars.hostnameIndex).children[0] var hostnameItem = listView.itemAtIndex(vars.hostnameIndex).children[0]
if (_hostname.textFieldText === "") { if (hostnameItem.textField.text === "") {
_hostname.errorText = qsTr("Ip address cannot be empty") hostnameItem.errorText = qsTr("Ip address cannot be empty")
hasEmptyField = true hasEmptyField = true
} else if (!_hostname.textField.acceptableInput) { } else if (!hostnameItem.textField.acceptableInput) {
_hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88") hostnameItem.errorText = qsTr("Enter the address in the format 255.255.255.255:88")
} }
var _username = listView.itemAtIndex(vars.usernameIndex).children[0] var usernameItem = listView.itemAtIndex(vars.usernameIndex).children[0]
if (_username.textFieldText === "") { if (usernameItem.textField.text === "") {
_username.errorText = qsTr("Login cannot be empty") usernameItem.errorText = qsTr("Login cannot be empty")
hasEmptyField = true hasEmptyField = true
} }
var _secretData = listView.itemAtIndex(vars.secretDataIndex).children[0] var secretDataItem = listView.itemAtIndex(vars.secretDataIndex).children[0]
if (_secretData.textFieldText === "") { if (secretDataItem.textField.text === "") {
_secretData.errorText = qsTr("Password/private key cannot be empty") secretDataItem.errorText = qsTr("Password/private key cannot be empty")
hasEmptyField = true hasEmptyField = true
} }
@ -218,46 +208,37 @@ PageType {
} }
property list<QtObject> inputFields: [ property list<QtObject> inputFields: [
hostname, hostnameObject,
username, usernameObject,
secretData secretDataObject
] ]
QtObject { QtObject {
id: hostname id: hostnameObject
property string title: qsTr("Server IP address [:port]") property string title: qsTr("Server IP address [:port]")
readonly property string placeholderText: qsTr("255.255.255.255:22") readonly property string placeholderContent: qsTr("255.255.255.255:22")
property string textFieldText: "" property bool hideContent: false
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 var clickedHandler: undefined readonly property var clickedHandler: undefined
} }
QtObject { 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") property string title: qsTr("Password or SSH private key")
readonly property string placeholderText: "" readonly property string placeholderContent: ""
property string textFieldText: "" property bool hideContent: true
property bool hideText: true
property string imageSource: textFieldText !== "" ? (hideText ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : ""
readonly property var clickedHandler: function() { readonly property var clickedHandler: function() {
hideText = !hideText hideContent = !hideContent
} }
} }

View file

@ -65,7 +65,7 @@ PageType {
implicitWidth: parent.width implicitWidth: parent.width
headerTextMaximumLineCount: 10 headerTextMaximumLineCount: 10
headerText: qsTr("What is the level of internet control in your region?") headerText: qsTr("Choose Installation Type")
} }
ButtonGroup { ButtonGroup {
@ -139,7 +139,8 @@ PageType {
CardType { CardType {
implicitWidth: parent.width implicitWidth: parent.width
headerText: qsTr("Choose a VPN protocol") headerText: qsTr("Manual")
bodyText: qsTr("Choose a VPN protocol")
ButtonGroup.group: buttonGroup ButtonGroup.group: buttonGroup

View file

@ -257,7 +257,7 @@ PageType {
} }
PageController.goToPage(PageEnum.PageSetupWizardInstalling); 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) { if (ProtocolProps.defaultPort(defaultContainerProto) < 0) {
port.visible = false port.visible = false
} else { } else {
port.textFieldText = ProtocolProps.getPortForInstall(defaultContainerProto) port.textField.text = ProtocolProps.getPortForInstall(defaultContainerProto)
} }
transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto)

View file

@ -51,7 +51,7 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: qsTr("Key") headerText: qsTr("Key")
textFieldPlaceholderText: "vpn://" textField.placeholderText: "vpn://"
buttonText: qsTr("Insert") buttonText: qsTr("Insert")
clickedFunc: function() { clickedFunc: function() {
@ -75,7 +75,7 @@ PageType {
text: qsTr("Continue") text: qsTr("Continue")
clickedFunc: function() { clickedFunc: function() {
if (ImportController.extractConfigFromData(textKey.textFieldText)) { if (ImportController.extractConfigFromData(textKey.textField.text)) {
PageController.goToPage(PageEnum.PageSetupWizardViewConfig) PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
} }
} }

View file

@ -46,30 +46,29 @@ PageType {
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
shareConnectionDrawer.openTriggered() shareConnectionDrawer.openTriggered()
shareConnectionDrawer.contentVisible = false
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
switch (type) { switch (type) {
case PageShare.ConfigType.AmneziaConnection: { case PageShare.ConfigType.AmneziaConnection: {
ExportController.generateConnectionConfig(clientNameTextField.textFieldText); ExportController.generateConnectionConfig(clientNameTextField.textField.text);
break; break;
} }
case PageShare.ConfigType.OpenVpn: { case PageShare.ConfigType.OpenVpn: {
ExportController.generateOpenVpnConfig(clientNameTextField.textFieldText) ExportController.generateOpenVpnConfig(clientNameTextField.textField.text)
shareConnectionDrawer.configCaption = qsTr("Save OpenVPN config") shareConnectionDrawer.configCaption = qsTr("Save OpenVPN config")
shareConnectionDrawer.configExtension = ".ovpn" shareConnectionDrawer.configExtension = ".ovpn"
shareConnectionDrawer.configFileName = "amnezia_for_openvpn" shareConnectionDrawer.configFileName = "amnezia_for_openvpn"
break break
} }
case PageShare.ConfigType.WireGuard: { case PageShare.ConfigType.WireGuard: {
ExportController.generateWireGuardConfig(clientNameTextField.textFieldText) ExportController.generateWireGuardConfig(clientNameTextField.textField.text)
shareConnectionDrawer.configCaption = qsTr("Save WireGuard config") shareConnectionDrawer.configCaption = qsTr("Save WireGuard config")
shareConnectionDrawer.configExtension = ".conf" shareConnectionDrawer.configExtension = ".conf"
shareConnectionDrawer.configFileName = "amnezia_for_wireguard" shareConnectionDrawer.configFileName = "amnezia_for_wireguard"
break break
} }
case PageShare.ConfigType.Awg: { case PageShare.ConfigType.Awg: {
ExportController.generateAwgConfig(clientNameTextField.textFieldText) ExportController.generateAwgConfig(clientNameTextField.textField.text)
shareConnectionDrawer.configCaption = qsTr("Save AmneziaWG config") shareConnectionDrawer.configCaption = qsTr("Save AmneziaWG config")
shareConnectionDrawer.configExtension = ".conf" shareConnectionDrawer.configExtension = ".conf"
shareConnectionDrawer.configFileName = "amnezia_for_awg" shareConnectionDrawer.configFileName = "amnezia_for_awg"
@ -90,7 +89,7 @@ PageType {
break break
} }
case PageShare.ConfigType.Xray: { case PageShare.ConfigType.Xray: {
ExportController.generateXrayConfig(clientNameTextField.textFieldText) ExportController.generateXrayConfig(clientNameTextField.textField.text)
shareConnectionDrawer.configCaption = qsTr("Save XRay config") shareConnectionDrawer.configCaption = qsTr("Save XRay config")
shareConnectionDrawer.configExtension = ".json" shareConnectionDrawer.configExtension = ".json"
shareConnectionDrawer.configFileName = "amnezia_for_xray" shareConnectionDrawer.configFileName = "amnezia_for_xray"
@ -296,7 +295,7 @@ PageType {
visible: accessTypeSelector.currentIndex === 0 visible: accessTypeSelector.currentIndex === 0
headerText: qsTr("User name") headerText: qsTr("User name")
textFieldText: "New client" textField.text: "New client"
textField.maximumLength: 20 textField.maximumLength: 20
checkEmptyText: true checkEmptyText: true
@ -525,7 +524,7 @@ PageType {
parentFlickable: a parentFlickable: a
clickedFunc: function(){ clickedFunc: function(){
if (clientNameTextField.textFieldText !== "") { if (clientNameTextField.textField.text !== "") {
ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type) ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type)
} }
} }
@ -555,14 +554,14 @@ PageType {
id: searchTextField id: searchTextField
Layout.fillWidth: true Layout.fillWidth: true
textFieldPlaceholderText: qsTr("Search") textField.placeholderText: qsTr("Search")
Keys.onEscapePressed: { Keys.onEscapePressed: {
root.isSearchBarVisible = false root.isSearchBarVisible = false
} }
function navigateTo() { function navigateTo() {
if (searchTextField.textFieldText === "") { if (searchTextField.textField.text === "") {
root.isSearchBarVisible = false root.isSearchBarVisible = false
} }
} }
@ -601,7 +600,7 @@ PageType {
sourceModel: ClientManagementModel sourceModel: ClientManagementModel
filters: RegExpFilter { filters: RegExpFilter {
roleName: "clientName" roleName: "clientName"
pattern: ".*" + searchTextField.textFieldText + ".*" pattern: ".*" + searchTextField.textField.text + ".*"
caseSensitivity: Qt.CaseInsensitive caseSensitivity: Qt.CaseInsensitive
} }
} }
@ -765,7 +764,7 @@ PageType {
id: clientNameEditor id: clientNameEditor
Layout.fillWidth: true Layout.fillWidth: true
headerText: qsTr("Client name") headerText: qsTr("Client name")
textFieldText: clientName textField.text: clientName
textField.maximumLength: 20 textField.maximumLength: 20
checkEmptyText: true checkEmptyText: true
} }
@ -778,14 +777,14 @@ PageType {
text: qsTr("Save") text: qsTr("Save")
clickedFunc: function() { clickedFunc: function() {
if (clientNameEditor.textFieldText === "") { if (clientNameEditor.textField.text === "") {
return return
} }
if (clientNameEditor.textFieldText !== clientName) { if (clientNameEditor.textField.text !== clientName) {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
ExportController.renameClient(index, ExportController.renameClient(index,
clientNameEditor.textFieldText, clientNameEditor.textField.text,
ContainersModel.getProcessedContainerIndex(), ContainersModel.getProcessedContainerIndex(),
ServersModel.getProcessedServerCredentials()) ServersModel.getProcessedServerCredentials())
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)

View file

@ -141,7 +141,6 @@ PageType {
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
shareConnectionDrawer.openTriggered() shareConnectionDrawer.openTriggered()
shareConnectionDrawer.contentVisible = true
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
} }

View file

@ -132,27 +132,22 @@ PageType {
PageController.showNotificationMessage(message) PageController.showNotificationMessage(message)
} }
function onInstallServerFromApiFinished(message) { function onRemoveProcessedServerFinished(finishedMessage) {
PageController.showBusyIndicator(false) if (!ServersModel.getServersCount()) {
if (!ConnectionController.isConnected) { PageController.goToPageHome()
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); } else {
ServersModel.processedIndex = ServersModel.defaultIndex PageController.goToStartPage()
PageController.goToPage(PageEnum.PageSettingsServersList)
}
PageController.showNotificationMessage(finishedMessage)
} }
PageController.goToPageHome() function onNoInstalledContainers() {
PageController.showNotificationMessage(message) PageController.setTriggeredByConnectButton(true)
}
function onChangeApiCountryFinished(message) { ServersModel.processedIndex = ServersModel.getDefaultServerIndex()
PageController.showBusyIndicator(false) InstallController.setShouldCreateServer(false)
PageController.goToPage(PageEnum.PageSetupWizardEasy)
PageController.goToPageHome()
PageController.showNotificationMessage(message)
}
function onReloadServerFromApiFinished(message) {
PageController.goToPageHome()
PageController.showNotificationMessage(message)
} }
} }
@ -165,14 +160,6 @@ PageType {
PageController.showNotificationMessage(message) PageController.showNotificationMessage(message)
PageController.closePage() PageController.closePage()
} }
function onNoInstalledContainers() {
PageController.setTriggeredByConnectButton(true)
ServersModel.processedIndex = ServersModel.getDefaultServerIndex()
InstallController.setShouldCreateServer(false)
PageController.goToPage(PageEnum.PageSetupWizardEasy)
}
} }
Connections { 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 { StackViewType {
id: tabBarStackView id: tabBarStackView
objectName: "tabBarStackView" objectName: "tabBarStackView"

View file

@ -176,12 +176,12 @@ Window {
Connections { Connections {
target: privateKeyPassphraseDrawer target: privateKeyPassphraseDrawer
function onOpened() { function onOpened() {
passphrase.textFieldText = "" passphrase.textField.text = ""
passphrase.textField.forceActiveFocus() passphrase.textField.forceActiveFocus()
} }
function onAboutToHide() { function onAboutToHide() {
if (passphrase.textFieldText !== "") { if (passphrase.textField.text !== "") {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
} }
} }
@ -222,7 +222,7 @@ Window {
clickedFunc: function() { clickedFunc: function() {
privateKeyPassphraseDrawer.closeTriggered() privateKeyPassphraseDrawer.closeTriggered()
PageController.passphraseRequestDrawerClosed(passphrase.textFieldText) PageController.passphraseRequestDrawerClosed(passphrase.textField.text)
} }
} }
} }

View file

@ -215,10 +215,9 @@ ErrorCode VpnConnection::lastError() const
void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &vpnConfiguration) 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(serverIndex)
.arg(ContainerProps::containerToString(container)) .arg(ContainerProps::containerToString(container));
<< m_settings->routeMode();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
if (!m_IpcClient) { if (!m_IpcClient) {
m_IpcClient = new IpcClient(this); 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; QJsonArray sitesJsonArray;
if (m_settings->isSitesSplitTunnelingEnabled()) { if (m_settings->isSitesSplitTunnelingEnabled()) {
routeMode = m_settings->routeMode(); sitesRouteMode = m_settings->routeMode();
if (allowSiteBasedSplitTunneling) { if (allowSiteBasedSplitTunneling) {
auto sites = m_settings->getVpnIps(routeMode); auto sites = m_settings->getVpnIps(sitesRouteMode);
for (const auto &site : sites) { for (const auto &site : sites) {
sitesJsonArray.append(site); sitesJsonArray.append(site);
} }
// Allow traffic to Amnezia DNS // 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::dns1).toString());
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns2).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); m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray);
Settings::AppsRouteMode appsRouteMode = Settings::AppsRouteMode::VpnAllApps; 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::appSplitTunnelType, appsRouteMode);
m_vpnConfiguration.insert(config_key::splitTunnelApps, appsJsonArray); 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 #ifdef Q_OS_ANDROID