Merge pull request #1395 from amnezia-vpn/feature/subscription-settings-page
feature/subscription settings page
This commit is contained in:
commit
83460bc29b
63 changed files with 3349 additions and 1703 deletions
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.8.3.3
|
project(${PROJECT} VERSION 4.8.4.0
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
|
@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 2076)
|
set(APP_ANDROID_VERSION_CODE 2077)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|
|
@ -105,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
|
||||||
|
@ -115,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
|
||||||
|
@ -319,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)
|
||||||
|
|
|
@ -14,22 +14,14 @@
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
|
||||||
#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);
|
||||||
|
@ -295,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
|
||||||
|
@ -305,165 +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]() {
|
|
||||||
if (m_reloadConfigErrorOccurredConnection) {
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
191
client/cmake/sources.cmake
Normal file
191
client/cmake/sources.cmake
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||||
|
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/migrations.h
|
||||||
|
${CLIENT_ROOT_DIR}/../ipc/ipc.h
|
||||||
|
${CLIENT_ROOT_DIR}/amnezia_application.h
|
||||||
|
${CLIENT_ROOT_DIR}/containers/containers_defs.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/defs.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/errorstrings.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/scripts_registry.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/server_defs.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/api/apiDefs.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/qrCodeUtils.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/coreController.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/serverController.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/protocols_defs.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/qml_register_protocols.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/pages.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/qautostart.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.h
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/sshclient.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/networkUtilities.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/serialization.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/transfer.h
|
||||||
|
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
||||||
|
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mozilla headres
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/models/server.h
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT IOS)
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT ANDROID)
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/ui/notificationhandler.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/migrations.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/amnezia_application.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/containers/containers_defs.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/errorstrings.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/scripts_registry.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/server_defs.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/qrCodeUtils.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/coreController.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/serverController.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/protocols_defs.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/qautostart.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/sshclient.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/networkUtilities.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/outbound.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/inbound.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/ss.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/ssd.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/vless.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/trojan.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/vmess.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/vmess_new.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mozilla sources
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT IOS)
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT ANDROID)
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.h)
|
||||||
|
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.cpp)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.h)
|
||||||
|
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.cpp)
|
||||||
|
|
||||||
|
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.h)
|
||||||
|
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.cpp)
|
||||||
|
|
||||||
|
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/*.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/services/*.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/api/*.h
|
||||||
|
)
|
||||||
|
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/*.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/protocols/*.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/services/*.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/api/*.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS
|
||||||
|
${CLIENT_ROOT_DIR}/ui/controllers/*.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/controllers/api/*.h
|
||||||
|
)
|
||||||
|
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS
|
||||||
|
${CLIENT_ROOT_DIR}/ui/controllers/*.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/controllers/api/*.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${COMMON_FILES_H}
|
||||||
|
${PAGE_LOGIC_H}
|
||||||
|
${CONFIGURATORS_H}
|
||||||
|
${UI_MODELS_H}
|
||||||
|
${UI_CONTROLLERS_H}
|
||||||
|
)
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${COMMON_FILES_CPP}
|
||||||
|
${PAGE_LOGIC_CPP}
|
||||||
|
${CONFIGURATORS_CPP}
|
||||||
|
${UI_MODELS_CPP}
|
||||||
|
${UI_CONTROLLERS_CPP}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(RESOURCES ${RESOURCES}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
|
message("Client desktop build")
|
||||||
|
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||||
|
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/core/ipcclient.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/privileged_process.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/privileged_process.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
|
||||||
|
)
|
||||||
|
endif()
|
51
client/core/api/apiDefs.h
Normal file
51
client/core/api/apiDefs.h
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#ifndef APIDEFS_H
|
||||||
|
#define APIDEFS_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace apiDefs
|
||||||
|
{
|
||||||
|
enum ConfigType {
|
||||||
|
AmneziaFreeV2 = 0,
|
||||||
|
AmneziaFreeV3,
|
||||||
|
AmneziaPremiumV1,
|
||||||
|
AmneziaPremiumV2,
|
||||||
|
SelfHosted
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ConfigSource {
|
||||||
|
Telegram = 1,
|
||||||
|
AmneziaGateway
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace key
|
||||||
|
{
|
||||||
|
constexpr QLatin1String configVersion("config_version");
|
||||||
|
|
||||||
|
constexpr QLatin1String apiConfig("api_config");
|
||||||
|
constexpr QLatin1String stackType("stack_type");
|
||||||
|
constexpr QLatin1String serviceType("service_type");
|
||||||
|
|
||||||
|
constexpr QLatin1String vpnKey("vpn_key");
|
||||||
|
|
||||||
|
constexpr QLatin1String installationUuid("installation_uuid");
|
||||||
|
constexpr QLatin1String workerLastUpdated("worker_last_updated");
|
||||||
|
constexpr QLatin1String lastDownloaded("last_downloaded");
|
||||||
|
constexpr QLatin1String sourceType("source_type");
|
||||||
|
|
||||||
|
constexpr QLatin1String serverCountryCode("server_country_code");
|
||||||
|
constexpr QLatin1String serverCountryName("server_country_name");
|
||||||
|
|
||||||
|
constexpr QLatin1String osVersion("os_version");
|
||||||
|
|
||||||
|
constexpr QLatin1String availableCountries("available_countries");
|
||||||
|
constexpr QLatin1String activeDeviceCount("active_device_count");
|
||||||
|
constexpr QLatin1String maxDeviceCount("max_device_count");
|
||||||
|
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
||||||
|
constexpr QLatin1String issuedConfigs("issued_configs");
|
||||||
|
}
|
||||||
|
|
||||||
|
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // APIDEFS_H
|
87
client/core/api/apiUtils.cpp
Normal file
87
client/core/api/apiUtils.cpp
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
#include "apiUtils.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
||||||
|
{
|
||||||
|
QDateTime now = QDateTime::currentDateTime();
|
||||||
|
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
|
||||||
|
return endDate < now;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
|
||||||
|
switch (configVersion) {
|
||||||
|
case apiDefs::ConfigSource::Telegram: return true;
|
||||||
|
case apiDefs::ConfigSource::AmneziaGateway: return true;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
|
||||||
|
switch (configVersion) {
|
||||||
|
case apiDefs::ConfigSource::Telegram: {
|
||||||
|
};
|
||||||
|
case apiDefs::ConfigSource::AmneziaGateway: {
|
||||||
|
constexpr QLatin1String stackPremium("prem");
|
||||||
|
constexpr QLatin1String stackFree("free");
|
||||||
|
|
||||||
|
constexpr QLatin1String servicePremium("amnezia-premium");
|
||||||
|
constexpr QLatin1String serviceFree("amnezia-free");
|
||||||
|
|
||||||
|
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||||
|
auto stackType = apiConfigObject.value(apiDefs::key::stackType).toString();
|
||||||
|
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
||||||
|
|
||||||
|
if (serviceType == servicePremium || stackType == stackPremium) {
|
||||||
|
return apiDefs::ConfigType::AmneziaPremiumV2;
|
||||||
|
} else if (serviceType == serviceFree || stackType == stackFree) {
|
||||||
|
return apiDefs::ConfigType::AmneziaFreeV3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return apiDefs::ConfigType::SelfHosted;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
const int httpStatusCodeConflict = 409;
|
||||||
|
const int httpStatusCodeNotFound = 404;
|
||||||
|
|
||||||
|
if (!sslErrors.empty()) {
|
||||||
|
qDebug().noquote() << sslErrors;
|
||||||
|
return amnezia::ErrorCode::ApiConfigSslError;
|
||||||
|
} else if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
return amnezia::ErrorCode::NoError;
|
||||||
|
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||||
|
} else {
|
||||||
|
QString err = reply->errorString();
|
||||||
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
qDebug() << QString::fromUtf8(reply->readAll());
|
||||||
|
qDebug() << reply->error();
|
||||||
|
qDebug() << err;
|
||||||
|
qDebug() << httpStatusCode;
|
||||||
|
if (httpStatusCode == httpStatusCodeConflict) {
|
||||||
|
return amnezia::ErrorCode::ApiConfigLimitError;
|
||||||
|
} else if (httpStatusCode == httpStatusCodeNotFound) {
|
||||||
|
return amnezia::ErrorCode::ApiNotFoundError;
|
||||||
|
}
|
||||||
|
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "something went wrong";
|
||||||
|
return amnezia::ErrorCode::InternalError;
|
||||||
|
}
|
22
client/core/api/apiUtils.h
Normal file
22
client/core/api/apiUtils.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#ifndef APIUTILS_H
|
||||||
|
#define APIUTILS_H
|
||||||
|
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "apiDefs.h"
|
||||||
|
#include "core/defs.h"
|
||||||
|
|
||||||
|
namespace apiUtils
|
||||||
|
{
|
||||||
|
bool isServerFromApi(const QJsonObject &serverConfigObject);
|
||||||
|
|
||||||
|
bool isSubscriptionExpired(const QString &subscriptionEndDate);
|
||||||
|
|
||||||
|
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
||||||
|
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
||||||
|
|
||||||
|
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // APIUTILS_H
|
|
@ -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 if (reply->error() == QNetworkReply::NetworkError::SslHandshakeFailedError) {
|
|
||||||
emit errorOccurred(ErrorCode::ApiConfigSslError);
|
|
||||||
} 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; });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -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
|
|
338
client/core/controllers/coreController.cpp
Normal file
338
client/core/controllers/coreController.cpp
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
#include "coreController.h"
|
||||||
|
|
||||||
|
#include <QTranslator>
|
||||||
|
|
||||||
|
#if defined(Q_OS_ANDROID)
|
||||||
|
#include "core/installedAppsImageProvider.h"
|
||||||
|
#include "platforms/android/android_controller.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_IOS)
|
||||||
|
#include "platforms/ios/ios_controller.h"
|
||||||
|
#include <AmneziaVPN-Swift.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
|
||||||
|
QQmlApplicationEngine *engine, QObject *parent)
|
||||||
|
: QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine)
|
||||||
|
{
|
||||||
|
initModels();
|
||||||
|
initControllers();
|
||||||
|
initSignalHandlers();
|
||||||
|
|
||||||
|
initNotificationHandler();
|
||||||
|
|
||||||
|
auto locale = m_settings->getAppLanguage();
|
||||||
|
m_translator.reset(new QTranslator());
|
||||||
|
updateTranslator(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initModels()
|
||||||
|
{
|
||||||
|
m_containersModel.reset(new ContainersModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
|
||||||
|
|
||||||
|
m_defaultServerContainersModel.reset(new ContainersModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
|
||||||
|
|
||||||
|
m_serversModel.reset(new ServersModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
|
||||||
|
|
||||||
|
m_languageModel.reset(new LanguageModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
||||||
|
|
||||||
|
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||||
|
|
||||||
|
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
||||||
|
|
||||||
|
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
||||||
|
|
||||||
|
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
|
||||||
|
|
||||||
|
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
|
||||||
|
|
||||||
|
m_cloakConfigModel.reset(new CloakConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
|
||||||
|
|
||||||
|
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
|
||||||
|
|
||||||
|
m_awgConfigModel.reset(new AwgConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
||||||
|
|
||||||
|
m_xrayConfigModel.reset(new XrayConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
|
||||||
|
|
||||||
|
#ifdef Q_OS_WINDOWS
|
||||||
|
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
||||||
|
|
||||||
|
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
|
||||||
|
|
||||||
|
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||||
|
|
||||||
|
m_apiServicesModel.reset(new ApiServicesModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
||||||
|
|
||||||
|
m_apiCountryModel.reset(new ApiCountryModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
||||||
|
|
||||||
|
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initControllers()
|
||||||
|
{
|
||||||
|
m_connectionController.reset(
|
||||||
|
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||||
|
|
||||||
|
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||||
|
|
||||||
|
m_focusController.reset(new FocusController(m_engine, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get());
|
||||||
|
|
||||||
|
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||||
|
|
||||||
|
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||||
|
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
|
||||||
|
|
||||||
|
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||||
|
|
||||||
|
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
||||||
|
|
||||||
|
m_settingsController.reset(
|
||||||
|
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||||
|
|
||||||
|
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
||||||
|
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||||
|
|
||||||
|
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
||||||
|
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
||||||
|
|
||||||
|
m_systemController.reset(new SystemController(m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
||||||
|
|
||||||
|
m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
|
||||||
|
|
||||||
|
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initAndroidController()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
if (!AndroidController::initLogging()) {
|
||||||
|
qFatal("Android logging initialization failed");
|
||||||
|
}
|
||||||
|
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
|
||||||
|
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
||||||
|
|
||||||
|
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
|
||||||
|
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
||||||
|
|
||||||
|
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
|
||||||
|
|
||||||
|
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
|
||||||
|
|
||||||
|
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
|
||||||
|
m_connectionController->onConnectionStateChanged(state);
|
||||||
|
if (m_vpnConnection)
|
||||||
|
m_vpnConnection->restoreConnection();
|
||||||
|
});
|
||||||
|
if (!AndroidController::instance()->initialize()) {
|
||||||
|
qFatal("Android controller initialization failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
|
||||||
|
emit m_pageController->goToPageHome();
|
||||||
|
m_importController->extractConfigFromData(data);
|
||||||
|
data.clear();
|
||||||
|
emit m_pageController->goToPageViewConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initAppleController()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->initialize();
|
||||||
|
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
|
||||||
|
emit m_pageController->goToPageHome();
|
||||||
|
m_importController->extractConfigFromData(data);
|
||||||
|
emit m_pageController->goToPageViewConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
|
||||||
|
emit m_pageController->goToPageHome();
|
||||||
|
m_pageController->goToPageSettingsBackup();
|
||||||
|
emit m_settingsController->importBackupFromOutside(filePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
||||||
|
|
||||||
|
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initSignalHandlers()
|
||||||
|
{
|
||||||
|
initErrorMessagesHandler();
|
||||||
|
|
||||||
|
initApiCountryModelUpdateHandler();
|
||||||
|
initContainerModelUpdateHandler();
|
||||||
|
initAdminConfigRevokedHandler();
|
||||||
|
initPassphraseRequestHandler();
|
||||||
|
initTranslationsUpdatedHandler();
|
||||||
|
initAutoConnectHandler();
|
||||||
|
initAmneziaDnsToggledHandler();
|
||||||
|
initPrepareConfigHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initNotificationHandler()
|
||||||
|
{
|
||||||
|
#ifndef Q_OS_ANDROID
|
||||||
|
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||||
|
|
||||||
|
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||||
|
&NotificationHandler::setConnectionState);
|
||||||
|
|
||||||
|
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
||||||
|
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
||||||
|
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
|
||||||
|
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||||
|
&ConnectionController::closeConnection);
|
||||||
|
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::updateTranslator(const QLocale &locale)
|
||||||
|
{
|
||||||
|
if (!m_translator->isEmpty()) {
|
||||||
|
QCoreApplication::removeTranslator(m_translator.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
|
||||||
|
if (m_translator->load(strFileName)) {
|
||||||
|
if (QCoreApplication::installTranslator(m_translator.get())) {
|
||||||
|
m_settings->setAppLanguage(locale);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_settings->setAppLanguage(QLocale::English);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_engine->retranslate();
|
||||||
|
|
||||||
|
emit translationsUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initErrorMessagesHandler()
|
||||||
|
{
|
||||||
|
connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](ErrorCode errorCode) {
|
||||||
|
emit m_pageController->showErrorMessage(errorCode);
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_apiConfigsController.get(), &ApiConfigsController::errorOccurred, m_pageController.get(),
|
||||||
|
qOverload<ErrorCode>(&PageController::showErrorMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::setQmlRoot()
|
||||||
|
{
|
||||||
|
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initApiCountryModelUpdateHandler()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() {
|
||||||
|
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
||||||
|
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
||||||
|
});
|
||||||
|
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
|
||||||
|
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initContainerModelUpdateHandler()
|
||||||
|
{
|
||||||
|
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||||
|
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||||
|
&ContainersModel::updateModel);
|
||||||
|
m_serversModel->resetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initAdminConfigRevokedHandler()
|
||||||
|
{
|
||||||
|
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||||
|
&ServersModel::clearCachedProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initPassphraseRequestHandler()
|
||||||
|
{
|
||||||
|
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
||||||
|
&PageController::showPassphraseRequestDrawer);
|
||||||
|
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
|
||||||
|
&InstallController::setEncryptedPassphrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initTranslationsUpdatedHandler()
|
||||||
|
{
|
||||||
|
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &CoreController::updateTranslator);
|
||||||
|
connect(this, &CoreController::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
|
||||||
|
connect(this, &CoreController::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initAutoConnectHandler()
|
||||||
|
{
|
||||||
|
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
||||||
|
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initAmneziaDnsToggledHandler()
|
||||||
|
{
|
||||||
|
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initPrepareConfigHandler()
|
||||||
|
{
|
||||||
|
connect(m_connectionController.get(), &ConnectionController::prepareConfig, this, [this]() {
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
|
||||||
|
|
||||||
|
if (!m_apiConfigsController->isConfigValid()) {
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_installController->isConfigValid()) {
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connectionController->openConnection();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<PageController> CoreController::pageController() const
|
||||||
|
{
|
||||||
|
return m_pageController;
|
||||||
|
}
|
134
client/core/controllers/coreController.h
Normal file
134
client/core/controllers/coreController.h
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
#ifndef CORECONTROLLER_H
|
||||||
|
#define CORECONTROLLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#include "ui/controllers/api/apiConfigsController.h"
|
||||||
|
#include "ui/controllers/api/apiSettingsController.h"
|
||||||
|
#include "ui/controllers/appSplitTunnelingController.h"
|
||||||
|
#include "ui/controllers/connectionController.h"
|
||||||
|
#include "ui/controllers/exportController.h"
|
||||||
|
#include "ui/controllers/focusController.h"
|
||||||
|
#include "ui/controllers/importController.h"
|
||||||
|
#include "ui/controllers/installController.h"
|
||||||
|
#include "ui/controllers/pageController.h"
|
||||||
|
#include "ui/controllers/settingsController.h"
|
||||||
|
#include "ui/controllers/sitesController.h"
|
||||||
|
#include "ui/controllers/systemController.h"
|
||||||
|
|
||||||
|
#include "ui/models/containers_model.h"
|
||||||
|
#include "ui/models/languageModel.h"
|
||||||
|
#include "ui/models/protocols/cloakConfigModel.h"
|
||||||
|
#ifdef Q_OS_WINDOWS
|
||||||
|
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||||
|
#endif
|
||||||
|
#include "ui/models/api/apiAccountInfoModel.h"
|
||||||
|
#include "ui/models/api/apiServicesModel.h"
|
||||||
|
#include "ui/models/api/apiCountryModel.h"
|
||||||
|
#include "ui/models/appSplitTunnelingModel.h"
|
||||||
|
#include "ui/models/clientManagementModel.h"
|
||||||
|
#include "ui/models/protocols/awgConfigModel.h"
|
||||||
|
#include "ui/models/protocols/openvpnConfigModel.h"
|
||||||
|
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
||||||
|
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||||
|
#include "ui/models/protocols/xrayConfigModel.h"
|
||||||
|
#include "ui/models/protocols_model.h"
|
||||||
|
#include "ui/models/servers_model.h"
|
||||||
|
#include "ui/models/services/sftpConfigModel.h"
|
||||||
|
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||||
|
#include "ui/models/sites_model.h"
|
||||||
|
|
||||||
|
#ifndef Q_OS_ANDROID
|
||||||
|
#include "ui/notificationhandler.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class CoreController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
|
||||||
|
QQmlApplicationEngine *engine, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
QSharedPointer<PageController> pageController() const;
|
||||||
|
void setQmlRoot();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void translationsUpdated();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initModels();
|
||||||
|
void initControllers();
|
||||||
|
void initAndroidController();
|
||||||
|
void initAppleController();
|
||||||
|
void initSignalHandlers();
|
||||||
|
|
||||||
|
void initNotificationHandler();
|
||||||
|
|
||||||
|
void updateTranslator(const QLocale &locale);
|
||||||
|
|
||||||
|
void initErrorMessagesHandler();
|
||||||
|
|
||||||
|
void initApiCountryModelUpdateHandler();
|
||||||
|
void initContainerModelUpdateHandler();
|
||||||
|
void initAdminConfigRevokedHandler();
|
||||||
|
void initPassphraseRequestHandler();
|
||||||
|
void initTranslationsUpdatedHandler();
|
||||||
|
void initAutoConnectHandler();
|
||||||
|
void initAmneziaDnsToggledHandler();
|
||||||
|
void initPrepareConfigHandler();
|
||||||
|
|
||||||
|
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
|
QSharedPointer<QTranslator> m_translator;
|
||||||
|
|
||||||
|
#ifndef Q_OS_ANDROID
|
||||||
|
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||||
|
|
||||||
|
QScopedPointer<ConnectionController> m_connectionController;
|
||||||
|
QScopedPointer<FocusController> m_focusController;
|
||||||
|
QSharedPointer<PageController> m_pageController; // TODO
|
||||||
|
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;
|
||||||
|
|
||||||
|
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||||
|
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||||
|
|
||||||
|
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;
|
||||||
|
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CORECONTROLLER_H
|
303
client/core/controllers/gatewayController.cpp
Normal file
303
client/core/controllers/gatewayController.cpp
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
#include "gatewayController.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
#include "QBlockCipher.h"
|
||||||
|
#include "QRsa.h"
|
||||||
|
|
||||||
|
#include "amnezia_application.h"
|
||||||
|
#include "core/api/apiUtils.h"
|
||||||
|
#include "utilities.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
namespace configKey
|
||||||
|
{
|
||||||
|
constexpr char aesKey[] = "aes_key";
|
||||||
|
constexpr char aesIv[] = "aes_iv";
|
||||||
|
constexpr char aesSalt[] = "aes_salt";
|
||||||
|
|
||||||
|
constexpr char apiPayload[] = "api_payload";
|
||||||
|
constexpr char keyPayload[] = "key_payload";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent)
|
||||||
|
: QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment), m_requestTimeoutMsecs(requestTimeoutMsecs)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
|
||||||
|
|
||||||
|
QNetworkReply *reply;
|
||||||
|
reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
|
QList<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)) {
|
||||||
|
auto requestFunction = [&request, &responseBody](const QString &url) {
|
||||||
|
request.setUrl(url);
|
||||||
|
return amnApp->networkManager()->get(request);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
|
||||||
|
const QList<QSslError> &nestedSslErrors) {
|
||||||
|
responseBody = nestedReply->readAll();
|
||||||
|
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
|
||||||
|
sslErrors = nestedSslErrors;
|
||||||
|
reply = nestedReply;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
request.setUrl(endpoint.arg(m_gatewayEndpoint));
|
||||||
|
|
||||||
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||||
|
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||||
|
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
||||||
|
|
||||||
|
QJsonObject keyPayload;
|
||||||
|
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
||||||
|
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
||||||
|
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
||||||
|
|
||||||
|
QByteArray encryptedKeyPayload;
|
||||||
|
QByteArray encryptedApiPayload;
|
||||||
|
try {
|
||||||
|
QSimpleCrypto::QRsa rsa;
|
||||||
|
|
||||||
|
EVP_PKEY *publicKey = nullptr;
|
||||||
|
try {
|
||||||
|
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
QSimpleCrypto::QRsa rsa;
|
||||||
|
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
||||||
|
} catch (...) {
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error loading public key from environment variables";
|
||||||
|
return ErrorCode::ApiMissingAgwPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||||
|
EVP_PKEY_free(publicKey);
|
||||||
|
|
||||||
|
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||||
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error when encrypting the request body";
|
||||||
|
return ErrorCode::ApiConfigDecryptionError;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject requestBody;
|
||||||
|
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||||
|
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
QByteArray encryptedResponseBody = reply->readAll();
|
||||||
|
|
||||||
|
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, false)) {
|
||||||
|
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
||||||
|
request.setUrl(url);
|
||||||
|
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||||
|
};
|
||||||
|
|
||||||
|
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
||||||
|
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
||||||
|
encryptedResponseBody = nestedReply->readAll();
|
||||||
|
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
|
sslErrors = nestedSslErrors;
|
||||||
|
reply = nestedReply;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
|
reply->deleteLater();
|
||||||
|
if (errorCode) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error when decrypting the request body";
|
||||||
|
return ErrorCode::ApiConfigDecryptionError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList GatewayController::getProxyUrls()
|
||||||
|
{
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QList<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->networkManager()->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" << encryptedResponseBody;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||||
|
|
||||||
|
QStringList endpoints;
|
||||||
|
for (const auto &endpoint : endpointsArray) {
|
||||||
|
endpoints.push_back(endpoint.toString());
|
||||||
|
}
|
||||||
|
return endpoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
||||||
|
const QByteArray &iv, const QByteArray &salt)
|
||||||
|
{
|
||||||
|
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
qDebug() << "Timeout occurred";
|
||||||
|
return true;
|
||||||
|
} else if (responseBody.contains("html")) {
|
||||||
|
qDebug() << "The response contains an html tag";
|
||||||
|
return true;
|
||||||
|
} else if (checkEncryption) {
|
||||||
|
try {
|
||||||
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
|
||||||
|
} catch (...) {
|
||||||
|
qDebug() << "Failed to decrypt the data";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
|
||||||
|
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
||||||
|
{
|
||||||
|
QStringList proxyUrls = getProxyUrls();
|
||||||
|
std::random_device randomDevice;
|
||||||
|
std::mt19937 generator(randomDevice());
|
||||||
|
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
QByteArray responseBody;
|
||||||
|
|
||||||
|
for (const QString &proxyUrl : proxyUrls) {
|
||||||
|
qDebug() << "Go to the next endpoint";
|
||||||
|
reply->deleteLater(); // delete the previous reply
|
||||||
|
reply = requestFunction(endpoint.arg(proxyUrl));
|
||||||
|
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
if (!replyProcessingFunction(reply, sslErrors)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
client/core/controllers/gatewayController.h
Normal file
35
client/core/controllers/gatewayController.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#ifndef GATEWAYCONTROLLER_H
|
||||||
|
#define GATEWAYCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "core/defs.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
#include "platforms/ios/ios_controller.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class GatewayController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
|
||||||
|
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QStringList getProxyUrls();
|
||||||
|
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
||||||
|
const QByteArray &iv = "", const QByteArray &salt = "");
|
||||||
|
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||||
|
|
||||||
|
int m_requestTimeoutMsecs;
|
||||||
|
QString m_gatewayEndpoint;
|
||||||
|
bool m_isDevEnvironment = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // GATEWAYCONTROLLER_H
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -97,6 +95,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 +109,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,
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
#ifndef APIENUMS_H
|
|
||||||
#define APIENUMS_H
|
|
||||||
|
|
||||||
enum ApiConfigSources {
|
|
||||||
Telegram = 1,
|
|
||||||
AmneziaGateway
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // APIENUMS_H
|
|
|
@ -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;
|
||||||
|
@ -51,6 +52,7 @@ QString errorString(ErrorCode code) {
|
||||||
|
|
||||||
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
case (ErrorCode::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 +66,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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
35
client/core/qrCodeUtils.cpp
Normal file
35
client/core/qrCodeUtils.cpp
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#include "qrCodeUtils.h"
|
||||||
|
|
||||||
|
#include <QIODevice>
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
QList<QString> qrCodeUtils::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 << qrCodeUtils::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k);
|
||||||
|
|
||||||
|
QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
|
|
||||||
|
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW);
|
||||||
|
QString svg = QString::fromStdString(toSvgString(qr, 1));
|
||||||
|
chunks.append(svgToBase64(svg));
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString qrCodeUtils::svgToBase64(const QString &image)
|
||||||
|
{
|
||||||
|
return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data());
|
||||||
|
}
|
||||||
|
|
||||||
|
qrcodegen::QrCode qrCodeUtils::generateQrCode(const QByteArray &data)
|
||||||
|
{
|
||||||
|
return qrcodegen::QrCode::encodeText(data, qrcodegen::QrCode::Ecc::LOW);
|
||||||
|
}
|
17
client/core/qrCodeUtils.h
Normal file
17
client/core/qrCodeUtils.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef QRCODEUTILS_H
|
||||||
|
#define QRCODEUTILS_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "qrcodegen.hpp"
|
||||||
|
|
||||||
|
namespace qrCodeUtils
|
||||||
|
{
|
||||||
|
constexpr const qint16 qrMagicCode = 1984;
|
||||||
|
|
||||||
|
QList<QString> generateQrCodeImageSeries(const QByteArray &data);
|
||||||
|
qrcodegen::QrCode generateQrCode(const QByteArray &data);
|
||||||
|
QString svgToBase64(const QString &image);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QRCODEUTILS_H
|
|
@ -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,11 @@
|
||||||
<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>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/countriesFlags">
|
<qresource prefix="/countriesFlags">
|
||||||
<file>images/flagKit/ZW.svg</file>
|
<file>images/flagKit/ZW.svg</file>
|
||||||
|
|
499
client/ui/controllers/api/apiConfigsController.cpp
Normal file
499
client/ui/controllers/api/apiConfigsController.cpp
Normal file
|
@ -0,0 +1,499 @@
|
||||||
|
#include "apiConfigsController.h"
|
||||||
|
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QClipboard>
|
||||||
|
|
||||||
|
#include "amnezia_application.h"
|
||||||
|
#include "configurators/wireguard_configurator.h"
|
||||||
|
#include "core/api/apiDefs.h"
|
||||||
|
#include "core/api/apiUtils.h"
|
||||||
|
#include "core/controllers/gatewayController.h"
|
||||||
|
#include "core/qrCodeUtils.h"
|
||||||
|
#include "ui/controllers/systemController.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
namespace configKey
|
||||||
|
{
|
||||||
|
constexpr char cloak[] = "cloak";
|
||||||
|
constexpr char awg[] = "awg";
|
||||||
|
|
||||||
|
constexpr char apiEdnpoint[] = "api_endpoint";
|
||||||
|
constexpr char accessToken[] = "api_key";
|
||||||
|
constexpr char certificate[] = "certificate";
|
||||||
|
constexpr char publicKey[] = "public_key";
|
||||||
|
constexpr char protocol[] = "protocol";
|
||||||
|
|
||||||
|
constexpr char uuid[] = "installation_uuid";
|
||||||
|
constexpr char osVersion[] = "os_version";
|
||||||
|
constexpr char appVersion[] = "app_version";
|
||||||
|
|
||||||
|
constexpr char userCountryCode[] = "user_country_code";
|
||||||
|
constexpr char serverCountryCode[] = "server_country_code";
|
||||||
|
constexpr char serviceType[] = "service_type";
|
||||||
|
constexpr char serviceInfo[] = "service_info";
|
||||||
|
constexpr char serviceProtocol[] = "service_protocol";
|
||||||
|
|
||||||
|
constexpr char aesKey[] = "aes_key";
|
||||||
|
constexpr char aesIv[] = "aes_iv";
|
||||||
|
constexpr char aesSalt[] = "aes_salt";
|
||||||
|
|
||||||
|
constexpr char apiPayload[] = "api_payload";
|
||||||
|
constexpr char keyPayload[] = "key_payload";
|
||||||
|
|
||||||
|
constexpr char apiConfig[] = "api_config";
|
||||||
|
constexpr char authData[] = "auth_data";
|
||||||
|
|
||||||
|
constexpr char config[] = "config";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel,
|
||||||
|
const QSharedPointer<ApiServicesModel> &apiServicesModel,
|
||||||
|
const std::shared_ptr<Settings> &settings, QObject *parent)
|
||||||
|
: QObject(parent), m_serversModel(serversModel), m_apiServicesModel(apiServicesModel), m_settings(settings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName)
|
||||||
|
{
|
||||||
|
if (fileName.isEmpty()) {
|
||||||
|
emit errorOccurred(ErrorCode::PermissionsError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
|
|
||||||
|
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
|
||||||
|
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
|
QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString();
|
||||||
|
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||||
|
|
||||||
|
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||||
|
apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode);
|
||||||
|
apiPayload[configKey::serverCountryCode] = serverCountryCode;
|
||||||
|
apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType);
|
||||||
|
apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData);
|
||||||
|
|
||||||
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/native_config"), apiPayload, responseBody);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object();
|
||||||
|
QString nativeConfig = jsonConfig.value(configKey::config).toString();
|
||||||
|
nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
||||||
|
|
||||||
|
SystemController::saveFile(fileName, nativeConfig);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
|
||||||
|
{
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
|
|
||||||
|
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
|
||||||
|
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
|
QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString();
|
||||||
|
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||||
|
|
||||||
|
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||||
|
apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode);
|
||||||
|
apiPayload[configKey::serverCountryCode] = serverCountryCode;
|
||||||
|
apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType);
|
||||||
|
apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData);
|
||||||
|
|
||||||
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_native_config"), apiPayload, responseBody);
|
||||||
|
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiConfigsController::prepareVpnKeyExport()
|
||||||
|
{
|
||||||
|
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
|
||||||
|
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
|
auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString();
|
||||||
|
m_vpnKey = vpnKey;
|
||||||
|
|
||||||
|
vpnKey.replace("vpn://", "");
|
||||||
|
|
||||||
|
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(vpnKey.toUtf8());
|
||||||
|
|
||||||
|
emit vpnKeyExportReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiConfigsController::copyVpnKeyToClipboard()
|
||||||
|
{
|
||||||
|
auto clipboard = amnApp->getClipboard();
|
||||||
|
clipboard->setText(m_vpnKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiConfigsController::fillAvailableServices()
|
||||||
|
{
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
|
|
||||||
|
QJsonObject apiPayload;
|
||||||
|
apiPayload[configKey::osVersion] = QSysInfo::productType();
|
||||||
|
|
||||||
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/services"), apiPayload, responseBody);
|
||||||
|
if (errorCode == ErrorCode::NoError) {
|
||||||
|
if (!responseBody.contains("services")) {
|
||||||
|
errorCode = ErrorCode::ApiServicesMissingError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject data = QJsonDocument::fromJson(responseBody).object();
|
||||||
|
m_apiServicesModel->updateModel(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiConfigsController::importServiceFromGateway()
|
||||||
|
{
|
||||||
|
if (m_serversModel->isServerFromApiAlreadyExists(m_apiServicesModel->getCountryCode(), m_apiServicesModel->getSelectedServiceType(),
|
||||||
|
m_apiServicesModel->getSelectedServiceProtocol())) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiConfigAlreadyAdded);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
|
|
||||||
|
auto installationUuid = m_settings->getInstallationUuid(true);
|
||||||
|
auto userCountryCode = m_apiServicesModel->getCountryCode();
|
||||||
|
auto serviceType = m_apiServicesModel->getSelectedServiceType();
|
||||||
|
auto serviceProtocol = m_apiServicesModel->getSelectedServiceProtocol();
|
||||||
|
|
||||||
|
ApiPayloadData apiPayloadData = generateApiPayloadData(serviceProtocol);
|
||||||
|
|
||||||
|
QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData);
|
||||||
|
apiPayload[configKey::userCountryCode] = userCountryCode;
|
||||||
|
apiPayload[configKey::serviceType] = serviceType;
|
||||||
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
|
|
||||||
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody);
|
||||||
|
|
||||||
|
QJsonObject serverConfig;
|
||||||
|
if (errorCode == ErrorCode::NoError) {
|
||||||
|
fillServerConfig(serviceProtocol, apiPayloadData, responseBody, serverConfig);
|
||||||
|
|
||||||
|
QJsonObject apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
||||||
|
apiConfig.insert(configKey::userCountryCode, m_apiServicesModel->getCountryCode());
|
||||||
|
apiConfig.insert(configKey::serviceType, m_apiServicesModel->getSelectedServiceType());
|
||||||
|
apiConfig.insert(configKey::serviceProtocol, m_apiServicesModel->getSelectedServiceProtocol());
|
||||||
|
|
||||||
|
serverConfig.insert(configKey::apiConfig, apiConfig);
|
||||||
|
|
||||||
|
m_serversModel->addServer(serverConfig);
|
||||||
|
emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName()));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
||||||
|
bool reloadServiceConfig)
|
||||||
|
{
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
|
|
||||||
|
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||||
|
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
||||||
|
auto authData = serverConfig.value(configKey::authData).toObject();
|
||||||
|
|
||||||
|
auto installationUuid = m_settings->getInstallationUuid(true);
|
||||||
|
auto userCountryCode = apiConfig.value(configKey::userCountryCode).toString();
|
||||||
|
auto serviceType = apiConfig.value(configKey::serviceType).toString();
|
||||||
|
auto serviceProtocol = apiConfig.value(configKey::serviceProtocol).toString();
|
||||||
|
|
||||||
|
ApiPayloadData apiPayloadData = generateApiPayloadData(serviceProtocol);
|
||||||
|
|
||||||
|
QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData);
|
||||||
|
apiPayload[configKey::userCountryCode] = userCountryCode;
|
||||||
|
apiPayload[configKey::serviceType] = serviceType;
|
||||||
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
|
|
||||||
|
if (!newCountryCode.isEmpty()) {
|
||||||
|
apiPayload[configKey::serverCountryCode] = newCountryCode;
|
||||||
|
}
|
||||||
|
if (!authData.isEmpty()) {
|
||||||
|
apiPayload[configKey::authData] = authData;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody);
|
||||||
|
|
||||||
|
QJsonObject newServerConfig;
|
||||||
|
if (errorCode == ErrorCode::NoError) {
|
||||||
|
fillServerConfig(serviceProtocol, apiPayloadData, responseBody, newServerConfig);
|
||||||
|
|
||||||
|
QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject();
|
||||||
|
newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode));
|
||||||
|
newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType));
|
||||||
|
newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol));
|
||||||
|
newApiConfig.insert(apiDefs::key::vpnKey, apiConfig.value(apiDefs::key::vpnKey));
|
||||||
|
|
||||||
|
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
||||||
|
newServerConfig.insert(configKey::authData, authData);
|
||||||
|
|
||||||
|
m_serversModel->editServer(newServerConfig, serverIndex);
|
||||||
|
if (reloadServiceConfig) {
|
||||||
|
emit reloadServerFromApiFinished(tr("API config reloaded"));
|
||||||
|
} else if (newCountryName.isEmpty()) {
|
||||||
|
emit updateServerFromApiFinished();
|
||||||
|
} else {
|
||||||
|
emit changeApiCountryFinished(tr("Successfully changed the country of connection to %1").arg(newCountryName));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
|
||||||
|
{
|
||||||
|
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||||
|
auto installationUuid = m_settings->getInstallationUuid(true);
|
||||||
|
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(apiDefs::requestTimeoutMsecs);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
||||||
|
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
||||||
|
request.setUrl(endpoint);
|
||||||
|
|
||||||
|
QString protocol = serverConfig.value(configKey::protocol).toString();
|
||||||
|
|
||||||
|
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||||
|
|
||||||
|
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||||
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
|
|
||||||
|
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->post(request, requestBody);
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
reply->deleteLater();
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto apiResponseBody = reply->readAll();
|
||||||
|
reply->deleteLater();
|
||||||
|
fillServerConfig(protocol, apiPayloadData, apiResponseBody, serverConfig);
|
||||||
|
m_serversModel->editServer(serverConfig, serverIndex);
|
||||||
|
emit updateServerFromApiFinished();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiConfigsController::deactivateDevice()
|
||||||
|
{
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
|
|
||||||
|
auto serverIndex = m_serversModel->getProcessedServerIndex();
|
||||||
|
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
||||||
|
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
|
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString();
|
||||||
|
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||||
|
|
||||||
|
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||||
|
apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode);
|
||||||
|
apiPayload[configKey::serverCountryCode] = apiConfigObject.value(configKey::serverCountryCode);
|
||||||
|
apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType);
|
||||||
|
apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData);
|
||||||
|
apiPayload[configKey::uuid] = m_settings->getInstallationUuid(true);
|
||||||
|
|
||||||
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody);
|
||||||
|
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConfigObject.remove(config_key::containers);
|
||||||
|
m_serversModel->editServer(serverConfigObject, serverIndex);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiConfigsController::isConfigValid()
|
||||||
|
{
|
||||||
|
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||||
|
QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
||||||
|
auto configSource = apiUtils::getConfigSource(serverConfigObject);
|
||||||
|
|
||||||
|
if (configSource == apiDefs::ConfigSource::Telegram
|
||||||
|
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
|
m_serversModel->removeApiConfig(serverIndex);
|
||||||
|
return updateServiceFromTelegram(serverIndex);
|
||||||
|
} else if (configSource == apiDefs::ConfigSource::AmneziaGateway
|
||||||
|
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
|
return updateServiceFromGateway(serverIndex, "", "");
|
||||||
|
} else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) {
|
||||||
|
qDebug() << "attempt to update api config by expires_at event";
|
||||||
|
if (configSource == apiDefs::ConfigSource::Telegram) {
|
||||||
|
return updateServiceFromGateway(serverIndex, "", "");
|
||||||
|
} else {
|
||||||
|
m_serversModel->removeApiConfig(serverIndex);
|
||||||
|
return updateServiceFromTelegram(serverIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiConfigsController::ApiPayloadData ApiConfigsController::generateApiPayloadData(const QString &protocol)
|
||||||
|
{
|
||||||
|
ApiConfigsController::ApiPayloadData apiPayload;
|
||||||
|
if (protocol == configKey::cloak) {
|
||||||
|
apiPayload.certRequest = OpenVpnConfigurator::createCertRequest();
|
||||||
|
} else if (protocol == configKey::awg) {
|
||||||
|
auto connData = WireguardConfigurator::genClientKeys();
|
||||||
|
apiPayload.wireGuardClientPubKey = connData.clientPubKey;
|
||||||
|
apiPayload.wireGuardClientPrivKey = connData.clientPrivKey;
|
||||||
|
}
|
||||||
|
return apiPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject ApiConfigsController::fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData)
|
||||||
|
{
|
||||||
|
QJsonObject obj;
|
||||||
|
if (protocol == configKey::cloak) {
|
||||||
|
obj[configKey::certificate] = apiPayloadData.certRequest.request;
|
||||||
|
} else if (protocol == configKey::awg) {
|
||||||
|
obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[configKey::osVersion] = QSysInfo::productType();
|
||||||
|
obj[configKey::appVersion] = QString(APP_VERSION);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiConfigsController::fillServerConfig(const QString &protocol, const ApiPayloadData &apiPayloadData,
|
||||||
|
const QByteArray &apiResponseBody, QJsonObject &serverConfig)
|
||||||
|
{
|
||||||
|
QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString();
|
||||||
|
|
||||||
|
data.replace("vpn://", "");
|
||||||
|
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
|
|
||||||
|
if (ba.isEmpty()) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray ba_uncompressed = qUncompress(ba);
|
||||||
|
if (!ba_uncompressed.isEmpty()) {
|
||||||
|
ba = ba_uncompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString configStr = ba;
|
||||||
|
if (protocol == configKey::cloak) {
|
||||||
|
configStr.replace("<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() == apiDefs::ConfigSource::AmneziaGateway) {
|
||||||
|
serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion);
|
||||||
|
serverConfig[config_key::description] = newServerConfig.value(config_key::description);
|
||||||
|
serverConfig[config_key::name] = newServerConfig.value(config_key::name);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString();
|
||||||
|
serverConfig[config_key::defaultContainer] = defaultContainer;
|
||||||
|
|
||||||
|
QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap();
|
||||||
|
map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap());
|
||||||
|
auto apiConfig = QJsonObject::fromVariantMap(map);
|
||||||
|
|
||||||
|
if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) {
|
||||||
|
apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConfig[configKey::apiConfig] = apiConfig;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QString> ApiConfigsController::getQrCodes()
|
||||||
|
{
|
||||||
|
return m_qrCodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApiConfigsController::getQrCodesCount()
|
||||||
|
{
|
||||||
|
return m_qrCodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ApiConfigsController::getVpnKey()
|
||||||
|
{
|
||||||
|
return m_vpnKey;
|
||||||
|
}
|
73
client/ui/controllers/api/apiConfigsController.h
Normal file
73
client/ui/controllers/api/apiConfigsController.h
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
#ifndef APICONFIGSCONTROLLER_H
|
||||||
|
#define APICONFIGSCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "configurators/openvpn_configurator.h"
|
||||||
|
#include "ui/models/api/apiServicesModel.h"
|
||||||
|
#include "ui/models/servers_model.h"
|
||||||
|
|
||||||
|
class ApiConfigsController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ApiConfigsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiServicesModel> &apiServicesModel,
|
||||||
|
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
Q_PROPERTY(QList<QString> qrCodes READ getQrCodes NOTIFY vpnKeyExportReady)
|
||||||
|
Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY vpnKeyExportReady)
|
||||||
|
Q_PROPERTY(QString vpnKey READ getVpnKey NOTIFY vpnKeyExportReady)
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName);
|
||||||
|
bool revokeNativeConfig(const QString &serverCountryCode);
|
||||||
|
// bool exportVpnKey(const QString &fileName);
|
||||||
|
void prepareVpnKeyExport();
|
||||||
|
void copyVpnKeyToClipboard();
|
||||||
|
|
||||||
|
bool fillAvailableServices();
|
||||||
|
bool importServiceFromGateway();
|
||||||
|
bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
||||||
|
bool reloadServiceConfig = false);
|
||||||
|
bool updateServiceFromTelegram(const int serverIndex);
|
||||||
|
bool deactivateDevice();
|
||||||
|
|
||||||
|
bool isConfigValid();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void errorOccurred(ErrorCode errorCode);
|
||||||
|
|
||||||
|
void installServerFromApiFinished(const QString &message);
|
||||||
|
void changeApiCountryFinished(const QString &message);
|
||||||
|
void reloadServerFromApiFinished(const QString &message);
|
||||||
|
void updateServerFromApiFinished();
|
||||||
|
|
||||||
|
void vpnKeyExportReady();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ApiPayloadData
|
||||||
|
{
|
||||||
|
OpenVpnConfigurator::ConnectionData certRequest;
|
||||||
|
|
||||||
|
QString wireGuardClientPrivKey;
|
||||||
|
QString wireGuardClientPubKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
||||||
|
QJsonObject fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData);
|
||||||
|
void fillServerConfig(const QString &protocol, const ApiPayloadData &apiPayloadData, const QByteArray &apiResponseBody,
|
||||||
|
QJsonObject &serverConfig);
|
||||||
|
|
||||||
|
QList<QString> getQrCodes();
|
||||||
|
int getQrCodesCount();
|
||||||
|
QString getVpnKey();
|
||||||
|
|
||||||
|
QList<QString> m_qrCodes;
|
||||||
|
QString m_vpnKey;
|
||||||
|
|
||||||
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
|
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APICONFIGSCONTROLLER_H
|
85
client/ui/controllers/api/apiSettingsController.cpp
Normal file
85
client/ui/controllers/api/apiSettingsController.cpp
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
#include "apiSettingsController.h"
|
||||||
|
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "core/api/apiUtils.h"
|
||||||
|
#include "core/controllers/gatewayController.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
namespace configKey
|
||||||
|
{
|
||||||
|
constexpr char userCountryCode[] = "user_country_code";
|
||||||
|
constexpr char serverCountryCode[] = "server_country_code";
|
||||||
|
constexpr char serviceType[] = "service_type";
|
||||||
|
constexpr char serviceInfo[] = "service_info";
|
||||||
|
|
||||||
|
constexpr char apiConfig[] = "api_config";
|
||||||
|
constexpr char authData[] = "auth_data";
|
||||||
|
}
|
||||||
|
|
||||||
|
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel,
|
||||||
|
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
||||||
|
const QSharedPointer<ApiCountryModel> &apiCountryModel,
|
||||||
|
const std::shared_ptr<Settings> &settings, QObject *parent)
|
||||||
|
: QObject(parent),
|
||||||
|
m_serversModel(serversModel),
|
||||||
|
m_apiAccountInfoModel(apiAccountInfoModel),
|
||||||
|
m_apiCountryModel(apiCountryModel),
|
||||||
|
m_settings(settings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiSettingsController::~ApiSettingsController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiSettingsController::getAccountInfo(bool reload)
|
||||||
|
{
|
||||||
|
if (reload) {
|
||||||
|
QEventLoop wait;
|
||||||
|
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
|
||||||
|
wait.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs);
|
||||||
|
|
||||||
|
auto processedIndex = m_serversModel->getProcessedServerIndex();
|
||||||
|
auto serverConfig = m_serversModel->getServerConfig(processedIndex);
|
||||||
|
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
||||||
|
auto authData = serverConfig.value(configKey::authData).toObject();
|
||||||
|
|
||||||
|
QJsonObject apiPayload;
|
||||||
|
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
|
||||||
|
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
|
||||||
|
apiPayload[configKey::authData] = authData;
|
||||||
|
|
||||||
|
QByteArray responseBody;
|
||||||
|
|
||||||
|
if (apiUtils::getConfigType(serverConfig) == apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject accountInfo = QJsonDocument::fromJson(responseBody).object();
|
||||||
|
m_apiAccountInfoModel->updateModel(accountInfo, serverConfig);
|
||||||
|
|
||||||
|
if (reload) {
|
||||||
|
updateApiCountryModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiSettingsController::updateApiCountryModel()
|
||||||
|
{
|
||||||
|
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
|
||||||
|
m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo());
|
||||||
|
}
|
34
client/ui/controllers/api/apiSettingsController.h
Normal file
34
client/ui/controllers/api/apiSettingsController.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef APISETTINGSCONTROLLER_H
|
||||||
|
#define APISETTINGSCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "ui/models/api/apiAccountInfoModel.h"
|
||||||
|
#include "ui/models/api/apiCountryModel.h"
|
||||||
|
#include "ui/models/servers_model.h"
|
||||||
|
|
||||||
|
class ApiSettingsController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
||||||
|
const QSharedPointer<ApiCountryModel> &apiCountryModel, const std::shared_ptr<Settings> &settings,
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
~ApiSettingsController();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
bool getAccountInfo(bool reload);
|
||||||
|
void updateApiCountryModel();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void errorOccurred(ErrorCode errorCode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
|
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||||
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
|
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APISETTINGSCONTROLLER_H
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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());
|
||||||
|
|
||||||
|
|
|
@ -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);
|
return true;
|
||||||
if (errorCode != ErrorCode::NoError) {
|
}
|
||||||
emit installationErrorOccurred(errorCode);
|
|
||||||
|
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
|
emit noInstalledContainers();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject data = QJsonDocument::fromJson(responseBody).object();
|
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
||||||
m_apiServicesModel->updateModel(data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InstallController::installServiceFromApi()
|
if (container == DockerContainer::None) {
|
||||||
{
|
emit installationErrorOccurred(ErrorCode::NoInstalledContainersError);
|
||||||
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());
|
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
||||||
QJsonObject serverConfig;
|
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
|
||||||
|
|
||||||
ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(),
|
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
||||||
m_apiServicesModel->getSelectedServiceType(),
|
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
||||||
m_apiServicesModel->getSelectedServiceProtocol(), "", QJsonObject(), serverConfig);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
emit installationErrorOccurred(errorCode);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto serviceInfo = m_apiServicesModel->getSelectedServiceInfo();
|
QFutureWatcher<ErrorCode> watcher;
|
||||||
QJsonObject apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
|
||||||
apiConfig.insert(configKey::serviceInfo, serviceInfo);
|
|
||||||
apiConfig.insert(configKey::userCountryCode, m_apiServicesModel->getCountryCode());
|
|
||||||
apiConfig.insert(configKey::serviceType, m_apiServicesModel->getSelectedServiceType());
|
|
||||||
apiConfig.insert(configKey::serviceProtocol, m_apiServicesModel->getSelectedServiceProtocol());
|
|
||||||
|
|
||||||
serverConfig.insert(configKey::apiConfig, apiConfig);
|
QFuture<ErrorCode> future = QtConcurrent::run([this, container, &credentials, &containerConfig, &serverController]() {
|
||||||
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
m_serversModel->addServer(serverConfig);
|
auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) {
|
||||||
emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName()));
|
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
|
||||||
return true;
|
QString protocolConfig =
|
||||||
}
|
containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString();
|
||||||
|
|
||||||
bool InstallController::updateServiceFromApi(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
if (protocolConfig.isEmpty()) {
|
||||||
bool reloadServiceConfig)
|
return false;
|
||||||
{
|
}
|
||||||
ApiController apiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv());
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
if (!isProtocolConfigExists(containerConfig, container)) {
|
||||||
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
|
||||||
auto authData = serverConfig.value(configKey::authData).toObject();
|
errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container, containerConfig);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
m_serversModel->updateContainerConfig(container, containerConfig);
|
||||||
|
|
||||||
QJsonObject newServerConfig;
|
errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig,
|
||||||
ErrorCode errorCode = apiController.getConfigForService(
|
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
|
||||||
m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(),
|
if (errorCode != ErrorCode::NoError) {
|
||||||
apiConfig.value(configKey::serviceType).toString(), apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode,
|
return errorCode;
|
||||||
authData, newServerConfig);
|
}
|
||||||
if (errorCode != ErrorCode::NoError) {
|
}
|
||||||
emit installationErrorOccurred(errorCode);
|
return errorCode;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject();
|
|
||||||
newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode));
|
|
||||||
newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType));
|
|
||||||
newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol));
|
|
||||||
|
|
||||||
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
|
||||||
newServerConfig.insert(configKey::authData, authData);
|
|
||||||
m_serversModel->editServer(newServerConfig, serverIndex);
|
|
||||||
|
|
||||||
if (reloadServiceConfig) {
|
|
||||||
emit reloadServerFromApiFinished(tr("API config reloaded"));
|
|
||||||
} else if (newCountryName.isEmpty()) {
|
|
||||||
emit updateServerFromApiFinished();
|
|
||||||
} else {
|
|
||||||
emit changeApiCountryFinished(tr("Successfully changed the country of connection to %1").arg(newCountryName));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InstallController::updateServiceFromTelegram(const int serverIndex)
|
|
||||||
{
|
|
||||||
ApiController *apiController = new ApiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv());
|
|
||||||
|
|
||||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
|
||||||
|
|
||||||
apiController->updateServerConfigFromApi(m_settings->getInstallationUuid(true), serverIndex, serverConfig);
|
|
||||||
connect(apiController, &ApiController::finished, this, [this, apiController](const QJsonObject &config, const int serverIndex) {
|
|
||||||
m_serversModel->editServer(config, serverIndex);
|
|
||||||
emit updateServerFromApiFinished();
|
|
||||||
apiController->deleteLater();
|
|
||||||
});
|
});
|
||||||
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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,11 @@ namespace PageLoader
|
||||||
PageSettingsLogging,
|
PageSettingsLogging,
|
||||||
PageSettingsSplitTunneling,
|
PageSettingsSplitTunneling,
|
||||||
PageSettingsAppSplitTunneling,
|
PageSettingsAppSplitTunneling,
|
||||||
|
PageSettingsApiServerInfo,
|
||||||
|
PageSettingsApiAvailableCountries,
|
||||||
|
PageSettingsApiSupport,
|
||||||
|
PageSettingsApiInstructions,
|
||||||
|
PageSettingsApiNativeConfigs,
|
||||||
|
|
||||||
PageServiceSftpSettings,
|
PageServiceSftpSettings,
|
||||||
PageServiceTorWebsiteSettings,
|
PageServiceTorWebsiteSettings,
|
||||||
|
@ -53,7 +58,7 @@ namespace PageLoader
|
||||||
PageProtocolOpenVpnSettings,
|
PageProtocolOpenVpnSettings,
|
||||||
PageProtocolShadowSocksSettings,
|
PageProtocolShadowSocksSettings,
|
||||||
PageProtocolCloakSettings,
|
PageProtocolCloakSettings,
|
||||||
PageProtocolXraySettings,
|
PageProtocolXraySettings,
|
||||||
PageProtocolWireGuardSettings,
|
PageProtocolWireGuardSettings,
|
||||||
PageProtocolAwgSettings,
|
PageProtocolAwgSettings,
|
||||||
PageProtocolIKev2Settings,
|
PageProtocolIKev2Settings,
|
||||||
|
@ -104,7 +109,7 @@ public slots:
|
||||||
int incrementDrawerDepth();
|
int incrementDrawerDepth();
|
||||||
int decrementDrawerDepth();
|
int decrementDrawerDepth();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onShowErrorMessage(amnezia::ErrorCode errorCode);
|
void onShowErrorMessage(amnezia::ErrorCode errorCode);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
129
client/ui/models/api/apiAccountInfoModel.cpp
Normal file
129
client/ui/models/api/apiAccountInfoModel.cpp
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
#include "apiAccountInfoModel.h"
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include "core/api/apiUtils.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
Logger logger("AccountInfoModel");
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiAccountInfoModel::ApiAccountInfoModel(QObject *parent) : QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApiAccountInfoModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case SubscriptionStatusRole: {
|
||||||
|
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
||||||
|
return tr("Active");
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active");
|
||||||
|
}
|
||||||
|
case EndDateRole: {
|
||||||
|
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return QDateTime::fromString(m_accountInfoData.subscriptionEndDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
|
||||||
|
}
|
||||||
|
case ConnectedDevicesRole: {
|
||||||
|
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount);
|
||||||
|
}
|
||||||
|
case ServiceDescriptionRole: {
|
||||||
|
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||||
|
return tr("Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to 200 "
|
||||||
|
"Mb/s");
|
||||||
|
} else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
||||||
|
return tr("Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and "
|
||||||
|
"more. YouTube is not included in the free plan.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case IsComponentVisibleRole: {
|
||||||
|
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
AccountInfoData accountInfoData;
|
||||||
|
|
||||||
|
m_availableCountries = accountInfoObject.value(apiDefs::key::availableCountries).toArray();
|
||||||
|
m_issuedConfigsInfo = accountInfoObject.value(apiDefs::key::issuedConfigs).toArray();
|
||||||
|
|
||||||
|
accountInfoData.activeDeviceCount = accountInfoObject.value(apiDefs::key::activeDeviceCount).toInt();
|
||||||
|
accountInfoData.maxDeviceCount = accountInfoObject.value(apiDefs::key::maxDeviceCount).toInt();
|
||||||
|
accountInfoData.subscriptionEndDate = accountInfoObject.value(apiDefs::key::subscriptionEndDate).toString();
|
||||||
|
|
||||||
|
accountInfoData.configType = apiUtils::getConfigType(serverConfig);
|
||||||
|
|
||||||
|
m_accountInfoData = accountInfoData;
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ApiAccountInfoModel::data(const QString &roleString)
|
||||||
|
{
|
||||||
|
QModelIndex modelIndex = index(0);
|
||||||
|
auto roles = roleNames();
|
||||||
|
for (auto it = roles.begin(); it != roles.end(); it++) {
|
||||||
|
if (QString(it.value()) == roleString) {
|
||||||
|
return data(modelIndex, it.key());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray ApiAccountInfoModel::getAvailableCountries()
|
||||||
|
{
|
||||||
|
return m_availableCountries;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray ApiAccountInfoModel::getIssuedConfigsInfo()
|
||||||
|
{
|
||||||
|
return m_issuedConfigsInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ApiAccountInfoModel::getTelegramBotLink()
|
||||||
|
{
|
||||||
|
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
||||||
|
return tr("amnezia_free_support_bot");
|
||||||
|
} else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||||
|
return tr("amnezia_premium_support_bot");
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
roles[SubscriptionStatusRole] = "subscriptionStatus";
|
||||||
|
roles[EndDateRole] = "endDate";
|
||||||
|
roles[ConnectedDevicesRole] = "connectedDevices";
|
||||||
|
roles[ServiceDescriptionRole] = "serviceDescription";
|
||||||
|
roles[IsComponentVisibleRole] = "isComponentVisible";
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
}
|
55
client/ui/models/api/apiAccountInfoModel.h
Normal file
55
client/ui/models/api/apiAccountInfoModel.h
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
#ifndef APIACCOUNTINFOMODEL_H
|
||||||
|
#define APIACCOUNTINFOMODEL_H
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include "core/api/apiDefs.h"
|
||||||
|
|
||||||
|
class ApiAccountInfoModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Roles {
|
||||||
|
SubscriptionStatusRole = Qt::UserRole + 1,
|
||||||
|
ConnectedDevicesRole,
|
||||||
|
ServiceDescriptionRole,
|
||||||
|
EndDateRole,
|
||||||
|
IsComponentVisibleRole
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit ApiAccountInfoModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig);
|
||||||
|
QVariant data(const QString &roleString);
|
||||||
|
|
||||||
|
QJsonArray getAvailableCountries();
|
||||||
|
QJsonArray getIssuedConfigsInfo();
|
||||||
|
QString getTelegramBotLink();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct AccountInfoData
|
||||||
|
{
|
||||||
|
QString subscriptionEndDate;
|
||||||
|
int activeDeviceCount;
|
||||||
|
int maxDeviceCount;
|
||||||
|
|
||||||
|
apiDefs::ConfigType configType;
|
||||||
|
};
|
||||||
|
|
||||||
|
AccountInfoData m_accountInfoData;
|
||||||
|
QJsonArray m_availableCountries;
|
||||||
|
QJsonArray m_issuedConfigsInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APIACCOUNTINFOMODEL_H
|
118
client/ui/models/api/apiCountryModel.cpp
Normal file
118
client/ui/models/api/apiCountryModel.cpp
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
#include "apiCountryModel.h"
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include "core/api/apiDefs.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
Logger logger("ApiCountryModel");
|
||||||
|
|
||||||
|
constexpr QLatin1String countryConfig("country_config");
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiCountryModel::ApiCountryModel(QObject *parent) : QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApiCountryModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
return m_countries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
CountryInfo countryInfo = m_countries.at(index.row());
|
||||||
|
IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.value(countryInfo.countryCode);
|
||||||
|
bool isIssued = issuedConfigInfo.sourceType == countryConfig;
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case CountryCodeRole: {
|
||||||
|
return countryInfo.countryCode;
|
||||||
|
}
|
||||||
|
case CountryNameRole: {
|
||||||
|
return countryInfo.countryName;
|
||||||
|
}
|
||||||
|
case CountryImageCodeRole: {
|
||||||
|
return countryInfo.countryCode.toUpper();
|
||||||
|
}
|
||||||
|
case IsIssuedRole: {
|
||||||
|
return isIssued;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiCountryModel::updateModel(const QJsonArray &countries, const QString ¤tCountryCode)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
m_countries.clear();
|
||||||
|
for (int i = 0; i < countries.size(); i++) {
|
||||||
|
CountryInfo countryInfo;
|
||||||
|
QJsonObject countryObject = countries.at(i).toObject();
|
||||||
|
|
||||||
|
countryInfo.countryName = countryObject.value(apiDefs::key::serverCountryName).toString();
|
||||||
|
countryInfo.countryCode = countryObject.value(apiDefs::key::serverCountryCode).toString();
|
||||||
|
|
||||||
|
if (countryInfo.countryCode == currentCountryCode) {
|
||||||
|
m_currentIndex = i;
|
||||||
|
emit currentIndexChanged(m_currentIndex);
|
||||||
|
}
|
||||||
|
m_countries.push_back(countryInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiCountryModel::updateIssuedConfigsInfo(const QJsonArray &issuedConfigs)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
m_issuedConfigs.clear();
|
||||||
|
for (int i = 0; i < issuedConfigs.size(); i++) {
|
||||||
|
IssuedConfigInfo issuedConfigInfo;
|
||||||
|
QJsonObject issuedConfigObject = issuedConfigs.at(i).toObject();
|
||||||
|
|
||||||
|
if (issuedConfigObject.value(apiDefs::key::sourceType).toString() != countryConfig) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
issuedConfigInfo.installationUuid = issuedConfigObject.value(apiDefs::key::installationUuid).toString();
|
||||||
|
issuedConfigInfo.workerLastUpdated = issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString();
|
||||||
|
issuedConfigInfo.lastDownloaded = issuedConfigObject.value(apiDefs::key::lastDownloaded).toString();
|
||||||
|
issuedConfigInfo.sourceType = issuedConfigObject.value(apiDefs::key::sourceType).toString();
|
||||||
|
issuedConfigInfo.osVersion = issuedConfigObject.value(apiDefs::key::osVersion).toString();
|
||||||
|
|
||||||
|
m_issuedConfigs.insert(issuedConfigObject.value(apiDefs::key::serverCountryCode).toString(), issuedConfigInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApiCountryModel::getCurrentIndex()
|
||||||
|
{
|
||||||
|
return m_currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiCountryModel::setCurrentIndex(const int i)
|
||||||
|
{
|
||||||
|
m_currentIndex = i;
|
||||||
|
emit currentIndexChanged(m_currentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> ApiCountryModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
roles[CountryNameRole] = "countryName";
|
||||||
|
roles[CountryCodeRole] = "countryCode";
|
||||||
|
roles[CountryImageCodeRole] = "countryImageCode";
|
||||||
|
roles[IsIssuedRole] = "isIssued";
|
||||||
|
return roles;
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
#define APICOUNTRYMODEL_H
|
#define APICOUNTRYMODEL_H
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
#include <QHash>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
|
||||||
class ApiCountryModel : public QAbstractListModel
|
class ApiCountryModel : public QAbstractListModel
|
||||||
|
@ -12,7 +13,8 @@ public:
|
||||||
enum Roles {
|
enum Roles {
|
||||||
CountryNameRole = Qt::UserRole + 1,
|
CountryNameRole = Qt::UserRole + 1,
|
||||||
CountryCodeRole,
|
CountryCodeRole,
|
||||||
CountryImageCodeRole
|
CountryImageCodeRole,
|
||||||
|
IsIssuedRole
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ApiCountryModel(QObject *parent = nullptr);
|
explicit ApiCountryModel(QObject *parent = nullptr);
|
||||||
|
@ -24,7 +26,8 @@ public:
|
||||||
Q_PROPERTY(int currentIndex READ getCurrentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
|
Q_PROPERTY(int currentIndex READ getCurrentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateModel(const QJsonArray &data, const QString ¤tCountryCode);
|
void updateModel(const QJsonArray &countries, const QString ¤tCountryCode);
|
||||||
|
void updateIssuedConfigsInfo(const QJsonArray &issuedConfigs);
|
||||||
|
|
||||||
int getCurrentIndex();
|
int getCurrentIndex();
|
||||||
void setCurrentIndex(const int i);
|
void setCurrentIndex(const int i);
|
||||||
|
@ -36,7 +39,23 @@ protected:
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QJsonArray m_countries;
|
struct IssuedConfigInfo
|
||||||
|
{
|
||||||
|
QString installationUuid;
|
||||||
|
QString workerLastUpdated;
|
||||||
|
QString lastDownloaded;
|
||||||
|
QString sourceType;
|
||||||
|
QString osVersion;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CountryInfo
|
||||||
|
{
|
||||||
|
QString countryName;
|
||||||
|
QString countryCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
QVector<CountryInfo> m_countries;
|
||||||
|
QHash<QString, IssuedConfigInfo> m_issuedConfigs;
|
||||||
int m_currentIndex;
|
int m_currentIndex;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 ¤tCountryCode)
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
|
|
||||||
m_countries = data;
|
|
||||||
for (int i = 0; i < m_countries.size(); i++) {
|
|
||||||
if (m_countries.at(i).toObject().value(configKey::serverCountryCode).toString() == currentCountryCode) {
|
|
||||||
m_currentIndex = i;
|
|
||||||
emit currentIndexChanged(m_currentIndex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
int ApiCountryModel::getCurrentIndex()
|
|
||||||
{
|
|
||||||
return m_currentIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiCountryModel::setCurrentIndex(const int i)
|
|
||||||
{
|
|
||||||
m_currentIndex = i;
|
|
||||||
emit currentIndexChanged(m_currentIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> ApiCountryModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[CountryNameRole] = "countryName";
|
|
||||||
roles[CountryCodeRole] = "countryCode";
|
|
||||||
roles[CountryImageCodeRole] = "countryImageCode";
|
|
||||||
return roles;
|
|
||||||
}
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
55
client/ui/qml/Components/RenameServerDrawer.qml
Normal file
55
client/ui/qml/Components/RenameServerDrawer.qml
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
|
||||||
|
import "../Config"
|
||||||
|
|
||||||
|
DrawerType2 {
|
||||||
|
property string serverNameText
|
||||||
|
|
||||||
|
id: root
|
||||||
|
objectName: "serverNameEditDrawer"
|
||||||
|
|
||||||
|
expandedStateContent: ColumnLayout {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.topMargin: 32
|
||||||
|
anchors.leftMargin: 16
|
||||||
|
anchors.rightMargin: 16
|
||||||
|
|
||||||
|
TextFieldWithHeaderType {
|
||||||
|
id: serverName
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
headerText: qsTr("Server name")
|
||||||
|
textField.text: root.serverNameText
|
||||||
|
textField.maximumLength: 30
|
||||||
|
checkEmptyText: true
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
id: saveButton
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Save")
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
if (serverName.textField.text === "") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverName.textField.text !== root.serverNameText) {
|
||||||
|
ServersModel.setProcessedServerData("name", serverName.textField.text);
|
||||||
|
}
|
||||||
|
root.closeTriggered()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,7 +110,24 @@ ListView {
|
||||||
|
|
||||||
onClicked: function() {
|
onClicked: function() {
|
||||||
ServersModel.processedIndex = index
|
ServersModel.processedIndex = index
|
||||||
PageController.goToPage(PageEnum.PageSettingsServerInfo)
|
|
||||||
|
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
|
||||||
|
if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
|
||||||
|
} else {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
let result = ApiSettingsController.getAccountInfo(false)
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
if (!result) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsServerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
drawer.closeTriggered()
|
drawer.closeTriggered()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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\"")
|
||||||
|
|
38
client/ui/qml/Controls2/ListViewType.qml
Normal file
38
client/ui/qml/Controls2/ListViewType.qml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool isFocusable: true
|
||||||
|
|
||||||
|
Keys.onTabPressed: {
|
||||||
|
FocusController.nextKeyTabItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onBacktabPressed: {
|
||||||
|
FocusController.previousKeyTabItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onUpPressed: {
|
||||||
|
FocusController.nextKeyUpItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onDownPressed: {
|
||||||
|
FocusController.nextKeyDownItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onLeftPressed: {
|
||||||
|
FocusController.nextKeyLeftItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onRightPressed: {
|
||||||
|
FocusController.nextKeyRightItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBarType {}
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
reuseItems: true
|
||||||
|
snapMode: ListView.SnapToItem
|
||||||
|
}
|
|
@ -297,7 +297,23 @@ PageType {
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
ServersModel.processedIndex = ServersModel.defaultIndex
|
ServersModel.processedIndex = ServersModel.defaultIndex
|
||||||
PageController.goToPage(PageEnum.PageSettingsServerInfo)
|
|
||||||
|
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
|
||||||
|
if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
|
||||||
|
} else {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
let result = ApiSettingsController.getAccountInfo(false)
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
if (!result) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsServerInfo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,22 +17,85 @@ import "../Components"
|
||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
ListView {
|
property var processedServer
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ServersModel
|
||||||
|
|
||||||
|
function onProcessedServerChanged() {
|
||||||
|
root.processedServer = proxyServersModel.get(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SortFilterProxyModel {
|
||||||
|
id: proxyServersModel
|
||||||
|
objectName: "proxyServersModel"
|
||||||
|
|
||||||
|
sourceModel: ServersModel
|
||||||
|
filters: [
|
||||||
|
ValueFilter {
|
||||||
|
roleName: "isCurrentlyProcessed"
|
||||||
|
value: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.processedServer = proxyServersModel.get(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
id: menuContent
|
id: menuContent
|
||||||
|
|
||||||
property bool isFocusable: true
|
anchors.fill: parent
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
interactive: true
|
|
||||||
model: ApiCountryModel
|
model: ApiCountryModel
|
||||||
|
|
||||||
|
currentIndex: 0
|
||||||
|
|
||||||
ButtonGroup {
|
ButtonGroup {
|
||||||
id: containersRadioButtonGroup
|
id: containersRadioButtonGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
width: menuContent.width
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
|
objectName: "backButton"
|
||||||
|
|
||||||
|
Layout.topMargin: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderType {
|
||||||
|
id: headerContent
|
||||||
|
objectName: "headerContent"
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.bottomMargin: 10
|
||||||
|
|
||||||
|
actionButtonImage: "qrc:/images/controls/settings.svg"
|
||||||
|
|
||||||
|
headerText: root.processedServer.name
|
||||||
|
descriptionText: qsTr("Locations for connection")
|
||||||
|
|
||||||
|
actionButtonFunction: function() {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
let result = ApiSettingsController.getAccountInfo(false)
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
if (!result) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
delegate: ColumnLayout {
|
||||||
id: content
|
id: content
|
||||||
|
|
||||||
|
@ -63,9 +128,10 @@ PageType {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
var prevIndex = ApiCountryModel.currentIndex
|
var prevIndex = ApiCountryModel.currentIndex
|
||||||
ApiCountryModel.currentIndex = index
|
ApiCountryModel.currentIndex = index
|
||||||
if (!InstallController.updateServiceFromApi(ServersModel.defaultIndex, countryCode, countryName)) {
|
if (!ApiConfigsController.updateServiceFromGateway(ServersModel.defaultIndex, countryCode, countryName)) {
|
||||||
ApiCountryModel.currentIndex = prevIndex
|
ApiCountryModel.currentIndex = prevIndex
|
||||||
}
|
}
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
125
client/ui/qml/Pages2/PageSettingsApiInstructions.qml
Normal file
125
client/ui/qml/Pages2/PageSettingsApiInstructions.qml
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
import "../Components"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: windows
|
||||||
|
|
||||||
|
readonly property string title: qsTr("Windows")
|
||||||
|
readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: macos
|
||||||
|
|
||||||
|
readonly property string title: qsTr("macOS")
|
||||||
|
readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#macos")
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: android
|
||||||
|
|
||||||
|
readonly property string title: qsTr("Android")
|
||||||
|
readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#android")
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: androidTv
|
||||||
|
|
||||||
|
readonly property string title: qsTr("AndroidTV")
|
||||||
|
readonly property string link: qsTr("https://docs.amnezia.org/ru/documentation/instructions/android_tv_connect/")
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: ios
|
||||||
|
|
||||||
|
readonly property string title: qsTr("iOS")
|
||||||
|
readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#ios")
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: linux
|
||||||
|
|
||||||
|
readonly property string title: qsTr("Linux")
|
||||||
|
readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#linux")
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: routers
|
||||||
|
|
||||||
|
readonly property string title: qsTr("Routers")
|
||||||
|
readonly property string link: qsTr("https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#routers")
|
||||||
|
}
|
||||||
|
|
||||||
|
property list<QtObject> instructionsModel: [
|
||||||
|
windows,
|
||||||
|
macos,
|
||||||
|
android,
|
||||||
|
androidTv,
|
||||||
|
ios,
|
||||||
|
linux,
|
||||||
|
routers
|
||||||
|
]
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 20
|
||||||
|
anchors.bottomMargin: 24
|
||||||
|
|
||||||
|
model: instructionsModel
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderType {
|
||||||
|
id: header
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
|
headerText: qsTr("How to connect on another device")
|
||||||
|
descriptionText: qsTr("Instructions on the Amnezia website")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
id: telegramButton
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 6
|
||||||
|
|
||||||
|
text: title
|
||||||
|
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
Qt.openUrlExternally(link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
211
client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml
Normal file
211
client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
|
import QtCore
|
||||||
|
|
||||||
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
import "../Components"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string configExtension: ".conf"
|
||||||
|
property string configCaption: qsTr("Save AmneziaVPN config")
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 20
|
||||||
|
anchors.bottomMargin: 24
|
||||||
|
|
||||||
|
model: ApiCountryModel
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderType {
|
||||||
|
id: header
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
|
headerText: qsTr("Configuration files")
|
||||||
|
descriptionText: qsTr("To connect a router or AmneziaWG application")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 6
|
||||||
|
|
||||||
|
text: countryName
|
||||||
|
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
||||||
|
rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
if (isIssued) {
|
||||||
|
moreOptionsDrawer.countryName = countryName
|
||||||
|
moreOptionsDrawer.countryCode = countryCode
|
||||||
|
moreOptionsDrawer.openTriggered()
|
||||||
|
} else {
|
||||||
|
issueConfig(countryCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawerType2 {
|
||||||
|
id: moreOptionsDrawer
|
||||||
|
|
||||||
|
property string countryName
|
||||||
|
property string countryCode
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
expandedHeight: parent.height * 0.4375
|
||||||
|
|
||||||
|
expandedStateContent: Item {
|
||||||
|
implicitHeight: moreOptionsDrawer.expandedHeight
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: moreOptionsDrawerBackButton
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.topMargin: 16
|
||||||
|
|
||||||
|
backButtonFunction: function() {
|
||||||
|
moreOptionsDrawer.closeTriggered()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlickableType {
|
||||||
|
anchors.top: moreOptionsDrawerBackButton.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
|
contentHeight: moreOptionsDrawerContent.height
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: moreOptionsDrawerContent
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
Header2Type {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: 16
|
||||||
|
|
||||||
|
headerText: qsTr("Configuration file ") + moreOptionsDrawer.countryName
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Create a new")
|
||||||
|
descriptionText: qsTr("The previously created one will stop working")
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
showQuestion(true, moreOptionsDrawer.countryCode, moreOptionsDrawer.countryName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("Revoke the current configuration file")
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
showQuestion(false, moreOptionsDrawer.countryCode, moreOptionsDrawer.countryName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function issueConfig(countryCode) {
|
||||||
|
var fileName = ""
|
||||||
|
if (GC.isMobile()) {
|
||||||
|
fileName = countryCode + configExtension
|
||||||
|
} else {
|
||||||
|
fileName = SystemController.getFileName(configCaption,
|
||||||
|
qsTr("Config files (*" + configExtension + ")"),
|
||||||
|
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + countryCode,
|
||||||
|
true,
|
||||||
|
configExtension)
|
||||||
|
}
|
||||||
|
if (fileName !== "") {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
let result = ApiConfigsController.exportNativeConfig(countryCode, fileName)
|
||||||
|
if (result) {
|
||||||
|
ApiSettingsController.getAccountInfo(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
if (result) {
|
||||||
|
PageController.showNotificationMessage(qsTr("Config file saved"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function revokeConfig(countryCode) {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
let result = ApiConfigsController.revokeNativeConfig(countryCode)
|
||||||
|
if (result) {
|
||||||
|
ApiSettingsController.getAccountInfo(true)
|
||||||
|
}
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
PageController.showNotificationMessage(qsTr("The config has been revoked"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showQuestion(isConfigIssue, countryCode, countryName) {
|
||||||
|
var headerText = qsTr("Revoke the actual %1 configuration file?").arg(countryName)
|
||||||
|
var descriptionText = qsTr("The previously created file will no longer be valid. It will not be possible to connect using it.")
|
||||||
|
var yesButtonText = qsTr("Continue")
|
||||||
|
var noButtonText = qsTr("Cancel")
|
||||||
|
|
||||||
|
var yesButtonFunction = function() {
|
||||||
|
if (isConfigIssue) {
|
||||||
|
issueConfig(countryCode)
|
||||||
|
} else {
|
||||||
|
revokeConfig(countryCode)
|
||||||
|
}
|
||||||
|
moreOptionsDrawer.closeTriggered()
|
||||||
|
}
|
||||||
|
var noButtonFunction = function() {
|
||||||
|
}
|
||||||
|
|
||||||
|
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
@ -16,28 +18,19 @@ PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property list<QtObject> labelsModel: [
|
property list<QtObject> labelsModel: [
|
||||||
regionObject,
|
statusObject,
|
||||||
priceObject,
|
|
||||||
endDateObject,
|
endDateObject,
|
||||||
speedObject
|
deviceCountObject
|
||||||
]
|
]
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: regionObject
|
id: statusObject
|
||||||
|
|
||||||
readonly property string title: qsTr("For the region")
|
readonly property string title: qsTr("Subscription status")
|
||||||
readonly property string contentKey: "region"
|
readonly property string contentKey: "subscriptionStatus"
|
||||||
readonly property string objectImageSource: "qrc:/images/controls/map-pin.svg"
|
readonly property string objectImageSource: "qrc:/images/controls/map-pin.svg"
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: priceObject
|
|
||||||
|
|
||||||
readonly property string title: qsTr("Price")
|
|
||||||
readonly property string contentKey: "price"
|
|
||||||
readonly property string objectImageSource: "qrc:/images/controls/tag.svg"
|
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: endDateObject
|
id: endDateObject
|
||||||
|
|
||||||
|
@ -47,116 +40,204 @@ PageType {
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: speedObject
|
id: deviceCountObject
|
||||||
|
|
||||||
readonly property string title: qsTr("Speed")
|
readonly property string title: qsTr("Connected devices")
|
||||||
readonly property string contentKey: "speed"
|
readonly property string contentKey: "connectedDevices"
|
||||||
readonly property string objectImageSource: "qrc:/images/controls/gauge.svg"
|
readonly property string objectImageSource: "qrc:/images/controls/gauge.svg"
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
property var processedServer
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ServersModel
|
||||||
|
|
||||||
|
function onProcessedServerChanged() {
|
||||||
|
root.processedServer = proxyServersModel.get(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SortFilterProxyModel {
|
||||||
|
id: proxyServersModel
|
||||||
|
objectName: "proxyServersModel"
|
||||||
|
|
||||||
|
sourceModel: ServersModel
|
||||||
|
filters: [
|
||||||
|
ValueFilter {
|
||||||
|
roleName: "isCurrentlyProcessed"
|
||||||
|
value: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.processedServer = proxyServersModel.get(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
property bool isFocusable: true
|
|
||||||
|
|
||||||
Keys.onTabPressed: {
|
|
||||||
FocusController.nextKeyTabItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onBacktabPressed: {
|
|
||||||
FocusController.previousKeyTabItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onUpPressed: {
|
|
||||||
FocusController.nextKeyUpItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onDownPressed: {
|
|
||||||
FocusController.nextKeyDownItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onLeftPressed: {
|
|
||||||
FocusController.nextKeyLeftItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onRightPressed: {
|
|
||||||
FocusController.nextKeyRightItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBarType {}
|
|
||||||
|
|
||||||
model: labelsModel
|
model: labelsModel
|
||||||
clip: true
|
|
||||||
reuseItems: true
|
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 {
|
delegate: ColumnLayout {
|
||||||
width: listView.width
|
width: listView.width
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ApiAccountInfoModel
|
||||||
|
|
||||||
|
function onModelReset() {
|
||||||
|
delegateItem.rightText = ApiAccountInfoModel.data(contentKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LabelWithImageType {
|
LabelWithImageType {
|
||||||
|
id: delegateItem
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: 16
|
Layout.margins: 16
|
||||||
|
|
||||||
imageSource: objectImageSource
|
imageSource: objectImageSource
|
||||||
leftText: title
|
leftText: title
|
||||||
rightText: ApiServicesModel.getSelectedServiceData(contentKey)
|
rightText: ApiAccountInfoModel.data(contentKey)
|
||||||
|
|
||||||
visible: rightText !== ""
|
visible: rightText !== ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: ColumnLayout {
|
footer: ColumnLayout {
|
||||||
|
id: footer
|
||||||
|
|
||||||
width: listView.width
|
width: listView.width
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
ParagraphTextType {
|
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
id: vpnKey
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.rightMargin: 16
|
Layout.topMargin: 32
|
||||||
Layout.leftMargin: 16
|
|
||||||
|
|
||||||
onLinkActivated: function(link) {
|
visible: false //footer.isVisibleForAmneziaFree
|
||||||
Qt.openUrlExternally(link)
|
|
||||||
}
|
|
||||||
textFormat: Text.RichText
|
|
||||||
text: {
|
|
||||||
var text = ApiServicesModel.getSelectedServiceData("features")
|
|
||||||
if (text === undefined) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return text.replace("%1", LanguageModel.getCurrentSiteUrl())
|
|
||||||
}
|
|
||||||
|
|
||||||
visible: text !== ""
|
text: qsTr("Subscription key")
|
||||||
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
MouseArea {
|
clickedFunction: function() {
|
||||||
anchors.fill: parent
|
shareConnectionDrawer.headerText = qsTr("Amnezia Premium subscription key")
|
||||||
acceptedButtons: Qt.NoButton
|
|
||||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
shareConnectionDrawer.openTriggered()
|
||||||
|
shareConnectionDrawer.isSelfHostedConfig = false;
|
||||||
|
shareConnectionDrawer.shareButtonText = qsTr("Save VPN key to file")
|
||||||
|
shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key")
|
||||||
|
|
||||||
|
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
|
||||||
|
ApiConfigsController.prepareVpnKeyExport()
|
||||||
|
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DividerType {
|
||||||
|
visible: false //footer.isVisibleForAmneziaFree
|
||||||
|
}
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
id: supportUuid
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 32
|
||||||
|
|
||||||
text: qsTr("Support tag")
|
visible: footer.isVisibleForAmneziaFree
|
||||||
descriptionText: SettingsController.getInstallationUuid()
|
|
||||||
|
|
||||||
descriptionOnTop: true
|
text: qsTr("Configuration files")
|
||||||
|
|
||||||
rightImageSource: "qrc:/images/controls/copy.svg"
|
descriptionText: qsTr("To connect a router or AmneziaWG application")
|
||||||
rightImageColor: AmneziaStyle.color.paleGray
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
clickedFunction: function() {
|
clickedFunction: function() {
|
||||||
GC.copyToClipBoard(descriptionText)
|
ApiSettingsController.updateApiCountryModel()
|
||||||
PageController.showNotificationMessage(qsTr("Copied"))
|
PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs)
|
||||||
if (!GC.isMobile()) {
|
|
||||||
this.rightButton.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -182,15 +263,51 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 yesButtonText = qsTr("Continue")
|
||||||
|
var noButtonText = qsTr("Cancel")
|
||||||
|
|
||||||
|
var yesButtonFunction = function() {
|
||||||
|
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||||
|
PageController.showNotificationMessage(qsTr("The next time the “Connect” button is pressed, the device will be activated again"))
|
||||||
|
} else {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
if (ApiConfigsController.deactivateDevice()) {
|
||||||
|
ApiSettingsController.getAccountInfo(true)
|
||||||
|
}
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var noButtonFunction = function() {
|
||||||
|
}
|
||||||
|
|
||||||
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
}
|
}
|
||||||
|
@ -220,14 +337,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)
|
||||||
InstallController.removeProcessedServer()
|
if (ApiConfigsController.deactivateDevice()) {
|
||||||
|
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)
|
||||||
|
@ -235,4 +351,10 @@ PageType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShareConnectionDrawer {
|
||||||
|
id: shareConnectionDrawer
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
109
client/ui/qml/Pages2/PageSettingsApiSupport.qml
Normal file
109
client/ui/qml/Pages2/PageSettingsApiSupport.qml
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
import "../Components"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: backButtonLayout
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
anchors.topMargin: 20
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HeaderType {
|
||||||
|
id: header
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
|
headerText: qsTr("Support")
|
||||||
|
descriptionText: qsTr("Our technical support specialists are ready to help you at any time")
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
readonly property string telegramBotLink: ApiAccountInfoModel.getTelegramBotLink()
|
||||||
|
|
||||||
|
text: qsTr("Telegram")
|
||||||
|
descriptionText: "@" + telegramBotLink
|
||||||
|
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
Qt.openUrlExternally("https://t.me/" + telegramBotLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Mail")
|
||||||
|
descriptionText: qsTr("support@amnezia.org")
|
||||||
|
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
Qt.openUrlExternally(qsTr("mailto:support@amnezia.org"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Site")
|
||||||
|
descriptionText: qsTr("amnezia.org")
|
||||||
|
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
id: supportUuid
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Support tag")
|
||||||
|
descriptionText: SettingsController.getInstallationUuid()
|
||||||
|
|
||||||
|
descriptionOnTop: true
|
||||||
|
|
||||||
|
rightImageSource: "qrc:/images/controls/copy.svg"
|
||||||
|
rightImageColor: AmneziaStyle.color.paleGray
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
GC.copyToClipBoard(descriptionText)
|
||||||
|
PageController.showNotificationMessage(qsTr("Copied"))
|
||||||
|
if (!GC.isMobile()) {
|
||||||
|
this.rightButton.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
serverNameEditDrawer.openTriggered()
|
||||||
nestedStackView.currentIndex = root.pageSettingsApiServerInfo
|
|
||||||
} else {
|
|
||||||
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")
|
|
||||||
textField.text: root.processedServer.name
|
|
||||||
textField.maximumLength: 30
|
|
||||||
checkEmptyText: true
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicButtonType {
|
|
||||||
id: saveButton
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: qsTr("Save")
|
|
||||||
|
|
||||||
clickedFunc: function() {
|
|
||||||
if (serverName.textField.text === "") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serverName.textField.text !== root.processedServer.name) {
|
|
||||||
ServersModel.setProcessedServerData("name", serverName.textField.text);
|
|
||||||
}
|
|
||||||
serverNameEditDrawer.closeTriggered()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,19 @@ PageType {
|
||||||
|
|
||||||
clickedFunction: function() {
|
clickedFunction: function() {
|
||||||
ServersModel.processedIndex = index
|
ServersModel.processedIndex = index
|
||||||
PageController.goToPage(PageEnum.PageSettingsServerInfo)
|
|
||||||
|
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
let result = ApiSettingsController.getAccountInfo(false)
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
if (!result) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
|
||||||
|
} else {
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsServerInfo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,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)
|
||||||
|
|
|
@ -46,7 +46,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 = false
|
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
PageController.showNotificationMessage(message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeApiCountryFinished(message) {
|
function onNoInstalledContainers() {
|
||||||
PageController.showBusyIndicator(false)
|
PageController.setTriggeredByConnectButton(true)
|
||||||
|
|
||||||
PageController.goToPageHome()
|
ServersModel.processedIndex = ServersModel.getDefaultServerIndex()
|
||||||
PageController.showNotificationMessage(message)
|
InstallController.setShouldCreateServer(false)
|
||||||
}
|
PageController.goToPage(PageEnum.PageSetupWizardEasy)
|
||||||
|
|
||||||
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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue