mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-06 18:41:55 +03:00
Compare commits
11 Commits
fix/extend
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
594635e5cf | ||
|
|
f9b106cf5b | ||
|
|
a9861d18b7 | ||
|
|
c14138f031 | ||
|
|
60686fde24 | ||
|
|
bd0747296e | ||
|
|
ba61019a50 | ||
|
|
113f967006 | ||
|
|
bcee58b08b | ||
|
|
52de1acebf | ||
|
|
027a12a1df |
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
@@ -23,6 +23,9 @@ jobs:
|
||||
- 'recipes/**'
|
||||
- 'conanfile.py'
|
||||
- '.github/workflows/deploy.yml'
|
||||
- 'cmake/conan_provider.cmake'
|
||||
- 'cmake/platform_settings.cmake'
|
||||
- 'cmake/recipes_bootstrap.cmake'
|
||||
|
||||
Bake-Prebuilts-Linux:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -917,3 +920,4 @@ jobs:
|
||||
run: |
|
||||
echo "Pull request:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.0)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.2)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
@@ -18,9 +18,9 @@ project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
|
||||
# trigger conan to kick off `conan install` globally
|
||||
find_package(OpenSSL REQUIRED)
|
||||
if (PREBUILTS_ONLY)
|
||||
# trigger conan to kick off `conan install`
|
||||
find_package(OpenSSL REQUIRED)
|
||||
return()
|
||||
endif()
|
||||
|
||||
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2122)
|
||||
set(APP_ANDROID_VERSION_CODE 2123)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -193,10 +193,6 @@ elseif(APPLE)
|
||||
include(cmake/macos.cmake)
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
|
||||
|
||||
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
|
||||
@@ -216,11 +212,32 @@ endif()
|
||||
|
||||
install(TARGETS ${PROJECT}
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
RUNTIME_DEPENDENCY_SET client_deps
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
|
||||
if(APPLE)
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR}/AmneziaVPN.app/Contents/Frameworks)
|
||||
else()
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
install(RUNTIME_DEPENDENCY_SET client_deps
|
||||
PRE_EXCLUDE_REGEXES
|
||||
[[api-ms-win-.*]]
|
||||
[[ext-ms-.*]]
|
||||
[[kernel32\.dll]]
|
||||
[[hvsifiletrust\.dll]]
|
||||
[[libc\.so\..*]] [[libgcc_s\.so\..*]] [[libm\.so\..*]] [[libstdc\+\+\.so\..*]]
|
||||
[[.*\.framework]]
|
||||
[[^[Qq]t.*]]
|
||||
POST_EXCLUDE_REGEXES
|
||||
[[^.*[\\/]system32[\\/].*\.dll$]]
|
||||
[[^/lib.*]]
|
||||
[[^/usr/lib.*]]
|
||||
DIRECTORIES ${CONAN_RUNTIME_LIB_DIRS}
|
||||
COMPONENT AmneziaVPN
|
||||
DESTINATION "${RUNTIME_DEPS_DIR}"
|
||||
)
|
||||
|
||||
set(deploy_tool_options "")
|
||||
|
||||
@@ -54,7 +54,6 @@ target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QEventLoop>
|
||||
#include <QFutureWatcher>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QPromise>
|
||||
#include <QSet>
|
||||
#include <QSysInfo>
|
||||
@@ -216,7 +217,8 @@ ErrorCode SubscriptionController::executeRequest(const QString &endpoint, const
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData)
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
CaptchaInfo &captchaInfo)
|
||||
{
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
@@ -233,6 +235,19 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||
|
||||
if (errorCode == ErrorCode::ApiCaptchaRequiredError) {
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (jsonDoc.isObject()) {
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
captchaInfo.captchaId = jsonObj.value("captcha_id").toString();
|
||||
captchaInfo.captchaImageBase64 = jsonObj.value("captcha_image").toString();
|
||||
captchaInfo.hint = jsonObj.value("hint").toString();
|
||||
captchaInfo.isRequired = true;
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -242,9 +257,9 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody);
|
||||
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
@@ -956,3 +971,74 @@ QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(const
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::resolveImportServiceCaptcha(const QString &userCountryCode,
|
||||
const QString &serviceType,
|
||||
const QString &serviceProtocol,
|
||||
const ProtocolData &protocolData,
|
||||
const QString &captchaId,
|
||||
const QString &captchaSolution,
|
||||
CaptchaInfo *retryCaptchaOut)
|
||||
{
|
||||
GatewayRequestData gatewayRequestData{QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
userCountryCode,
|
||||
"",
|
||||
serviceType,
|
||||
serviceProtocol,
|
||||
QJsonObject()};
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
|
||||
apiPayload["captcha_id"] = captchaId;
|
||||
QString normalizedSolution;
|
||||
normalizedSolution.reserve(captchaSolution.size());
|
||||
for (const QChar &ch : captchaSolution) {
|
||||
const ushort u = ch.unicode();
|
||||
if (u >= '0' && u <= '9') {
|
||||
normalizedSolution += ch;
|
||||
} else if (u >= 0xFF10 && u <= 0xFF19) {
|
||||
normalizedSolution += QChar(static_cast<char16_t>(u - 0xFF10 + '0'));
|
||||
}
|
||||
}
|
||||
apiPayload["captcha_solution"] = normalizedSolution.isEmpty() ? captchaSolution.trimmed() : normalizedSolution;
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
if (retryCaptchaOut
|
||||
&& (errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|
||||
|| errorCode == ErrorCode::ApiCaptchaRequiredError)) {
|
||||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (jsonDoc.isObject()) {
|
||||
const QJsonObject jsonObj = jsonDoc.object();
|
||||
if (jsonObj.contains(QStringLiteral("captcha_id")) && jsonObj.contains(QStringLiteral("captcha_image"))) {
|
||||
retryCaptchaOut->captchaId = jsonObj.value(QStringLiteral("captcha_id")).toString();
|
||||
retryCaptchaOut->captchaImageBase64 = jsonObj.value(QStringLiteral("captcha_image")).toString();
|
||||
retryCaptchaOut->hint = jsonObj.value(QStringLiteral("hint")).toString();
|
||||
retryCaptchaOut->isRequired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonObject serverConfigJson;
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody);
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(serverConfigJson);
|
||||
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,13 @@ public:
|
||||
QJsonObject toJsonObject() const;
|
||||
};
|
||||
|
||||
struct CaptchaInfo {
|
||||
QString captchaId;
|
||||
QString captchaImageBase64;
|
||||
QString hint;
|
||||
bool isRequired = false;
|
||||
};
|
||||
|
||||
explicit SubscriptionController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository);
|
||||
|
||||
@@ -49,7 +56,8 @@ public:
|
||||
void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload);
|
||||
|
||||
ErrorCode importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData);
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
CaptchaInfo &captchaInfo);
|
||||
ErrorCode importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &email);
|
||||
|
||||
@@ -98,6 +106,11 @@ public:
|
||||
AppStoreRestoreResult processAppStoreRestore(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol);
|
||||
|
||||
ErrorCode resolveImportServiceCaptcha(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
const QString &captchaId, const QString &captchaSolution,
|
||||
CaptchaInfo *retryCaptchaOut = nullptr);
|
||||
|
||||
private:
|
||||
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
|
||||
bool isApiKeyExpired(const QString &serverId) const;
|
||||
|
||||
@@ -49,14 +49,92 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
|
||||
{
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::isConnectionSupported(const QString &serverId) const
|
||||
{
|
||||
if (serverId.isEmpty()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
if (serverConfigUtils::isLegacyApiSubscription(m_serversRepository->serverKind(serverId))) {
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
}
|
||||
|
||||
DockerContainer container = DockerContainer::None;
|
||||
const ErrorCode errorCode = defaultContainerForServer(serverId, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
return ErrorCode::NoInstalledContainersError;
|
||||
}
|
||||
|
||||
if (ContainerUtils::isUnsupportedContainer(container)) {
|
||||
return ErrorCode::LegacyContainerNotSupportedError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
{
|
||||
ContainerConfig containerConfigModel;
|
||||
QPair<QString, QString> dns;
|
||||
QString hostName;
|
||||
@@ -120,10 +198,6 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
|
||||
containerConfigModel, container);
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ public:
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container);
|
||||
|
||||
ErrorCode isConnectionSupported(const QString &serverId) const;
|
||||
|
||||
ErrorCode openConnection(const QString &serverId);
|
||||
|
||||
void closeConnection();
|
||||
@@ -73,6 +75,8 @@ signals:
|
||||
#endif
|
||||
|
||||
private:
|
||||
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
VpnConnection* m_vpnConnection;
|
||||
|
||||
@@ -191,7 +191,7 @@ void CoreController::initControllers()
|
||||
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
|
||||
setQmlContextProperty("LanguageUiController", m_languageUiController);
|
||||
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
|
||||
setQmlContextProperty("SettingsController", m_settingsUiController);
|
||||
|
||||
m_pageController = new PageController(m_serversController, m_settingsController, this);
|
||||
@@ -213,8 +213,8 @@ void CoreController::initControllers()
|
||||
setQmlContextProperty("SystemController", m_systemController);
|
||||
|
||||
m_networkReachabilityController = new NetworkReachabilityController(this);
|
||||
m_engine->rootContext()->setContextProperty("NetworkReachabilityController", m_networkReachabilityController);
|
||||
m_engine->rootContext()->setContextProperty("NetworkReachability", m_networkReachabilityController);
|
||||
setQmlContextProperty("NetworkReachabilityController", m_networkReachabilityController);
|
||||
setQmlContextProperty("NetworkReachability", m_networkReachabilityController);
|
||||
|
||||
m_servicesCatalogUiController = new ServicesCatalogUiController(m_servicesCatalogController, m_apiServicesModel, this);
|
||||
setQmlContextProperty("ServicesCatalogUiController", m_servicesCatalogUiController);
|
||||
|
||||
@@ -82,33 +82,11 @@
|
||||
#endif
|
||||
|
||||
class CoreSignalHandlers;
|
||||
class TestMultipleImports;
|
||||
class TestAdminSelfHostedExport;
|
||||
class TestServerEdit;
|
||||
class TestDefaultServerChange;
|
||||
class TestServerEdgeCases;
|
||||
class TestSignalOrder;
|
||||
class TestServersModelSync;
|
||||
class TestComplexOperations;
|
||||
class TestSettingsSignals;
|
||||
class TestUiServersModelAndController;
|
||||
class TestSelfHostedServerSetup;
|
||||
|
||||
class CoreController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class CoreSignalHandlers;
|
||||
friend class TestMultipleImports;
|
||||
friend class TestAdminSelfHostedExport;
|
||||
friend class TestServerEdit;
|
||||
friend class TestDefaultServerChange;
|
||||
friend class TestServerEdgeCases;
|
||||
friend class TestSignalOrder;
|
||||
friend class TestServersModelSync;
|
||||
friend class TestComplexOperations;
|
||||
friend class TestSettingsSignals;
|
||||
friend class TestUiServersModelAndController;
|
||||
friend class TestSelfHostedServerSetup;
|
||||
|
||||
public:
|
||||
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
@@ -125,6 +103,36 @@ signals:
|
||||
void translationsUpdated();
|
||||
void websiteUrlChanged(const QString &newUrl);
|
||||
|
||||
protected:
|
||||
SecureServersRepository* serversRepositoryProtected() const { return m_serversRepository; }
|
||||
SecureAppSettingsRepository* appSettingsRepositoryProtected() const { return m_appSettingsRepository; }
|
||||
ServersModel* serversModelProtected() const { return m_serversModel; }
|
||||
ContainersModel* containersModelProtected() const { return m_containersModel; }
|
||||
ApiServicesModel* apiServicesModelProtected() const { return m_apiServicesModel; }
|
||||
NewsModel* newsModelProtected() const { return m_newsModel; }
|
||||
AllowedDnsModel* allowedDnsModelProtected() const { return m_allowedDnsModel; }
|
||||
AppSplitTunnelingModel* appSplitTunnelingModelProtected() const { return m_appSplitTunnelingModel; }
|
||||
IpSplitTunnelingModel* ipSplitTunnelingModelProtected() const { return m_ipSplitTunnelingModel; }
|
||||
LanguageModel* languageModelProtected() const { return m_languageModel; }
|
||||
|
||||
InstallUiController* installUiControllerProtected() const { return m_installUiController; }
|
||||
ImportController* importCoreControllerProtected() const { return m_importCoreController; }
|
||||
ExportController* exportControllerProtected() const { return m_exportController; }
|
||||
InstallController* installControllerProtected() const { return m_installController; }
|
||||
ServersController* serversControllerProtected() const { return m_serversController; }
|
||||
SettingsUiController* settingsUiControllerProtected() const { return m_settingsUiController; }
|
||||
SettingsController* settingsControllerProtected() const { return m_settingsController; }
|
||||
AllowedDnsUiController* allowedDnsUiControllerProtected() const { return m_allowedDnsUiController; }
|
||||
AllowedDnsController* allowedDnsControllerProtected() const { return m_allowedDnsController; }
|
||||
LanguageUiController* languageUiControllerProtected() const { return m_languageUiController; }
|
||||
IpSplitTunnelingController* ipSplitTunnelingControllerProtected() const { return m_ipSplitTunnelingController; }
|
||||
IpSplitTunnelingUiController* ipSplitTunnelingUiControllerProtected() const { return m_ipSplitTunnelingUiController; }
|
||||
AppSplitTunnelingController* appSplitTunnelingControllerProtected() const { return m_appSplitTunnelingController; }
|
||||
AppSplitTunnelingUiController* appSplitTunnelingUiControllerProtected() const { return m_appSplitTunnelingUiController; }
|
||||
ServersUiController* serversUiControllerProtected() const { return m_serversUiController; }
|
||||
ServicesCatalogUiController* servicesCatalogUiControllerProtected() const { return m_servicesCatalogUiController; }
|
||||
ApiNewsUiController* apiNewsUiControllerProtected() const { return m_apiNewsUiController; }
|
||||
|
||||
private:
|
||||
void initRepositories();
|
||||
void initCoreControllers();
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/controllers/api/apiNewsUiController.h"
|
||||
#include "ui/models/api/apiCountryModel.h"
|
||||
#include "ui/models/containersModel.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
@@ -156,15 +155,17 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
void CoreSignalHandlers::initImportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
|
||||
if (!m_coreController->m_connectionController->isConnected()) {
|
||||
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
if (m_coreController->m_connectionUiController->isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -176,17 +177,14 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
if (processedServerId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray availableCountries;
|
||||
QString serverCountryCode;
|
||||
|
||||
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
|
||||
if (apiV2.has_value()) {
|
||||
availableCountries = apiV2->apiConfig.availableCountries;
|
||||
serverCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
if (!apiV2.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
|
||||
apiV2->apiConfig.serverCountryCode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -237,13 +235,16 @@ void CoreSignalHandlers::initLanguageHandler()
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
|
||||
});
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::appLanguageChanged, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->onAppLanguageChanged(m_coreController->m_settingsController->getAppLanguage());
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAutoConnectHandler()
|
||||
{
|
||||
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
|
||||
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +349,9 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
|
||||
{
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initStrictKillSwitchHandler()
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace
|
||||
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
|
||||
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
|
||||
constexpr QLatin1String errorResponsePattern3("Account not found.");
|
||||
constexpr QLatin1String errorResponsePatternQrSessionNotFound("QR session not found");
|
||||
constexpr QLatin1String errorResponsePatternSessionNotFound("Session not found");
|
||||
|
||||
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
|
||||
|
||||
@@ -37,6 +39,7 @@ namespace
|
||||
constexpr int httpStatusCodeConflict = 409;
|
||||
constexpr int httpStatusCodeNotImplemented = 501;
|
||||
constexpr int httpStatusCodePaymentRequired = 402;
|
||||
constexpr int httpStatusCodeRequestTimeout = 408;
|
||||
constexpr int httpStatusCodeUnprocessableEntity = 422;
|
||||
|
||||
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
||||
@@ -206,8 +209,9 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
|
||||
}
|
||||
|
||||
auto errorCode =
|
||||
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
|
||||
responseBody = decryptionResult.decryptedBody;
|
||||
const auto errorCode =
|
||||
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, responseBody);
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -217,7 +221,6 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
responseBody = decryptionResult.decryptedBody;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -256,7 +259,7 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode,
|
||||
decryptionResult.decryptedBody);
|
||||
if (errorCode) {
|
||||
promise->addResult(qMakePair(errorCode, QByteArray()));
|
||||
promise->addResult(qMakePair(errorCode, decryptionResult.decryptedBody));
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
@@ -459,15 +462,19 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
|
||||
qDebug() << "the response contains an html tag";
|
||||
return true;
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeRequestTimeout) {
|
||||
return false;
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeNotFound) {
|
||||
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||
|| responseBody.contains(errorResponsePattern3)) {
|
||||
|| responseBody.contains(errorResponsePattern3) || responseBody.contains(errorResponsePatternQrSessionNotFound)
|
||||
|| responseBody.contains(errorResponsePatternSessionNotFound)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeNotImplemented) {
|
||||
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||
return false;
|
||||
|
||||
@@ -486,7 +486,7 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data) const
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = configKey::amneziaOpenvpn;
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
||||
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data);
|
||||
@@ -645,7 +645,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = containerName;
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp(
|
||||
"DNS = "
|
||||
@@ -699,7 +699,7 @@ QJsonObject ImportController::extractXrayConfig(const QString &data, ConfigTypes
|
||||
? configKey::amneziaSsxray
|
||||
: configKey::amneziaXray;
|
||||
if (description.isEmpty()) {
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
} else {
|
||||
config[configKey::description] = description;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,16 @@ namespace
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString buildRemoveContainerScript(const amnezia::ScriptVars &vars, bool removeDataVolume)
|
||||
{
|
||||
QString script = SshSession::replaceVars(amnezia::scriptData(SharedScriptType::remove_container), vars);
|
||||
if (removeDataVolume) {
|
||||
script += QLatin1String("\nsudo docker volume rm -f $CONTAINER_NAME-data 2>/dev/null || true");
|
||||
script = SshSession::replaceVars(script, vars);
|
||||
}
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
InstallController::InstallController(SecureServersRepository *serversRepository,
|
||||
@@ -120,14 +130,10 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return e;
|
||||
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
|
||||
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
if (!isUpdate) {
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
}
|
||||
sshSession.runScript(credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
||||
removeContainerVars));
|
||||
const bool removeDataVolume = !isUpdate && (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
|
||||
|
||||
qDebug().noquote() << "buildContainerWorker start";
|
||||
@@ -152,8 +158,8 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return startupContainerWorker(credentials, container, config, sshSession);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
{
|
||||
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
@@ -185,7 +191,7 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
SshSession sshSession(this);
|
||||
|
||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
|
||||
|
||||
bool xrayServerSettingsChanged = false;
|
||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||
@@ -213,11 +219,11 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
|
||||
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
|
||||
XrayConfigurator xrayConfigurator(&sshSession);
|
||||
qDebug() << "InstallController::updateContainer applying Xray server inbound sync, reinstall="
|
||||
qDebug() << "InstallController::updateServerConfig applying Xray server inbound sync, reinstall="
|
||||
<< reinstallRequired;
|
||||
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
qDebug() << "InstallController::updateContainer Xray inbound sync failed, error="
|
||||
qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
}
|
||||
}
|
||||
@@ -236,6 +242,41 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig)
|
||||
{
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
auto config = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
auto config = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedUser);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
auto config = m_serversRepository->nativeConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::Native);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
|
||||
{
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
||||
@@ -358,7 +399,7 @@ void InstallController::addEmptyServer(const ServerCredentials &credentials)
|
||||
serverConfig.userName = credentials.userName;
|
||||
serverConfig.password = credentials.secretData;
|
||||
serverConfig.port = credentials.port;
|
||||
serverConfig.description = m_appSettingsRepository->nextAvailableServerName();
|
||||
serverConfig.description = m_serversRepository->nextAvailableServerName();
|
||||
serverConfig.displayName = serverConfig.description.isEmpty() ? serverConfig.hostName : serverConfig.description;
|
||||
serverConfig.defaultContainer = DockerContainer::None;
|
||||
|
||||
@@ -980,12 +1021,11 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
ErrorCode errorCode = sshSession.runScript(
|
||||
credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars));
|
||||
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
ErrorCode errorCode =
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
|
||||
@@ -1170,7 +1210,7 @@ ErrorCode InstallController::installServer(const ServerCredentials &credentials,
|
||||
serverConfig.userName = credentials.userName;
|
||||
serverConfig.password = credentials.secretData;
|
||||
serverConfig.port = credentials.port;
|
||||
serverConfig.description = m_appSettingsRepository->nextAvailableServerName();
|
||||
serverConfig.description = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
for (auto iterator = preparedContainers.begin(); iterator != preparedContainers.end(); iterator++) {
|
||||
serverConfig.containers.insert(iterator.key(), iterator.value());
|
||||
@@ -1240,28 +1280,26 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::checkSshConnection(const ServerCredentials &credentials, QString &output,
|
||||
ErrorCode InstallController::checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback)
|
||||
{
|
||||
SshSession sshSession(this);
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
ServerCredentials processedCredentials = credentials;
|
||||
|
||||
if (processedCredentials.secretData.contains("BEGIN") && processedCredentials.secretData.contains("PRIVATE KEY")) {
|
||||
if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) {
|
||||
if (!passphraseCallback) {
|
||||
return ErrorCode::SshPrivateKeyError;
|
||||
}
|
||||
|
||||
QString decryptedPrivateKey;
|
||||
errorCode = sshSession.getDecryptedPrivateKey(processedCredentials, decryptedPrivateKey, passphraseCallback);
|
||||
errorCode = sshSession.getDecryptedPrivateKey(credentials, decryptedPrivateKey, passphraseCallback);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
processedCredentials.secretData = decryptedPrivateKey;
|
||||
credentials.secretData = decryptedPrivateKey;
|
||||
}
|
||||
|
||||
output = sshSession.checkSshConnection(processedCredentials, errorCode);
|
||||
output = sshSession.checkSshConnection(credentials, errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
@@ -1465,7 +1503,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = containerAndPortMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1490,7 +1528,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = torOrDnsRegMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,12 @@ public:
|
||||
~InstallController();
|
||||
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
|
||||
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// Updates server-side container settings (admin self-hosted only): reconfigures the container over SSH.
|
||||
ErrorCode updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// Updates client-local settings only: rewrites the stored container config for any self-hosted/native server. No SSH.
|
||||
ErrorCode updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode rebootServer(const QString &serverId);
|
||||
ErrorCode removeAllContainers(const QString &serverId);
|
||||
@@ -64,7 +69,8 @@ public:
|
||||
|
||||
bool isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode checkSshConnection(const ServerCredentials &credentials, QString &output, std::function<QString()> passphraseCallback = nullptr);
|
||||
ErrorCode checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback = nullptr);
|
||||
|
||||
bool isServerAlreadyExists(const ServerCredentials &credentials, int &existingServerIndex);
|
||||
|
||||
|
||||
@@ -363,6 +363,6 @@ void SettingsController::disablePremV1MigrationReminder()
|
||||
|
||||
QString SettingsController::nextAvailableServerName() const
|
||||
{
|
||||
return m_appSettingsRepository->nextAvailableServerName();
|
||||
return m_serversRepository->nextAvailableServerName();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "version.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/errorStrings.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
|
||||
namespace
|
||||
@@ -109,7 +108,7 @@ void UpdateController::fetchGatewayUrl()
|
||||
.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [err, gatewayResponse] = result;
|
||||
if (err != ErrorCode::NoError) {
|
||||
logger.error() << errorString(err);
|
||||
logger.error() << "Gateway request failed, error code:" << static_cast<int>(err);
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
@@ -250,17 +249,9 @@ void UpdateController::runInstaller()
|
||||
runLinuxInstaller(kInstallerLocalPath);
|
||||
#endif
|
||||
} else {
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << QString::fromUtf8(reply->readAll());
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
logger.error() << "Installer download failed, network error:" << static_cast<int>(reply->error())
|
||||
<< reply->errorString();
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
@@ -95,9 +95,6 @@ QJsonObject ApiV2ServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
|
||||
obj[configKey::configVersion] = configVersion;
|
||||
|
||||
@@ -149,7 +146,6 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
|
||||
config.name = json.value(configKey::name).toString();
|
||||
config.nameOverriddenByUser = json.value(configKey::nameOverriddenByUser).toBool(false);
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.configVersion = json.value(configKey::configVersion).toInt(2);
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
|
||||
@@ -23,9 +23,7 @@ LegacyApiServerConfig LegacyApiServerConfig::fromJson(const QJsonObject &json)
|
||||
{
|
||||
LegacyApiServerConfig config;
|
||||
|
||||
config.name = json.value(configKey::name).toString();
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
config.crc = json.value(configKey::crc).toInt(0);
|
||||
|
||||
@@ -29,6 +29,11 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void NativeServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
|
||||
{
|
||||
QString d1 = dns1;
|
||||
@@ -50,9 +55,6 @@ QJsonObject NativeServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = this->description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
if (!hostName.isEmpty()) {
|
||||
obj[configKey::hostName] = hostName;
|
||||
}
|
||||
@@ -85,7 +87,6 @@ NativeServerConfig NativeServerConfig::fromJson(const QJsonObject& json)
|
||||
NativeServerConfig config;
|
||||
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
|
||||
@@ -27,6 +27,8 @@ struct NativeServerConfig {
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
@@ -87,9 +87,6 @@ QJsonObject SelfHostedAdminServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = this->description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
if (!hostName.isEmpty()) {
|
||||
obj[configKey::hostName] = hostName;
|
||||
}
|
||||
@@ -132,7 +129,6 @@ SelfHostedAdminServerConfig SelfHostedAdminServerConfig::fromJson(const QJsonObj
|
||||
SelfHostedAdminServerConfig config;
|
||||
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
|
||||
@@ -43,6 +43,11 @@ ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer cont
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void SelfHostedUserServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns,
|
||||
const QString &secondaryDns) const
|
||||
{
|
||||
@@ -65,9 +70,6 @@ QJsonObject SelfHostedUserServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = this->description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
if (!hostName.isEmpty()) {
|
||||
obj[configKey::hostName] = hostName;
|
||||
}
|
||||
@@ -100,7 +102,6 @@ SelfHostedUserServerConfig SelfHostedUserServerConfig::fromJson(const QJsonObjec
|
||||
SelfHostedUserServerConfig config;
|
||||
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
|
||||
@@ -32,6 +32,8 @@ struct SelfHostedUserServerConfig {
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
@@ -39,33 +39,44 @@ QString OpenVpnProtocol::defaultConfigPath()
|
||||
return p;
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::stop()
|
||||
void OpenVpnProtocol::cleanupResources()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
|
||||
// TODO: need refactoring
|
||||
// sendTermSignal() will even return true while server connected ???
|
||||
if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Connected)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Reconnecting)) {
|
||||
if (m_openVpnProcess || openVpnProcessIsRunning()) {
|
||||
if (!sendTermSignal()) {
|
||||
killOpenVpnProcess();
|
||||
}
|
||||
QThread::msleep(10);
|
||||
m_managementServer.stop();
|
||||
}
|
||||
m_managementServer.stop();
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
|
||||
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
|
||||
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
|
||||
qWarning() << "OpenVpnProtocol::cleanupResources(): Failed to disable killswitch";
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
void OpenVpnProtocol::stop()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
|
||||
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
|
||||
|| m_connectionState == Vpn::ConnectionState::Connecting
|
||||
|| m_connectionState == Vpn::ConnectionState::Connected
|
||||
|| m_connectionState == Vpn::ConnectionState::Reconnecting;
|
||||
|
||||
if (wasActive) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
}
|
||||
|
||||
cleanupResources();
|
||||
|
||||
if (wasActive || m_connectionState == Vpn::ConnectionState::Disconnecting) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnProtocol::prepare()
|
||||
@@ -168,7 +179,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
|
||||
|
||||
ErrorCode OpenVpnProtocol::start()
|
||||
{
|
||||
OpenVpnProtocol::stop();
|
||||
cleanupResources();
|
||||
|
||||
if (!QFileInfo::exists(configPath())) {
|
||||
setLastError(ErrorCode::OpenVpnConfigMissing);
|
||||
|
||||
@@ -29,6 +29,7 @@ protected slots:
|
||||
void onReadyReadDataFromManagementServer();
|
||||
|
||||
private:
|
||||
void cleanupResources();
|
||||
QString configPath() const;
|
||||
bool openVpnProcessIsRunning() const;
|
||||
bool sendTermSignal();
|
||||
|
||||
@@ -426,26 +426,6 @@ void SecureAppSettingsRepository::clearSettings()
|
||||
emit settingsCleared();
|
||||
}
|
||||
|
||||
QString SecureAppSettingsRepository::nextAvailableServerName() const
|
||||
{
|
||||
int i = 0;
|
||||
bool nameExist = false;
|
||||
|
||||
do {
|
||||
i++;
|
||||
nameExist = false;
|
||||
QJsonArray servers = QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
|
||||
for (const QJsonValue &server : servers) {
|
||||
if (server.toObject().value(configKey::description).toString() == QString("Server") + " " + QString::number(i)) {
|
||||
nameExist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (nameExist);
|
||||
|
||||
return QString("Server") + " " + QString::number(i);
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setInstallationUuid(const QString &uuid)
|
||||
{
|
||||
m_settings->setValue("Conf/installationUuid", uuid);
|
||||
|
||||
@@ -90,8 +90,6 @@ public:
|
||||
bool restoreAppConfig(const QByteArray &cfg);
|
||||
void clearSettings();
|
||||
|
||||
QString nextAvailableServerName() const;
|
||||
|
||||
QByteArray xraySavedConfigs() const;
|
||||
void setXraySavedConfigs(const QByteArray &data);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonValue>
|
||||
#include <QSet>
|
||||
#include <QUuid>
|
||||
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
@@ -32,6 +33,45 @@ QJsonObject embedStorageServerId(const QString &serverId, const QJsonObject &pay
|
||||
return o;
|
||||
}
|
||||
|
||||
QString storedServerDisplayName(const SecureServersRepository *repository, const QString &serverId)
|
||||
{
|
||||
using Kind = serverConfigUtils::ConfigType;
|
||||
switch (repository->serverKind(serverId)) {
|
||||
case Kind::SelfHostedAdmin:
|
||||
if (const auto cfg = repository->selfHostedAdminConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::SelfHostedUser:
|
||||
if (const auto cfg = repository->selfHostedUserConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::Native:
|
||||
if (const auto cfg = repository->nativeConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::AmneziaPremiumV2:
|
||||
case Kind::AmneziaFreeV3:
|
||||
case Kind::ExternalPremium:
|
||||
if (const auto cfg = repository->apiV2Config(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::AmneziaPremiumV1:
|
||||
case Kind::AmneziaFreeV2:
|
||||
if (const auto cfg = repository->legacyApiConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::Invalid:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SecureServersRepository::SecureServersRepository(SecureQSettings *settings, QObject *parent)
|
||||
@@ -153,6 +193,28 @@ void SecureServersRepository::clearServers()
|
||||
syncToStorage();
|
||||
}
|
||||
|
||||
QString SecureServersRepository::nextAvailableServerName() const
|
||||
{
|
||||
QSet<QString> usedNames;
|
||||
usedNames.reserve(m_orderedServerIds.size());
|
||||
|
||||
for (const QString &serverId : m_orderedServerIds) {
|
||||
const QString displayName = storedServerDisplayName(this, serverId);
|
||||
if (!displayName.isEmpty()) {
|
||||
usedNames.insert(displayName);
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
QString candidate;
|
||||
do {
|
||||
i++;
|
||||
candidate = QStringLiteral("Server %1").arg(i);
|
||||
} while (usedNames.contains(candidate));
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
QString SecureServersRepository::addServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind)
|
||||
{
|
||||
const QString id = normalizedOrGeneratedServerId(serverId);
|
||||
|
||||
@@ -48,6 +48,8 @@ public:
|
||||
|
||||
void clearServers();
|
||||
|
||||
QString nextAvailableServerName() const;
|
||||
|
||||
void invalidateCache();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -84,15 +84,14 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
const int httpStatusCodeNotFound = 404;
|
||||
const int httpStatusCodeNotImplemented = 501;
|
||||
const int httpStatusCodePaymentRequired = 402;
|
||||
const int httpStatusCodeTooManyRequests = 429;
|
||||
const int httpStatusCodeRequestTimeout = 408;
|
||||
const int httpStatusCodeUnprocessableEntity = 422;
|
||||
|
||||
if (!sslErrors.empty()) {
|
||||
qDebug().noquote() << sslErrors;
|
||||
return amnezia::ErrorCode::ApiConfigSslError;
|
||||
}
|
||||
if (replyError == QNetworkReply::NoError) {
|
||||
return amnezia::ErrorCode::NoError;
|
||||
}
|
||||
if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << replyError;
|
||||
@@ -107,6 +106,10 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
if (jsonDoc.isObject()) {
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
const int httpStatusFromBody = jsonObj.value(QStringLiteral("http_status")).toInt(-1);
|
||||
|
||||
if (httpStatusFromBody == httpStatusCodeTooManyRequests) {
|
||||
return amnezia::ErrorCode::ApiRateLimitError;
|
||||
}
|
||||
if (httpStatusFromBody == httpStatusCodeConflict) {
|
||||
if (apiErrorMessageFromJson(jsonObj).contains(trialAlreadyUsedMessage, Qt::CaseInsensitive)) {
|
||||
return amnezia::ErrorCode::ApiTrialAlreadyUsedError;
|
||||
@@ -116,6 +119,9 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
if (httpStatusFromBody == httpStatusCodeNotFound) {
|
||||
return amnezia::ErrorCode::ApiNotFoundError;
|
||||
}
|
||||
if (httpStatusFromBody == httpStatusCodeRequestTimeout) {
|
||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||
}
|
||||
if (httpStatusFromBody == httpStatusCodeNotImplemented) {
|
||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||
}
|
||||
@@ -126,9 +132,28 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
if (httpStatusFromBody == httpStatusCodePaymentRequired) {
|
||||
const QString message = apiErrorMessageFromJson(jsonObj);
|
||||
if (message.contains(QLatin1String("refresh_captcha"), Qt::CaseInsensitive)) {
|
||||
return amnezia::ErrorCode::ApiCaptchaRefreshError;
|
||||
}
|
||||
if (message.contains(QLatin1String("invalid_captcha"), Qt::CaseInsensitive)) {
|
||||
return amnezia::ErrorCode::ApiCaptchaInvalidError;
|
||||
}
|
||||
if (jsonObj.contains(QStringLiteral("captcha_id")) || jsonObj.contains(QStringLiteral("captcha_image"))
|
||||
|| message.compare(QLatin1String("rate_limit_exceeded"), Qt::CaseInsensitive) == 0
|
||||
|| message.contains(QLatin1String("rate_limit_exceeded"), Qt::CaseInsensitive)) {
|
||||
return amnezia::ErrorCode::ApiCaptchaRequiredError;
|
||||
}
|
||||
return amnezia::ErrorCode::ApiSubscriptionNotActiveError;
|
||||
}
|
||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||
|
||||
if (httpStatusFromBody >= 300) {
|
||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
}
|
||||
|
||||
if (replyError == QNetworkReply::NoError) {
|
||||
return amnezia::ErrorCode::NoError;
|
||||
}
|
||||
|
||||
qDebug() << "something went wrong";
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace amnezia
|
||||
Awg2,
|
||||
WireGuard,
|
||||
OpenVpn,
|
||||
Cloak,
|
||||
ShadowSocks,
|
||||
Ipsec,
|
||||
Xray,
|
||||
SSXray,
|
||||
|
||||
@@ -21,6 +21,8 @@ QString ContainerUtils::containerToString(DockerContainer c)
|
||||
{
|
||||
if (c == DockerContainer::None)
|
||||
return "none";
|
||||
if (c == DockerContainer::Cloak)
|
||||
return "amnezia-openvpn-cloak";
|
||||
if (c == DockerContainer::Awg)
|
||||
return "amnezia-awg";
|
||||
if (c == DockerContainer::Awg2)
|
||||
@@ -62,6 +64,8 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
|
||||
{
|
||||
return { { DockerContainer::None, "Not installed" },
|
||||
{ DockerContainer::OpenVpn, "OpenVPN" },
|
||||
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
|
||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||
{ DockerContainer::WireGuard, "WireGuard" },
|
||||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Awg2, "AmneziaWG" },
|
||||
@@ -83,6 +87,10 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
|
||||
return { { DockerContainer::OpenVpn,
|
||||
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
|
||||
"own security protocol with SSL/TLS for key exchange.") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("This protocol is no longer supported.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("This protocol is no longer supported.") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
|
||||
"consumption.") },
|
||||
@@ -194,6 +202,9 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
|
||||
|
||||
ServiceType ContainerUtils::containerService(DockerContainer c)
|
||||
{
|
||||
if (isUnsupportedContainer(c)) {
|
||||
return ServiceType::Vpn;
|
||||
}
|
||||
return ProtocolUtils::protocolService(defaultProtocol(c));
|
||||
}
|
||||
|
||||
@@ -202,6 +213,8 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
|
||||
switch (c) {
|
||||
case DockerContainer::None: return Proto::Unknown;
|
||||
case DockerContainer::OpenVpn: return Proto::OpenVpn;
|
||||
case DockerContainer::Cloak:
|
||||
case DockerContainer::ShadowSocks: return Proto::Unknown;
|
||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||
case DockerContainer::Awg2: return Proto::Awg;
|
||||
case DockerContainer::Awg: return Proto::Awg;
|
||||
@@ -252,6 +265,8 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
// macOS build using Network Extension – allow OpenVPN for parity with iOS.
|
||||
switch (c) {
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::Cloak: return false;
|
||||
case DockerContainer::ShadowSocks: return false;
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg2: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
@@ -336,6 +351,10 @@ int ContainerUtils::easySetupOrder(DockerContainer container)
|
||||
|
||||
bool ContainerUtils::isShareable(DockerContainer container)
|
||||
{
|
||||
if (isUnsupportedContainer(container)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (container) {
|
||||
case DockerContainer::TorWebSite: return false;
|
||||
case DockerContainer::Dns: return false;
|
||||
@@ -352,6 +371,11 @@ bool ContainerUtils::isAwgContainer(DockerContainer container)
|
||||
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
|
||||
}
|
||||
|
||||
bool ContainerUtils::isUnsupportedContainer(DockerContainer container)
|
||||
{
|
||||
return container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks;
|
||||
}
|
||||
|
||||
QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||
{
|
||||
QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol))
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace amnezia
|
||||
|
||||
bool isAwgContainer(DockerContainer container);
|
||||
|
||||
bool isUnsupportedContainer(DockerContainer container);
|
||||
|
||||
QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig);
|
||||
|
||||
int installPageOrder(DockerContainer container);
|
||||
|
||||
@@ -79,6 +79,7 @@ namespace amnezia
|
||||
ImportBackupFileUseRestoreInstead = 903,
|
||||
RestoreBackupInvalidError = 904,
|
||||
LegacyApiV1NotSupportedError = 905,
|
||||
LegacyContainerNotSupportedError = 906,
|
||||
|
||||
// Android errors
|
||||
AndroidError = 1000,
|
||||
@@ -101,6 +102,10 @@ namespace amnezia
|
||||
ApiSubscriptionNotActiveError = 1114,
|
||||
ApiNoPurchasedSubscriptionsError = 1115,
|
||||
ApiTrialAlreadyUsedError = 1116,
|
||||
ApiCaptchaRequiredError = 1117,
|
||||
ApiCaptchaInvalidError = 1118,
|
||||
ApiCaptchaRefreshError = 1119,
|
||||
ApiRateLimitError = 1120,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
|
||||
@@ -69,6 +69,7 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
|
||||
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
|
||||
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
|
||||
case (ErrorCode::LegacyContainerNotSupportedError): errorMessage = QObject::tr("This protocol is no longer supported. Please select another protocol or remove this container from the server settings."); 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;
|
||||
|
||||
@@ -93,6 +94,10 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ApiSubscriptionNotActiveError): errorMessage = QObject::tr("No active subscription found"); break;
|
||||
case (ErrorCode::ApiNoPurchasedSubscriptionsError): errorMessage = QObject::tr("No purchased subscriptions found. Please purchase a subscription first"); break;
|
||||
case (ErrorCode::ApiTrialAlreadyUsedError): errorMessage = QObject::tr("This email address has already been used to activate a trial"); break;
|
||||
case (ErrorCode::ApiCaptchaRequiredError): errorMessage = QObject::tr("CAPTCHA verification is required"); break;
|
||||
case (ErrorCode::ApiCaptchaInvalidError): errorMessage = QObject::tr("CAPTCHA was incorrect. Please try again"); break;
|
||||
case (ErrorCode::ApiCaptchaRefreshError): errorMessage = QObject::tr("CAPTCHA refreshed. Please try again"); break;
|
||||
case (ErrorCode::ApiRateLimitError): errorMessage = QObject::tr("Too many requests. Please try again later"); break;
|
||||
|
||||
// QFile errors
|
||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||
|
||||
@@ -26,6 +26,8 @@ set_target_properties(networkextension PROPERTIES
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
|
||||
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
@@ -114,10 +116,20 @@ target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
|
||||
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
find_package(openvpnadapter REQUIRED)
|
||||
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
|
||||
target_link_libraries(networkextension PRIVATE amnezia::openvpnadapter)
|
||||
|
||||
find_package(awg-apple REQUIRED)
|
||||
target_link_libraries(networkextension PRIVATE amnezia::awg-apple)
|
||||
|
||||
find_package(hev-socks5-tunnel REQUIRED)
|
||||
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
|
||||
target_link_libraries(networkextension PRIVATE heiher::hev-socks5-tunnel)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
|
||||
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
|
||||
sudo docker volume ls | grep amnezia | awk '{print $2}' | xargs sudo docker volume rm -f;\
|
||||
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
||||
sudo rm -frd /opt/amnezia
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
sudo docker stop $CONTAINER_NAME;\
|
||||
sudo docker rm -fv $CONTAINER_NAME;\
|
||||
sudo docker rmi $CONTAINER_NAME;\
|
||||
test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true
|
||||
sudo docker rmi $CONTAINER_NAME;
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.25.0)
|
||||
|
||||
project(AmneziaVPN_Tests)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Test)
|
||||
|
||||
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
qt6_add_resources(TEST_QRC
|
||||
${CLIENT_ROOT_DIR}/server_scripts/serverScripts.qrc
|
||||
)
|
||||
|
||||
add_library(test_common OBJECT
|
||||
${SOURCES}
|
||||
${HEADERS}
|
||||
${TEST_QRC}
|
||||
)
|
||||
|
||||
qt_add_repc_replicas(test_common
|
||||
${CLIENT_ROOT_DIR}/../ipc/ipc_interface.rep
|
||||
${CLIENT_ROOT_DIR}/../ipc/ipc_process_interface.rep
|
||||
)
|
||||
|
||||
target_link_libraries(test_common PUBLIC
|
||||
${LIBS}
|
||||
)
|
||||
|
||||
target_include_directories(test_common PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
${CMAKE_CURRENT_BINARY_DIR}/..
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
add_executable(test_import_export
|
||||
testAdminSelfHostedExport.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_import_export PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_multiple_imports
|
||||
testMultipleImports.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_multiple_imports PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_server_edit
|
||||
testServerEdit.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_server_edit PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_default_server_change
|
||||
testDefaultServerChange.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_default_server_change PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_server_edge_cases
|
||||
testServerEdgeCases.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_server_edge_cases PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_signal_order
|
||||
testSignalOrder.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_signal_order PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_servers_model_sync
|
||||
testServersModelSync.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_servers_model_sync PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_complex_operations
|
||||
testComplexOperations.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_complex_operations PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_settings_signals
|
||||
testSettingsSignals.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_settings_signals PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_ui_servers_model_and_controller
|
||||
testUiServersModelAndController.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_ui_servers_model_and_controller PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_self_hosted_server_setup
|
||||
testSelfHostedServerSetup.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_self_hosted_server_setup PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
enable_testing()
|
||||
add_test(NAME ImportExportTest COMMAND test_import_export)
|
||||
add_test(NAME MultipleImportsTest COMMAND test_multiple_imports)
|
||||
add_test(NAME ServerEditTest COMMAND test_server_edit)
|
||||
add_test(NAME DefaultServerChangeTest COMMAND test_default_server_change)
|
||||
add_test(NAME ServerEdgeCasesTest COMMAND test_server_edge_cases)
|
||||
add_test(NAME SignalOrderTest COMMAND test_signal_order)
|
||||
add_test(NAME ServersModelSyncTest COMMAND test_servers_model_sync)
|
||||
add_test(NAME ComplexOperationsTest COMMAND test_complex_operations)
|
||||
add_test(NAME SettingsSignalsTest COMMAND test_settings_signals)
|
||||
add_test(NAME UiServersModelAndControllerTest COMMAND test_ui_servers_model_and_controller)
|
||||
add_test(NAME SelfHostedServerSetupTest COMMAND test_self_hosted_server_setup)
|
||||
@@ -1,147 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QFile>
|
||||
#include <QDebug>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
class TestAdminSelfHostedExport : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
QJsonObject decodeVpnKey(const QString &vpnKey) {
|
||||
QString key = vpnKey;
|
||||
key.replace("vpn://", "");
|
||||
|
||||
QByteArray ba = QByteArray::fromBase64(
|
||||
key.toUtf8(),
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals
|
||||
);
|
||||
|
||||
qDebug() << "Base64 decoded size:" << ba.size();
|
||||
|
||||
QJsonDocument testDoc = QJsonDocument::fromJson(ba);
|
||||
if (!testDoc.isNull()) {
|
||||
qDebug() << "Data is not compressed, using as-is";
|
||||
return testDoc.object();
|
||||
}
|
||||
|
||||
QByteArray baUncompressed = qUncompress(ba);
|
||||
if (!baUncompressed.isEmpty()) {
|
||||
qDebug() << "Data was compressed, uncompressed size:" << baUncompressed.size();
|
||||
ba = baUncompressed;
|
||||
} else {
|
||||
qDebug() << "qUncompress failed or data is not compressed";
|
||||
}
|
||||
|
||||
return QJsonDocument::fromJson(ba).object();
|
||||
}
|
||||
|
||||
QJsonObject sortContainers(const QJsonObject &config) {
|
||||
QJsonObject sorted = config;
|
||||
|
||||
if (!config.contains("containers")) {
|
||||
return sorted;
|
||||
}
|
||||
|
||||
QJsonArray containers = config["containers"].toArray();
|
||||
QVector<QJsonObject> containerVec;
|
||||
|
||||
for (const QJsonValue &val : containers) {
|
||||
containerVec.append(val.toObject());
|
||||
}
|
||||
|
||||
std::sort(containerVec.begin(), containerVec.end(), [](const QJsonObject &a, const QJsonObject &b) {
|
||||
return a["container"].toString() < b["container"].toString();
|
||||
});
|
||||
|
||||
QJsonArray sortedContainers;
|
||||
for (const QJsonObject &obj : containerVec) {
|
||||
sortedContainers.append(obj);
|
||||
}
|
||||
|
||||
sorted["containers"] = sortedContainers;
|
||||
return sorted;
|
||||
}
|
||||
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
}
|
||||
|
||||
void testAdminSelfHostedExport() {
|
||||
QString vpnKey = "vpn://AAABTXjarZIxT8MwEIX_Cro5jbDjQunKUhhYyoZQZZKjRGpsy3baQtT_zp2bJh3oACLLPfvz3bOe00FpTdS1QR9g_tKB3q1h3sFCwBzEdf9N5ElBBgtJqBiQOkcFoemAbs6RInQ7oNkZemAvrrKvRV9VX6fH-lhSVSwavU9GSdcmXZX0UqSbseJRMqlioDxuSsJZH1mKWTrhvI22tJvVljKoLU-TtB3aN4NxpavKYwhpSD7LRc4t0WsTeMwqNRNsKweHbAyTtnRj8KvWE0pUEut-hNah2TpDM0-Kwu8vKMSd-ttFLrntao_rVvuKWkc9OnIk4n8t915_Ulcqo5FSxa9tYsk2rxlU-K7bTby_lDWfCKWvXTy-5jOGeLVET-9L7MOG-KQbJEBx57jXjdtgXtqG_wUdws5yJhCpa1iefhopM2gD-n4An-ElHL4BvzD6nw";
|
||||
|
||||
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
|
||||
qDebug() << "IMPORTED KEY:" << vpnKey;
|
||||
|
||||
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(vpnKey);
|
||||
|
||||
QVERIFY2(importResult.errorCode == ErrorCode::NoError, "Import should succeed");
|
||||
QVERIFY2(!importResult.config.isEmpty(), "Config should not be empty");
|
||||
|
||||
QJsonObject importedConfig = importResult.config;
|
||||
|
||||
m_coreController->m_importCoreController->importConfig(importedConfig);
|
||||
|
||||
QVERIFY2(importFinishedSpy.count() == 1, "importFinished signal should be emitted");
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged signal should NOT be emitted (default is already 0)");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() > 0, "Server should be added");
|
||||
|
||||
const QString serverId = m_coreController->m_serversRepository->defaultServerId();
|
||||
auto exportResult = m_coreController->m_exportController->generateFullAccessConfig(serverId);
|
||||
|
||||
QVERIFY2(exportResult.errorCode == ErrorCode::NoError, "Export should succeed");
|
||||
QVERIFY2(!exportResult.config.isEmpty(), "Exported config should not be empty");
|
||||
|
||||
qDebug() << "EXPORTED KEY:" << exportResult.config;
|
||||
|
||||
QJsonObject exportedConfig = decodeVpnKey(exportResult.config);
|
||||
|
||||
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(exportResult.config);
|
||||
QVERIFY2(importResult2.errorCode == ErrorCode::NoError, "Re-import should succeed");
|
||||
|
||||
QJsonObject sortedImported = sortContainers(importedConfig);
|
||||
QJsonObject sortedExported = sortContainers(importResult2.config);
|
||||
|
||||
QString importedJson = QJsonDocument(sortedImported).toJson(QJsonDocument::Compact);
|
||||
QString exportedJson = QJsonDocument(sortedExported).toJson(QJsonDocument::Compact);
|
||||
|
||||
qDebug() << "IMPORTED JSON:" << importedJson;
|
||||
qDebug() << "EXPORTED JSON:" << exportedJson;
|
||||
|
||||
QCOMPARE(exportedJson, importedJson);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestAdminSelfHostedExport)
|
||||
#include "testAdminSelfHostedExport.moc"
|
||||
@@ -1,111 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
#include "tests/testServerRepositoryHelpers.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class TestComplexOperations : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), QString());
|
||||
}
|
||||
}
|
||||
|
||||
void testComplexOperationSequence() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
|
||||
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
|
||||
|
||||
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
|
||||
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
|
||||
QSignalSpy serverEditedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited);
|
||||
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
|
||||
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult1.config);
|
||||
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult2.config);
|
||||
auto importResult3 = m_coreController->m_importCoreController->extractConfigFromData(wgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult3.config);
|
||||
|
||||
QVERIFY2(importFinishedSpy.count() == 3, "importFinished should be emitted 3 times");
|
||||
QVERIFY2(serverAddedSpy.count() == 3, "serverAdded should be emitted 3 times");
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 2, "defaultServerChanged should be emitted 2 times (0->1, 1->2, first import doesn't emit as default is already 0)");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 3, "Should have 3 servers");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default should be index 2");
|
||||
|
||||
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversController->getServerId(0),
|
||||
QStringLiteral("Edited First Server"));
|
||||
|
||||
QVERIFY2(serverEditedSpy.count() == 1, "serverEdited should be emitted");
|
||||
QString editedDesc0 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QVERIFY2(editedDesc0 == "Edited First Server", "First server should be edited");
|
||||
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(1));
|
||||
|
||||
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved should be emitted");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "Should have 2 servers");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default should be index 1 (was 2, removed 1)");
|
||||
|
||||
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(0));
|
||||
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 4, "defaultServerChanged should be emitted again");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default should be index 0");
|
||||
|
||||
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversController->getServerId(0),
|
||||
QStringLiteral("Final Edited Server"));
|
||||
|
||||
QVERIFY2(serverEditedSpy.count() == 2, "serverEdited should be emitted again");
|
||||
QString finalDesc0 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QVERIFY2(finalDesc0 == "Final Edited Server", "First server should be edited again");
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "Final servers count should be 2");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Final default index should be 0");
|
||||
|
||||
if (m_coreController->m_serversModel) {
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 2, "Model should have 2 rows");
|
||||
QString modelDesc0 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
|
||||
QVERIFY2(modelDesc0 == "Final Edited Server", "Model should reflect final edited name");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestComplexOperations)
|
||||
#include "testComplexOperations.moc"
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
#include "tests/testServerRepositoryHelpers.h"
|
||||
#include "ui/models/serversModel.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class TestDefaultServerChange : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
m_coreController->m_serversRepository->invalidateCache();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), QString());
|
||||
}
|
||||
}
|
||||
|
||||
void testSetDefaultServerIndex() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
|
||||
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
|
||||
|
||||
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult1.config);
|
||||
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult2.config);
|
||||
auto importResult3 = m_coreController->m_importCoreController->extractConfigFromData(wgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult3.config);
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 3, "Should have 3 servers");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default should be index 2");
|
||||
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
|
||||
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(0));
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 1, "defaultServerChanged signal should be emitted");
|
||||
QVERIFY2(defaultServerChangedSpy.at(0).at(0).toString() == m_coreController->m_serversController->getServerId(0),
|
||||
"defaultServerChanged should emit new default server id");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default server index should be 0");
|
||||
|
||||
if (m_coreController->m_serversModel) {
|
||||
int modelDefaultIndex = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::IsDefaultRole).toBool() ? 0 : -1;
|
||||
QVERIFY2(modelDefaultIndex == 0, "Model should reflect default server");
|
||||
}
|
||||
|
||||
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(2));
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 2, "defaultServerChanged signal should be emitted again");
|
||||
QVERIFY2(defaultServerChangedSpy.at(1).at(0).toString() == m_coreController->m_serversController->getServerId(2),
|
||||
"defaultServerChanged should emit new default server id");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default server index should be 2");
|
||||
}
|
||||
|
||||
void testDefaultServerChangeOnRemoveEdgeCases() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
|
||||
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
|
||||
|
||||
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult1.config);
|
||||
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult2.config);
|
||||
auto importResult3 = m_coreController->m_importCoreController->extractConfigFromData(wgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult3.config);
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 3, "Should have 3 servers");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default should be index 2");
|
||||
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
|
||||
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
|
||||
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "Should have 2 servers");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default should be index 1 (was 2, removed 0)");
|
||||
|
||||
QString desc1 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QString desc2 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(1));
|
||||
QVERIFY2(desc1 == "Xray Server", "First remaining server should be Xray");
|
||||
QVERIFY2(desc2 == "WireGuard Server", "Second remaining server should be WireGuard");
|
||||
|
||||
defaultServerChangedSpy.clear();
|
||||
serverRemovedSpy.clear();
|
||||
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
|
||||
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "Should have 1 server");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default should be index 0 (was 1, removed 0)");
|
||||
|
||||
QString lastDesc = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QVERIFY2(lastDesc == "WireGuard Server", "Last server should be WireGuard");
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestDefaultServerChange)
|
||||
#include "testDefaultServerChange.moc"
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QDebug>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
#include "tests/testServerRepositoryHelpers.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class TestMultipleImports : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
m_coreController->m_serversRepository->invalidateCache();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), QString());
|
||||
}
|
||||
}
|
||||
|
||||
void testMultipleImports() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
|
||||
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
|
||||
|
||||
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 0, "Initial servers count should be 0");
|
||||
if (m_coreController->m_serversModel) {
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 0, "Initial model row count should be 0");
|
||||
}
|
||||
|
||||
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
QVERIFY2(importResult1.errorCode == ErrorCode::NoError, "First import should succeed");
|
||||
|
||||
m_coreController->m_importCoreController->importConfig(importResult1.config);
|
||||
|
||||
QVERIFY2(importFinishedSpy.count() == 1, "importFinished signal should be emitted once");
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged signal should NOT be emitted (default is already 0)");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "After first import servers count should be 1");
|
||||
if (m_coreController->m_serversModel) {
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 1, "After first import model row count should be 1");
|
||||
}
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "First server should be default");
|
||||
|
||||
QString desc1 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QVERIFY2(desc1 == "AWG Server", "First server description should match");
|
||||
|
||||
if (m_coreController->m_serversModel) {
|
||||
QString modelDesc1 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
|
||||
QVERIFY2(modelDesc1 == "AWG Server", "First server description in model should match");
|
||||
}
|
||||
|
||||
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
|
||||
QVERIFY2(importResult2.errorCode == ErrorCode::NoError, "Second import should succeed");
|
||||
|
||||
m_coreController->m_importCoreController->importConfig(importResult2.config);
|
||||
|
||||
QVERIFY2(importFinishedSpy.count() == 2, "importFinished signal should be emitted twice");
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 1, "defaultServerChanged signal should be emitted once (0->1, first import doesn't emit)");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "After second import servers count should be 2");
|
||||
if (m_coreController->m_serversModel) {
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 2, "After second import model row count should be 2");
|
||||
}
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Second server should be default");
|
||||
|
||||
QString desc2 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(1));
|
||||
QVERIFY2(desc2 == "Xray Server", "Second server description should match");
|
||||
|
||||
if (m_coreController->m_serversModel) {
|
||||
QString modelDesc2 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(1, 0), ServersModel::NameRole).toString();
|
||||
QVERIFY2(modelDesc2 == "Xray Server", "Second server description in model should match");
|
||||
}
|
||||
|
||||
auto importResult3 = m_coreController->m_importCoreController->extractConfigFromData(wgKey);
|
||||
QVERIFY2(importResult3.errorCode == ErrorCode::NoError, "Third import should succeed");
|
||||
|
||||
m_coreController->m_importCoreController->importConfig(importResult3.config);
|
||||
|
||||
QVERIFY2(importFinishedSpy.count() == 3, "importFinished signal should be emitted three times");
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 2, "defaultServerChanged signal should be emitted twice (0->1, 1->2, first import doesn't emit)");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 3, "After third import servers count should be 3");
|
||||
if (m_coreController->m_serversModel) {
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 3, "After third import model row count should be 3");
|
||||
}
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Third server should be default");
|
||||
|
||||
QString desc3 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(2));
|
||||
QVERIFY2(desc3 == "WireGuard Server", "Third server description should match");
|
||||
|
||||
if (m_coreController->m_serversModel) {
|
||||
QString modelDesc3 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(2, 0), ServersModel::NameRole).toString();
|
||||
QVERIFY2(modelDesc3 == "WireGuard Server", "Third server description in model should match");
|
||||
}
|
||||
}
|
||||
|
||||
void testMultipleImportsRemoval() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
|
||||
|
||||
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 0, "Initial servers count should be 0");
|
||||
|
||||
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
QVERIFY2(importResult1.errorCode == ErrorCode::NoError, "First import should succeed");
|
||||
m_coreController->m_importCoreController->importConfig(importResult1.config);
|
||||
|
||||
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
|
||||
QVERIFY2(importResult2.errorCode == ErrorCode::NoError, "Second import should succeed");
|
||||
m_coreController->m_importCoreController->importConfig(importResult2.config);
|
||||
|
||||
QVERIFY2(importFinishedSpy.count() == 2, "importFinished signal should be emitted twice");
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 1, "defaultServerChanged signal should be emitted once (0->1, first import doesn't emit)");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "After two imports servers count should be 2");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Second server should be default");
|
||||
|
||||
QString desc0 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QString desc1 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(1));
|
||||
QVERIFY2(desc0 == "AWG Server", "First server description should match");
|
||||
QVERIFY2(desc1 == "Xray Server", "Second server description should match");
|
||||
|
||||
defaultServerChangedSpy.clear();
|
||||
serverRemovedSpy.clear();
|
||||
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
|
||||
|
||||
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
|
||||
QVERIFY2(serverRemovedSpy.at(0).at(1).toInt() == 0, "serverRemoved should emit removed index 0");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "After removing first server, servers count should be 1");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "After removing first server, default index should be 0");
|
||||
|
||||
QString remainingDesc = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QVERIFY2(remainingDesc == "Xray Server", "Remaining server should be Xray Server");
|
||||
|
||||
if (m_coreController->m_serversModel) {
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 1, "After removing first server, model row count should be 1");
|
||||
QString modelDesc = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
|
||||
QVERIFY2(modelDesc == "Xray Server", "Remaining server description in model should match");
|
||||
}
|
||||
|
||||
defaultServerChangedSpy.clear();
|
||||
serverRemovedSpy.clear();
|
||||
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
|
||||
|
||||
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
|
||||
QVERIFY2(serverRemovedSpy.at(0).at(1).toInt() == 0, "serverRemoved should emit removed index 0");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 0, "After removing last server, servers count should be 0");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "After removing last server, default index should be 0");
|
||||
|
||||
if (m_coreController->m_serversModel) {
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 0, "After removing last server, model row count should be 0");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestMultipleImports)
|
||||
#include "testMultipleImports.moc"
|
||||
@@ -1,385 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QDebug>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocols/awgProtocolConfig.h"
|
||||
#include "core/models/protocols/dnsProtocolConfig.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "ui/models/serversModel.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class TestSelfHostedServerSetup : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
ServerCredentials getCredentialsFromEnv() {
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
|
||||
QString hostName = env.value("TEST_SERVER_HOST");
|
||||
QString userName = env.value("TEST_SERVER_USER");
|
||||
QString password = env.value("TEST_SERVER_PASSWORD");
|
||||
QString portStr = env.value("TEST_SERVER_PORT", "22");
|
||||
int port = portStr.toInt();
|
||||
|
||||
ServerCredentials credentials;
|
||||
credentials.hostName = hostName;
|
||||
credentials.userName = userName;
|
||||
credentials.secretData = password;
|
||||
credentials.port = port;
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
void verifySshConnection(const ServerCredentials& credentials) {
|
||||
QString sshOutput;
|
||||
ErrorCode sshError = m_coreController->m_installController->checkSshConnection(credentials, sshOutput);
|
||||
QVERIFY2(sshError == ErrorCode::NoError,
|
||||
QString("SSH connection should succeed. Error: %1, Output: %2")
|
||||
.arg(static_cast<int>(sshError))
|
||||
.arg(sshOutput)
|
||||
.toUtf8().constData());
|
||||
qDebug() << "SSH connection successful. Output:" << sshOutput;
|
||||
}
|
||||
|
||||
void verifyAdminAccess(int serverIndex)
|
||||
{
|
||||
const QString serverId = m_coreController->m_serversRepository->serverIdAt(serverIndex);
|
||||
const auto adminCfg = m_coreController->m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
QVERIFY2(adminCfg.has_value(), "Server config should be SelfHostedAdminServerConfig");
|
||||
|
||||
const SelfHostedAdminServerConfig &selfHosted = *adminCfg;
|
||||
|
||||
QVERIFY2(selfHosted.hasCredentials(),
|
||||
"Server should have credentials (admin access)");
|
||||
|
||||
QVERIFY2(!selfHosted.userName.isEmpty(),
|
||||
"Server should have userName for admin access");
|
||||
|
||||
QVERIFY2(!selfHosted.password.isEmpty(),
|
||||
"Server should have password for admin access");
|
||||
|
||||
QVERIFY2(!selfHosted.isReadOnly(),
|
||||
"Server should not be read-only (should have admin access)");
|
||||
|
||||
if (m_coreController->m_serversModel) {
|
||||
bool hasWriteAccess = m_coreController->m_serversModel->data(
|
||||
m_coreController->m_serversModel->index(serverIndex, 0),
|
||||
ServersModel::HasWriteAccessRole
|
||||
).toBool();
|
||||
|
||||
QVERIFY2(hasWriteAccess,
|
||||
"Server should have write access (admin access) according to ServersModel");
|
||||
}
|
||||
|
||||
qDebug() << "Admin access verified for server at index:" << serverIndex;
|
||||
}
|
||||
|
||||
void verifyClientConfig(const ContainerConfig& containerConfig, DockerContainer container) {
|
||||
QString containerName = ContainerUtils::containerToString(container);
|
||||
qDebug() << "Checking container:" << containerName;
|
||||
|
||||
if (ContainerUtils::containerService(container) != ServiceType::Other) {
|
||||
bool hasClientConfig = containerConfig.protocolConfig.hasClientConfig();
|
||||
|
||||
QVERIFY2(hasClientConfig,
|
||||
QString("Container %1 should have client config initialized")
|
||||
.arg(containerName)
|
||||
.toUtf8().constData());
|
||||
|
||||
if (container == DockerContainer::Awg) {
|
||||
const AwgProtocolConfig* awgProtocolConfig = containerConfig.protocolConfig.as<AwgProtocolConfig>();
|
||||
QVERIFY2(awgProtocolConfig != nullptr, "Protocol config should be AwgProtocolConfig");
|
||||
QVERIFY2(awgProtocolConfig->hasClientConfig(), "AwgProtocolConfig should have client config");
|
||||
const std::optional<AwgClientConfig>& clientCfgOpt = awgProtocolConfig->clientConfig;
|
||||
QVERIFY2(clientCfgOpt.has_value(), "Awg client config should exist");
|
||||
|
||||
const AwgClientConfig& awgClientConfig = *clientCfgOpt;
|
||||
QVERIFY2(!awgClientConfig.hostName.isEmpty(), "Awg client config should have hostName");
|
||||
QVERIFY2(awgClientConfig.port > 0, "Awg client config should have valid port");
|
||||
QVERIFY2(!awgClientConfig.clientPrivateKey.isEmpty(), "Awg client config should have clientPrivateKey");
|
||||
QVERIFY2(!awgClientConfig.clientPublicKey.isEmpty(), "Awg client config should have clientPublicKey");
|
||||
QVERIFY2(!awgClientConfig.serverPublicKey.isEmpty(), "Awg client config should have serverPublicKey");
|
||||
QVERIFY2(!awgClientConfig.clientId.isEmpty(), "Awg client config should have clientId");
|
||||
QVERIFY2(!awgClientConfig.nativeConfig.isEmpty(), "Awg client config should have nativeConfig");
|
||||
}
|
||||
|
||||
qDebug() << "Container" << containerName << "has valid client config initialized";
|
||||
} else {
|
||||
qDebug() << "Container" << containerName << "is service type Other, skipping client config check";
|
||||
}
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), QString());
|
||||
}
|
||||
}
|
||||
|
||||
void testSelfHostedServerSetup() {
|
||||
ServerCredentials credentials = getCredentialsFromEnv();
|
||||
|
||||
if (credentials.hostName.isEmpty() || credentials.userName.isEmpty() || credentials.secretData.isEmpty()) {
|
||||
QSKIP("Test requires TEST_SERVER_HOST, TEST_SERVER_USER, TEST_SERVER_PASSWORD environment variables");
|
||||
}
|
||||
|
||||
QVERIFY2(credentials.isValid(), "Server credentials should be valid");
|
||||
qDebug() << "Using server:" << credentials.hostName << "user:" << credentials.userName << "port:" << credentials.port;
|
||||
|
||||
verifySshConnection(credentials);
|
||||
|
||||
int awgPort = 55424;
|
||||
TransportProto awgTransportProto = TransportProto::Udp;
|
||||
bool wasAwgInstalled = false;
|
||||
|
||||
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
|
||||
ErrorCode installServerError = m_coreController->m_installController->installServer(
|
||||
credentials, DockerContainer::Awg, awgPort, awgTransportProto, wasAwgInstalled);
|
||||
|
||||
QVERIFY2(installServerError == ErrorCode::NoError,
|
||||
QString("installServer for Awg should succeed. Error: %1")
|
||||
.arg(static_cast<int>(installServerError))
|
||||
.toUtf8().constData());
|
||||
QVERIFY2(serverAddedSpy.count() == 1, "serverAdded signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() > 0, "Server should be added");
|
||||
|
||||
int serverIndex = m_coreController->m_serversRepository->serversCount() - 1;
|
||||
qDebug() << "Server with Awg container added at index:" << serverIndex;
|
||||
|
||||
const auto adminAfterAwg = m_coreController->m_serversRepository->selfHostedAdminConfig(
|
||||
m_coreController->m_serversRepository->serverIdAt(serverIndex));
|
||||
QVERIFY2(adminAfterAwg.has_value(), "Server should be self-hosted (admin)");
|
||||
const SelfHostedAdminServerConfig *selfHostedAfterAwg = &(*adminAfterAwg);
|
||||
QVERIFY2(selfHostedAfterAwg->defaultContainer == DockerContainer::Awg, "Default container should be Awg");
|
||||
QVERIFY2(selfHostedAfterAwg->containers.contains(DockerContainer::Awg), "Server should have Awg container");
|
||||
|
||||
ContainerConfig awgConfig = selfHostedAfterAwg->containers.value(DockerContainer::Awg);
|
||||
QVERIFY2(awgConfig.container == DockerContainer::Awg, "Awg container config should be valid");
|
||||
QVERIFY2(selfHostedAfterAwg->containers.size() == 1,
|
||||
QString("Server should have exactly 1 container after Awg installation, but has %1")
|
||||
.arg(selfHostedAfterAwg->containers.size())
|
||||
.toUtf8().constData());
|
||||
verifyClientConfig(awgConfig, DockerContainer::Awg);
|
||||
|
||||
qDebug() << "Awg container installed and configured successfully with valid client config";
|
||||
|
||||
int dnsPort = 53;
|
||||
TransportProto dnsTransportProto = TransportProto::Udp;
|
||||
bool wasDnsInstalled = false;
|
||||
|
||||
const QString serverIdForOps = m_coreController->m_serversRepository->serverIdAt(serverIndex);
|
||||
ErrorCode installContainerError = m_coreController->m_installController->installContainer(
|
||||
serverIdForOps, DockerContainer::Dns, dnsPort, dnsTransportProto, wasDnsInstalled);
|
||||
|
||||
QVERIFY2(installContainerError == ErrorCode::NoError,
|
||||
QString("installContainer for Dns should succeed. Error: %1")
|
||||
.arg(static_cast<int>(installContainerError))
|
||||
.toUtf8().constData());
|
||||
qDebug() << "Dns container installed:" << wasDnsInstalled;
|
||||
|
||||
const auto adminAfterDns = m_coreController->m_serversRepository->selfHostedAdminConfig(
|
||||
m_coreController->m_serversRepository->serverIdAt(serverIndex));
|
||||
QVERIFY2(adminAfterDns.has_value(), "Server config should be SelfHostedAdminServerConfig");
|
||||
const SelfHostedAdminServerConfig *selfHostedAfterDns = &(*adminAfterDns);
|
||||
QVERIFY2(selfHostedAfterDns->containers.contains(DockerContainer::Awg), "Server should still have Awg container");
|
||||
QVERIFY2(selfHostedAfterDns->containers.contains(DockerContainer::Dns), "Server should have Dns container");
|
||||
QVERIFY2(selfHostedAfterDns->containers.size() == 2,
|
||||
QString("Server should have exactly 2 containers after Dns installation, but has %1")
|
||||
.arg(selfHostedAfterDns->containers.size())
|
||||
.toUtf8().constData());
|
||||
|
||||
ContainerConfig dnsConfig = selfHostedAfterDns->containers.value(DockerContainer::Dns);
|
||||
QVERIFY2(dnsConfig.container == DockerContainer::Dns, "Dns container config should be valid");
|
||||
|
||||
const DnsProtocolConfig* dnsProtocolConfig = dnsConfig.protocolConfig.as<DnsProtocolConfig>();
|
||||
QVERIFY2(dnsProtocolConfig != nullptr, "Protocol config should be DnsProtocolConfig");
|
||||
|
||||
qDebug() << "Dns container installed and configured successfully";
|
||||
|
||||
verifyAdminAccess(serverIndex);
|
||||
|
||||
qDebug() << "Test completed successfully. Server setup with Awg and Dns containers is complete.";
|
||||
}
|
||||
|
||||
void testSelfHostedServerEmptyRecover() {
|
||||
ServerCredentials credentials = getCredentialsFromEnv();
|
||||
|
||||
if (credentials.hostName.isEmpty() || credentials.userName.isEmpty() || credentials.secretData.isEmpty()) {
|
||||
QSKIP("Test requires TEST_SERVER_HOST, TEST_SERVER_USER, TEST_SERVER_PASSWORD environment variables");
|
||||
}
|
||||
|
||||
QVERIFY2(credentials.isValid(), "Server credentials should be valid");
|
||||
qDebug() << "Using server:" << credentials.hostName << "user:" << credentials.userName << "port:" << credentials.port;
|
||||
|
||||
verifySshConnection(credentials);
|
||||
|
||||
SelfHostedAdminServerConfig serverConfig;
|
||||
serverConfig.hostName = credentials.hostName;
|
||||
serverConfig.userName = credentials.userName;
|
||||
serverConfig.password = credentials.secretData;
|
||||
serverConfig.port = credentials.port;
|
||||
serverConfig.description = m_coreController->m_appSettingsRepository->nextAvailableServerName();
|
||||
serverConfig.displayName = serverConfig.description.isEmpty() ? serverConfig.hostName : serverConfig.description;
|
||||
serverConfig.defaultContainer = DockerContainer::None;
|
||||
|
||||
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
|
||||
m_coreController->m_serversRepository->addServer(QString(), serverConfig.toJson(),
|
||||
serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
|
||||
QVERIFY2(serverAddedSpy.count() == 1, "serverAdded signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() > 0, "Server should be added");
|
||||
|
||||
int serverIndex = m_coreController->m_serversRepository->serversCount() - 1;
|
||||
qDebug() << "Empty server added at index:" << serverIndex;
|
||||
|
||||
const auto addedAdmin = m_coreController->m_serversRepository->selfHostedAdminConfig(
|
||||
m_coreController->m_serversRepository->serverIdAt(serverIndex));
|
||||
QVERIFY2(addedAdmin.has_value(), "Added server should be self-hosted admin");
|
||||
const SelfHostedAdminServerConfig *selfHosted = &(*addedAdmin);
|
||||
QVERIFY2(selfHosted->containers.isEmpty(), "Server should have no containers initially");
|
||||
QVERIFY2(selfHosted->defaultContainer == DockerContainer::None, "Default container should be None");
|
||||
|
||||
const QString scanServerId = m_coreController->m_serversRepository->serverIdAt(serverIndex);
|
||||
ErrorCode scanError = m_coreController->m_installController->scanServerForInstalledContainers(scanServerId);
|
||||
QVERIFY2(scanError == ErrorCode::NoError,
|
||||
QString("Server scan should succeed. Error: %1")
|
||||
.arg(static_cast<int>(scanError))
|
||||
.toUtf8().constData());
|
||||
qDebug() << "Server scan completed successfully";
|
||||
|
||||
const auto scannedAdmin = m_coreController->m_serversRepository->selfHostedAdminConfig(
|
||||
m_coreController->m_serversRepository->serverIdAt(serverIndex));
|
||||
QVERIFY2(scannedAdmin.has_value(), "Scanned server config should be SelfHostedAdminServerConfig");
|
||||
const SelfHostedAdminServerConfig *scannedSelfHosted = &(*scannedAdmin);
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> containers = scannedSelfHosted->containers;
|
||||
int containersCount = containers.size();
|
||||
qDebug() << "Found containers count:" << containersCount;
|
||||
|
||||
QVERIFY2(containersCount >= 0,
|
||||
QString("Containers count should be non-negative, but got %1")
|
||||
.arg(containersCount)
|
||||
.toUtf8().constData());
|
||||
|
||||
if (containersCount > 0) {
|
||||
qDebug() << "Server has" << containersCount << "installed container(s)";
|
||||
} else {
|
||||
qDebug() << "Server has no installed containers";
|
||||
}
|
||||
|
||||
for (auto it = containers.begin(); it != containers.end(); ++it) {
|
||||
verifyClientConfig(it.value(), it.key());
|
||||
}
|
||||
|
||||
QVERIFY2(scannedSelfHosted->containers.size() == containersCount,
|
||||
QString("Scanned containers count should match. Expected: %1, Actual: %2")
|
||||
.arg(containersCount)
|
||||
.arg(scannedSelfHosted->containers.size())
|
||||
.toUtf8().constData());
|
||||
|
||||
verifyAdminAccess(serverIndex);
|
||||
|
||||
qDebug() << "Test completed successfully. Server has admin access and all containers are initialized.";
|
||||
}
|
||||
|
||||
void testRemoveAllContainers() {
|
||||
ServerCredentials credentials = getCredentialsFromEnv();
|
||||
|
||||
if (credentials.hostName.isEmpty() || credentials.userName.isEmpty() || credentials.secretData.isEmpty()) {
|
||||
QSKIP("Test requires TEST_SERVER_HOST, TEST_SERVER_USER, TEST_SERVER_PASSWORD environment variables");
|
||||
}
|
||||
|
||||
QVERIFY2(credentials.isValid(), "Server credentials should be valid");
|
||||
qDebug() << "Using server:" << credentials.hostName << "user:" << credentials.userName << "port:" << credentials.port;
|
||||
|
||||
verifySshConnection(credentials);
|
||||
|
||||
int awgPort = 55424;
|
||||
TransportProto awgTransportProto = TransportProto::Udp;
|
||||
bool wasAwgInstalled = false;
|
||||
|
||||
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
|
||||
ErrorCode installServerError = m_coreController->m_installController->installServer(
|
||||
credentials, DockerContainer::Awg, awgPort, awgTransportProto, wasAwgInstalled);
|
||||
|
||||
QVERIFY2(installServerError == ErrorCode::NoError,
|
||||
QString("installServer for Awg should succeed. Error: %1")
|
||||
.arg(static_cast<int>(installServerError))
|
||||
.toUtf8().constData());
|
||||
QVERIFY2(serverAddedSpy.count() == 1, "serverAdded signal should be emitted");
|
||||
|
||||
int serverIndex = m_coreController->m_serversRepository->serversCount() - 1;
|
||||
qDebug() << "Server with Awg container added at index:" << serverIndex;
|
||||
|
||||
const auto adminBeforeRemoval = m_coreController->m_serversRepository->selfHostedAdminConfig(
|
||||
m_coreController->m_serversRepository->serverIdAt(serverIndex));
|
||||
QVERIFY2(adminBeforeRemoval.has_value(), "Server config should be SelfHostedAdminServerConfig");
|
||||
const SelfHostedAdminServerConfig *selfHostedBeforeRemoval = &(*adminBeforeRemoval);
|
||||
QVERIFY2(!selfHostedBeforeRemoval->containers.isEmpty(), "Server should have containers before removal");
|
||||
QVERIFY2(selfHostedBeforeRemoval->defaultContainer != DockerContainer::None, "Server should have default container before removal");
|
||||
|
||||
qDebug() << "Containers before removal:" << selfHostedBeforeRemoval->containers.size();
|
||||
|
||||
const QString removeServerId = m_coreController->m_serversRepository->serverIdAt(serverIndex);
|
||||
ErrorCode removeError = m_coreController->m_installController->removeAllContainers(removeServerId);
|
||||
QVERIFY2(removeError == ErrorCode::NoError,
|
||||
QString("removeAllContainers should succeed. Error: %1")
|
||||
.arg(static_cast<int>(removeError))
|
||||
.toUtf8().constData());
|
||||
qDebug() << "All containers removed successfully";
|
||||
|
||||
const auto adminAfterRemoval = m_coreController->m_serversRepository->selfHostedAdminConfig(
|
||||
m_coreController->m_serversRepository->serverIdAt(serverIndex));
|
||||
QVERIFY2(adminAfterRemoval.has_value(), "Server config should be SelfHostedAdminServerConfig");
|
||||
const SelfHostedAdminServerConfig *selfHostedAfterRemoval = &(*adminAfterRemoval);
|
||||
|
||||
QVERIFY2(selfHostedAfterRemoval->containers.isEmpty(),
|
||||
"Server should have no containers after removal");
|
||||
QVERIFY2(selfHostedAfterRemoval->defaultContainer == DockerContainer::None,
|
||||
"Default container should be None after removal");
|
||||
|
||||
qDebug() << "Containers after removal:" << selfHostedAfterRemoval->containers.size();
|
||||
|
||||
verifyAdminAccess(serverIndex);
|
||||
|
||||
qDebug() << "Test completed successfully. All containers removed and server is empty.";
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestSelfHostedServerSetup)
|
||||
#include "testSelfHostedServerSetup.moc"
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class TestServerEdgeCases : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
m_coreController->m_serversRepository->invalidateCache();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), QString());
|
||||
}
|
||||
}
|
||||
|
||||
void testInvalidIndexOperations() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
|
||||
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult.config);
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "Should have 1 server");
|
||||
|
||||
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
|
||||
QSignalSpy serverEditedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited);
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(-1));
|
||||
QVERIFY2(serverRemovedSpy.count() == 0, "serverRemoved should NOT be emitted for invalid index");
|
||||
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(10));
|
||||
QVERIFY2(serverRemovedSpy.count() == 0, "serverRemoved should NOT be emitted for invalid index");
|
||||
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(100));
|
||||
QVERIFY2(serverRemovedSpy.count() == 0, "serverRemoved should NOT be emitted for invalid index");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "Server count should remain 1");
|
||||
|
||||
const QString validServerId = m_coreController->m_serversController->getServerId(0);
|
||||
const serverConfigUtils::ConfigType editKind =
|
||||
m_coreController->m_serversRepository->serverKind(validServerId);
|
||||
|
||||
m_coreController->m_serversRepository->editServer(m_coreController->m_serversController->getServerId(-1),
|
||||
QJsonObject(), editKind);
|
||||
QVERIFY2(serverEditedSpy.count() == 0, "serverEdited should NOT be emitted for invalid index");
|
||||
|
||||
m_coreController->m_serversRepository->editServer(m_coreController->m_serversController->getServerId(10),
|
||||
QJsonObject(), editKind);
|
||||
QVERIFY2(serverEditedSpy.count() == 0, "serverEdited should NOT be emitted for invalid index");
|
||||
|
||||
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(-1));
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged should NOT be emitted for invalid index");
|
||||
|
||||
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(10));
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged should NOT be emitted for invalid index");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default server index should remain 0");
|
||||
}
|
||||
|
||||
void testEmptyRepositoryOperations() {
|
||||
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
|
||||
QSignalSpy serverEditedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited);
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 0, "Should start with 0 servers");
|
||||
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
|
||||
QVERIFY2(serverRemovedSpy.count() == 0, "serverRemoved should NOT be emitted for empty repository");
|
||||
|
||||
m_coreController->m_serversRepository->editServer(m_coreController->m_serversController->getServerId(0),
|
||||
SelfHostedAdminServerConfig {}.toJson(),
|
||||
serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
QVERIFY2(serverEditedSpy.count() == 0, "serverEdited should NOT be emitted for empty repository");
|
||||
|
||||
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(0));
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged should NOT be emitted for empty repository");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default server index should be 0 for empty repository");
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 0, "Server count should remain 0");
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestServerEdgeCases)
|
||||
#include "testServerEdgeCases.moc"
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
#include "tests/testServerRepositoryHelpers.h"
|
||||
#include "ui/models/serversModel.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class TestServerEdit : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
m_coreController->m_serversRepository->invalidateCache();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), QString());
|
||||
}
|
||||
}
|
||||
|
||||
void testServerEditTriggersHandlers() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
|
||||
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
|
||||
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult.config);
|
||||
QVERIFY2(importFinishedSpy.count() == 1, "Import should succeed");
|
||||
|
||||
QSignalSpy serverEditedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited);
|
||||
|
||||
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversController->getServerId(0),
|
||||
QStringLiteral("Edited AWG Server"));
|
||||
|
||||
QVERIFY2(serverEditedSpy.count() == 1, "serverEdited signal should be emitted");
|
||||
QVERIFY2(serverEditedSpy.at(0).at(0).toString() == m_coreController->m_serversRepository->serverIdAt(0),
|
||||
"serverEdited should emit edited server id");
|
||||
|
||||
const QString editedDesc = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QVERIFY2(editedDesc == "Edited AWG Server", "Server description should be updated");
|
||||
|
||||
if (m_coreController->m_serversModel) {
|
||||
QString modelDesc = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
|
||||
QVERIFY2(modelDesc == "Edited AWG Server", "Server description in model should be updated");
|
||||
}
|
||||
}
|
||||
|
||||
void testServerEditPreservesDefault() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
|
||||
|
||||
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult1.config);
|
||||
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult2.config);
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default server should be index 1");
|
||||
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
|
||||
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversController->getServerId(1),
|
||||
QStringLiteral("Edited Default Server"));
|
||||
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged should NOT be emitted when editing default server");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default server index should remain 1");
|
||||
|
||||
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversController->getServerId(0),
|
||||
QStringLiteral("Edited Non-Default Server"));
|
||||
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged should NOT be emitted when editing non-default server");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default server index should remain 1");
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestServerEdit)
|
||||
#include "testServerEdit.moc"
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
#ifndef TESTSERVERREPOSITORYHELPERS_H
|
||||
#define TESTSERVERREPOSITORYHELPERS_H
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
|
||||
namespace amnezia::test
|
||||
{
|
||||
|
||||
inline QString serverDescription(SecureServersRepository *repo, const QString &serverId)
|
||||
{
|
||||
switch (repo->serverKind(serverId)) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = repo->selfHostedAdminConfig(serverId);
|
||||
return cfg.has_value() ? cfg->description : QString();
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = repo->selfHostedUserConfig(serverId);
|
||||
return cfg.has_value() ? cfg->description : QString();
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = repo->nativeConfig(serverId);
|
||||
return cfg.has_value() ? cfg->description : QString();
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = repo->apiV2Config(serverId);
|
||||
return cfg.has_value() ? cfg->description : QString();
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2: {
|
||||
const auto cfg = repo->legacyApiConfig(serverId);
|
||||
return cfg.has_value() ? cfg->description : QString();
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
inline void setServerDescription(SecureServersRepository *repo, const QString &serverId, const QString &description)
|
||||
{
|
||||
const serverConfigUtils::ConfigType kind = repo->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
auto cfg = repo->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->description = description;
|
||||
cfg->displayName = description;
|
||||
repo->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
auto cfg = repo->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->description = description;
|
||||
cfg->displayName = description;
|
||||
repo->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
auto cfg = repo->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->description = description;
|
||||
cfg->displayName = description;
|
||||
repo->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
auto cfg = repo->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->description = description;
|
||||
cfg->displayName = description;
|
||||
repo->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace amnezia::test
|
||||
|
||||
#endif
|
||||
@@ -1,112 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
#include "tests/testServerRepositoryHelpers.h"
|
||||
#include "ui/models/serversModel.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class TestServersModelSync : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), QString());
|
||||
}
|
||||
}
|
||||
|
||||
void testServersModelSyncOnOperations() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
|
||||
if (!m_coreController->m_serversModel) {
|
||||
QSKIP("ServersModel not available");
|
||||
}
|
||||
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 0, "Initial model row count should be 0");
|
||||
|
||||
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult.config);
|
||||
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 1, "Model should have 1 row after import");
|
||||
QString modelDesc1 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
|
||||
QVERIFY2(modelDesc1 == "AWG Server", "Model should have correct server name");
|
||||
|
||||
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversController->getServerId(0),
|
||||
QStringLiteral("Edited AWG Server"));
|
||||
|
||||
QString modelDesc2 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::NameRole).toString();
|
||||
QVERIFY2(modelDesc2 == "Edited AWG Server", "Model should be updated after edit");
|
||||
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 0, "Model should have 0 rows after removal");
|
||||
}
|
||||
|
||||
void testServersModelDefaultIndexSync() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
|
||||
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
|
||||
|
||||
if (!m_coreController->m_serversModel) {
|
||||
QSKIP("ServersModel not available");
|
||||
}
|
||||
|
||||
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult1.config);
|
||||
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult2.config);
|
||||
auto importResult3 = m_coreController->m_importCoreController->extractConfigFromData(wgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult3.config);
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default should be index 2");
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 3, "Model should have 3 rows");
|
||||
|
||||
bool isDefault0 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::IsDefaultRole).toBool();
|
||||
bool isDefault1 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(1, 0), ServersModel::IsDefaultRole).toBool();
|
||||
bool isDefault2 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(2, 0), ServersModel::IsDefaultRole).toBool();
|
||||
|
||||
QVERIFY2(!isDefault0, "Server 0 should not be default");
|
||||
QVERIFY2(!isDefault1, "Server 1 should not be default");
|
||||
QVERIFY2(isDefault2, "Server 2 should be default");
|
||||
|
||||
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(0));
|
||||
|
||||
isDefault0 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(0, 0), ServersModel::IsDefaultRole).toBool();
|
||||
isDefault2 = m_coreController->m_serversModel->data(m_coreController->m_serversModel->index(2, 0), ServersModel::IsDefaultRole).toBool();
|
||||
|
||||
QVERIFY2(isDefault0, "Server 0 should be default after change");
|
||||
QVERIFY2(!isDefault2, "Server 2 should not be default after change");
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestServersModelSync)
|
||||
#include "testServersModelSync.moc"
|
||||
|
||||
@@ -1,282 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
#include <QLocale>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "ui/controllers/settingsUiController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/models/allowedDnsModel.h"
|
||||
#include "ui/models/ipSplitTunnelingModel.h"
|
||||
#include "ui/models/appSplitTunnelingModel.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
class TestSettingsSignals : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
}
|
||||
|
||||
void testDnsSettingsSignals() {
|
||||
QSignalSpy primaryDnsChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::primaryDnsChanged);
|
||||
QSignalSpy secondaryDnsChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::secondaryDnsChanged);
|
||||
QSignalSpy allowedDnsServersChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::allowedDnsServersChanged);
|
||||
|
||||
QString primaryDns = "8.8.8.8";
|
||||
QString secondaryDns = "8.8.4.4";
|
||||
|
||||
m_coreController->m_settingsUiController->setPrimaryDns(primaryDns);
|
||||
QVERIFY2(primaryDnsChangedSpy.count() == 1, "primaryDnsChanged signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_settingsController->getPrimaryDns() == primaryDns, "Primary DNS should be updated in SettingsController");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->getPrimaryDns() == primaryDns, "Primary DNS should be available in SettingsUiController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->primaryDns() == primaryDns, "Primary DNS should be available in SecureAppSettingsRepository");
|
||||
|
||||
m_coreController->m_settingsUiController->setSecondaryDns(secondaryDns);
|
||||
QVERIFY2(secondaryDnsChangedSpy.count() == 1, "secondaryDnsChanged signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_settingsController->getSecondaryDns() == secondaryDns, "Secondary DNS should be updated in SettingsController");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->getSecondaryDns() == secondaryDns, "Secondary DNS should be available in SettingsUiController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->secondaryDns() == secondaryDns, "Secondary DNS should be available in SecureAppSettingsRepository");
|
||||
|
||||
QStringList dnsList = {"1.1.1.1", "1.0.0.1"};
|
||||
m_coreController->m_allowedDnsController->addDnsList(dnsList, true);
|
||||
QVERIFY2(allowedDnsServersChangedSpy.count() == 1, "allowedDnsServersChanged signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->getAllowedDnsServers() == dnsList, "Allowed DNS servers should be updated in SecureAppSettingsRepository");
|
||||
QVERIFY2(m_coreController->m_allowedDnsController->getCurrentDnsServers() == dnsList, "Allowed DNS servers should be available in AllowedDnsController");
|
||||
|
||||
QVERIFY2(m_coreController->m_allowedDnsUiController != nullptr, "AllowedDnsUiController should exist");
|
||||
QVERIFY2(m_coreController->m_allowedDnsModel != nullptr, "AllowedDnsModel should exist");
|
||||
|
||||
QStringList modelDnsList;
|
||||
for (int i = 0; i < m_coreController->m_allowedDnsModel->rowCount(); ++i) {
|
||||
modelDnsList.append(m_coreController->m_allowedDnsModel->data(m_coreController->m_allowedDnsModel->index(i, 0), AllowedDnsModel::IpRole).toString());
|
||||
}
|
||||
QVERIFY2(modelDnsList == dnsList, "Allowed DNS servers should be available in AllowedDnsModel");
|
||||
}
|
||||
|
||||
void testAmneziaDnsToggleSignal() {
|
||||
QSignalSpy amneziaDnsToggledSpy(m_coreController->m_settingsUiController, &SettingsUiController::amneziaDnsToggled);
|
||||
QSignalSpy useAmneziaDnsChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::useAmneziaDnsChanged);
|
||||
|
||||
bool initialValue = m_coreController->m_settingsController->isAmneziaDnsEnabled();
|
||||
|
||||
m_coreController->m_settingsUiController->toggleAmneziaDns(!initialValue);
|
||||
QVERIFY2(amneziaDnsToggledSpy.count() == 1, "amneziaDnsToggled signal should be emitted");
|
||||
QVERIFY2(amneziaDnsToggledSpy.at(0).at(0).toBool() == !initialValue, "amneziaDnsToggled should emit correct value");
|
||||
QVERIFY2(useAmneziaDnsChangedSpy.count() == 1, "useAmneziaDnsChanged signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_settingsController->isAmneziaDnsEnabled() == !initialValue, "Amnezia DNS state should be updated in SettingsController");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->isAmneziaDnsEnabled() == !initialValue, "Amnezia DNS state should be available in SettingsUiController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->useAmneziaDns() == !initialValue, "Amnezia DNS state should be available in SecureAppSettingsRepository");
|
||||
|
||||
m_coreController->m_settingsUiController->toggleAmneziaDns(initialValue);
|
||||
QVERIFY2(amneziaDnsToggledSpy.count() == 2, "amneziaDnsToggled signal should be emitted again");
|
||||
QVERIFY2(useAmneziaDnsChangedSpy.count() == 2, "useAmneziaDnsChanged signal should be emitted again");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->isAmneziaDnsEnabled() == initialValue, "Amnezia DNS state should be restored in SettingsUiController");
|
||||
}
|
||||
|
||||
void testLoggingSignals() {
|
||||
QSignalSpy loggingStateChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::loggingStateChanged);
|
||||
QSignalSpy saveLogsChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::saveLogsChanged);
|
||||
|
||||
bool initialLogging = m_coreController->m_settingsController->isLoggingEnabled();
|
||||
|
||||
m_coreController->m_settingsUiController->toggleLogging(!initialLogging);
|
||||
QVERIFY2(loggingStateChangedSpy.count() == 1, "loggingStateChanged signal should be emitted");
|
||||
QVERIFY2(saveLogsChangedSpy.count() == 1, "saveLogsChanged signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_settingsController->isLoggingEnabled() == !initialLogging, "Logging state should be updated in SettingsController");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->isLoggingEnabled() == !initialLogging, "Logging state should be available in SettingsUiController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->isSaveLogs() == !initialLogging, "Logging state should be available in SecureAppSettingsRepository");
|
||||
|
||||
m_coreController->m_settingsUiController->toggleLogging(initialLogging);
|
||||
QVERIFY2(loggingStateChangedSpy.count() == 2, "loggingStateChanged signal should be emitted again");
|
||||
QVERIFY2(saveLogsChangedSpy.count() == 2, "saveLogsChanged signal should be emitted again");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->isLoggingEnabled() == initialLogging, "Logging state should be restored in SettingsUiController");
|
||||
}
|
||||
|
||||
void testScreenshotsSignals() {
|
||||
QSignalSpy screenshotsEnabledChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged);
|
||||
|
||||
bool initialScreenshots = m_coreController->m_settingsController->isScreenshotsEnabled();
|
||||
|
||||
m_coreController->m_settingsUiController->toggleScreenshotsEnabled(!initialScreenshots);
|
||||
QVERIFY2(screenshotsEnabledChangedSpy.count() == 1, "screenshotsEnabledChanged signal should be emitted");
|
||||
QVERIFY2(screenshotsEnabledChangedSpy.at(0).at(0).toBool() == !initialScreenshots, "screenshotsEnabledChanged should emit correct value");
|
||||
QVERIFY2(m_coreController->m_settingsController->isScreenshotsEnabled() == !initialScreenshots, "Screenshots state should be updated in SettingsController");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->isScreenshotsEnabled() == !initialScreenshots, "Screenshots state should be available in SettingsUiController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->isScreenshotsEnabled() == !initialScreenshots, "Screenshots state should be available in SecureAppSettingsRepository");
|
||||
}
|
||||
|
||||
void testStartMinimizedSignals() {
|
||||
QSignalSpy startMinimizedChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::startMinimizedChanged);
|
||||
|
||||
m_coreController->m_settingsUiController->toggleAutoStart(true);
|
||||
|
||||
bool initialStartMinimized = m_coreController->m_settingsController->isStartMinimizedEnabled();
|
||||
|
||||
m_coreController->m_settingsUiController->toggleStartMinimized(!initialStartMinimized);
|
||||
QVERIFY2(startMinimizedChangedSpy.count() == 1, "startMinimizedChanged signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_settingsController->isStartMinimizedEnabled() == !initialStartMinimized, "Start minimized state should be updated in SettingsController");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->isStartMinimizedEnabled() == !initialStartMinimized, "Start minimized state should be available in SettingsUiController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->isStartMinimized() == !initialStartMinimized, "Start minimized state should be available in SecureAppSettingsRepository");
|
||||
|
||||
m_coreController->m_settingsUiController->toggleAutoStart(false);
|
||||
}
|
||||
|
||||
void testAutoStartDisablesStartMinimizedUi() {
|
||||
QSignalSpy startMinimizedChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::startMinimizedChanged);
|
||||
|
||||
m_coreController->m_settingsUiController->toggleAutoStart(true);
|
||||
m_coreController->m_settingsUiController->toggleStartMinimized(true);
|
||||
QVERIFY2(m_coreController->m_settingsUiController->isStartMinimizedEnabled(), "Start minimized should be enabled with autostart");
|
||||
|
||||
m_coreController->m_settingsUiController->toggleAutoStart(false);
|
||||
QVERIFY2(startMinimizedChangedSpy.count() >= 1, "startMinimizedChanged signal should be emitted when autostart is disabled");
|
||||
QVERIFY2(!m_coreController->m_settingsUiController->isStartMinimizedEnabled(), "Start minimized should be disabled when autostart is disabled");
|
||||
QVERIFY2(!m_coreController->m_appSettingsRepository->isStartMinimized(), "Start minimized setting should be cleared when autostart is disabled");
|
||||
}
|
||||
|
||||
void testAutoConnectSignals() {
|
||||
bool initialAutoConnect = m_coreController->m_settingsController->isAutoConnectEnabled();
|
||||
|
||||
m_coreController->m_settingsUiController->toggleAutoConnect(!initialAutoConnect);
|
||||
QVERIFY2(m_coreController->m_settingsController->isAutoConnectEnabled() == !initialAutoConnect, "Auto connect state should be updated in SettingsController");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->isAutoConnectEnabled() == !initialAutoConnect, "Auto connect state should be available in SettingsUiController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->isAutoConnect() == !initialAutoConnect, "Auto connect state should be available in SecureAppSettingsRepository");
|
||||
|
||||
m_coreController->m_settingsUiController->toggleAutoConnect(initialAutoConnect);
|
||||
QVERIFY2(m_coreController->m_settingsController->isAutoConnectEnabled() == initialAutoConnect, "Auto connect state should be restored in SettingsController");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->isAutoConnectEnabled() == initialAutoConnect, "Auto connect state should be restored in SettingsUiController");
|
||||
}
|
||||
|
||||
void testLanguageChangeSignals() {
|
||||
QSignalSpy appLanguageChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appLanguageChanged);
|
||||
QSignalSpy translationsUpdatedSpy(m_coreController->m_languageUiController, &LanguageUiController::translationsUpdated);
|
||||
|
||||
QLocale initialLocale = m_coreController->m_settingsController->getAppLanguage();
|
||||
QLocale newLocale = (initialLocale.language() == QLocale::English) ? QLocale::Russian : QLocale::English;
|
||||
|
||||
m_coreController->m_settingsController->setAppLanguage(newLocale);
|
||||
QVERIFY2(appLanguageChangedSpy.count() == 1, "appLanguageChanged signal should be emitted");
|
||||
QVERIFY2(appLanguageChangedSpy.at(0).at(0).value<QLocale>() == newLocale, "appLanguageChanged should emit correct locale");
|
||||
QVERIFY2(m_coreController->m_settingsController->getAppLanguage() == newLocale, "App language should be updated in SettingsController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->getAppLanguage() == newLocale, "App language should be available in SecureAppSettingsRepository");
|
||||
|
||||
if (m_coreController->m_languageModel) {
|
||||
QString newLanguageName = m_coreController->m_languageUiController->getCurrentLanguageName();
|
||||
QVERIFY2(!newLanguageName.isEmpty(), "Language name should be available in LanguageUiController");
|
||||
}
|
||||
}
|
||||
|
||||
void testGatewayEndpointSignals() {
|
||||
QSignalSpy gatewayEndpointChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::gatewayEndpointChanged);
|
||||
QSignalSpy devGatewayEnvChangedSpy(m_coreController->m_settingsUiController, &SettingsUiController::devGatewayEnvChanged);
|
||||
|
||||
QString initialEndpoint = m_coreController->m_settingsController->getGatewayEndpoint();
|
||||
QString newEndpoint = "https://test-gateway.example.com";
|
||||
|
||||
m_coreController->m_settingsUiController->setGatewayEndpoint(newEndpoint);
|
||||
QVERIFY2(gatewayEndpointChangedSpy.count() == 1, "gatewayEndpointChanged signal should be emitted");
|
||||
QVERIFY2(gatewayEndpointChangedSpy.at(0).at(0).toString() == newEndpoint, "gatewayEndpointChanged should emit correct endpoint");
|
||||
QVERIFY2(m_coreController->m_settingsController->getGatewayEndpoint() == newEndpoint, "Gateway endpoint should be updated in SettingsController");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->getGatewayEndpoint() == newEndpoint, "Gateway endpoint should be available in SettingsUiController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->getGatewayEndpoint() == newEndpoint, "Gateway endpoint should be available in SecureAppSettingsRepository");
|
||||
|
||||
bool initialDevEnv = m_coreController->m_settingsController->isDevGatewayEnv();
|
||||
m_coreController->m_settingsUiController->toggleDevGatewayEnv(!initialDevEnv);
|
||||
QVERIFY2(devGatewayEnvChangedSpy.count() == 1, "devGatewayEnvChanged signal should be emitted");
|
||||
QVERIFY2(devGatewayEnvChangedSpy.at(0).at(0).toBool() == !initialDevEnv, "devGatewayEnvChanged should emit correct value");
|
||||
QVERIFY2(m_coreController->m_settingsController->isDevGatewayEnv() == !initialDevEnv, "Dev gateway env state should be updated in SettingsController");
|
||||
QVERIFY2(m_coreController->m_settingsUiController->isDevGatewayEnv() == !initialDevEnv, "Dev gateway env state should be available in SettingsUiController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->isDevGatewayEnv() == !initialDevEnv, "Dev gateway env state should be available in SecureAppSettingsRepository");
|
||||
}
|
||||
|
||||
void testSettingsClearedSignal() {
|
||||
QSignalSpy settingsClearedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::settingsCleared);
|
||||
|
||||
m_coreController->m_settingsController->clearSettings();
|
||||
QVERIFY2(settingsClearedSpy.count() == 1, "settingsCleared signal should be emitted");
|
||||
}
|
||||
|
||||
void testSplitTunnelingSignals() {
|
||||
QSignalSpy siteSplitTunnelingToggledSpy(m_coreController->m_settingsController, &SettingsController::siteSplitTunnelingToggled);
|
||||
QSignalSpy appSplitTunnelingToggledSpy(m_coreController->m_settingsController, &SettingsController::appSplitTunnelingToggled);
|
||||
QSignalSpy sitesSplitTunnelingEnabledChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::sitesSplitTunnelingEnabledChanged);
|
||||
QSignalSpy appsSplitTunnelingEnabledChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsSplitTunnelingEnabledChanged);
|
||||
QSignalSpy routeModeChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::routeModeChanged);
|
||||
QSignalSpy appsRouteModeChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsRouteModeChanged);
|
||||
QSignalSpy sitesChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::sitesChanged);
|
||||
QSignalSpy appsChangedSpy(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsChanged);
|
||||
|
||||
bool initialSitesSplitTunneling = m_coreController->m_ipSplitTunnelingController->isSplitTunnelingEnabled();
|
||||
m_coreController->m_ipSplitTunnelingController->toggleSplitTunneling(!initialSitesSplitTunneling);
|
||||
QVERIFY2(sitesSplitTunnelingEnabledChangedSpy.count() == 1, "sitesSplitTunnelingEnabledChanged signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_ipSplitTunnelingController->isSplitTunnelingEnabled() == !initialSitesSplitTunneling, "Sites split tunneling should be updated in IpSplitTunnelingController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->isSitesSplitTunnelingEnabled() == !initialSitesSplitTunneling, "Sites split tunneling should be available in SecureAppSettingsRepository");
|
||||
|
||||
bool initialAppsSplitTunneling = m_coreController->m_appSplitTunnelingController->isSplitTunnelingEnabled();
|
||||
m_coreController->m_appSplitTunnelingController->toggleSplitTunneling(!initialAppsSplitTunneling);
|
||||
QVERIFY2(appsSplitTunnelingEnabledChangedSpy.count() == 1, "appsSplitTunnelingEnabledChanged signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_appSplitTunnelingController->isSplitTunnelingEnabled() == !initialAppsSplitTunneling, "Apps split tunneling should be updated in AppSplitTunnelingController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->isAppsSplitTunnelingEnabled() == !initialAppsSplitTunneling, "Apps split tunneling should be available in SecureAppSettingsRepository");
|
||||
|
||||
RouteMode initialRouteMode = m_coreController->m_ipSplitTunnelingController->getRouteMode();
|
||||
RouteMode newRouteMode = (initialRouteMode == RouteMode::VpnOnlyForwardSites)
|
||||
? RouteMode::VpnAllExceptSites
|
||||
: RouteMode::VpnOnlyForwardSites;
|
||||
m_coreController->m_ipSplitTunnelingController->setRouteMode(newRouteMode);
|
||||
QVERIFY2(routeModeChangedSpy.count() == 1, "routeModeChanged signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_ipSplitTunnelingController->getRouteMode() == newRouteMode, "Route mode should be updated in IpSplitTunnelingController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->routeMode() == newRouteMode, "Route mode should be available in SecureAppSettingsRepository");
|
||||
|
||||
AppsRouteMode initialAppsRouteMode = m_coreController->m_appSplitTunnelingController->getRouteMode();
|
||||
AppsRouteMode newAppsRouteMode = (initialAppsRouteMode == AppsRouteMode::VpnAllExceptApps)
|
||||
? AppsRouteMode::VpnAllApps
|
||||
: AppsRouteMode::VpnAllExceptApps;
|
||||
m_coreController->m_appSplitTunnelingController->setRouteMode(newAppsRouteMode);
|
||||
QVERIFY2(appsRouteModeChangedSpy.count() == 1, "appsRouteModeChanged signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_appSplitTunnelingController->getRouteMode() == newAppsRouteMode, "Apps route mode should be updated in AppSplitTunnelingController");
|
||||
QVERIFY2(m_coreController->m_appSettingsRepository->appsRouteMode() == newAppsRouteMode, "Apps route mode should be available in SecureAppSettingsRepository");
|
||||
|
||||
QMap<QString, QString> sitesMap{{"example.com", "1.2.3.4"}};
|
||||
m_coreController->m_ipSplitTunnelingController->addSites(sitesMap, true);
|
||||
QVERIFY2(sitesChangedSpy.count() >= 1, "sitesChanged signal should be emitted");
|
||||
QVector<QPair<QString, QString>> currentSites = m_coreController->m_ipSplitTunnelingController->getCurrentSites();
|
||||
QVERIFY2(currentSites.size() >= 1, "Sites should be available in IpSplitTunnelingController");
|
||||
|
||||
QVERIFY2(m_coreController->m_ipSplitTunnelingUiController != nullptr, "IpSplitTunnelingUiController should exist");
|
||||
QVERIFY2(m_coreController->m_ipSplitTunnelingModel != nullptr, "IpSplitTunnelingModel should exist");
|
||||
|
||||
m_coreController->m_ipSplitTunnelingUiController->updateModel();
|
||||
QVERIFY2(m_coreController->m_ipSplitTunnelingModel->rowCount() >= 1, "Sites should be available in IpSplitTunnelingModel");
|
||||
QString modelUrl = m_coreController->m_ipSplitTunnelingModel->data(m_coreController->m_ipSplitTunnelingModel->index(0, 0), IpSplitTunnelingModel::UrlRole).toString();
|
||||
QVERIFY2(modelUrl == "example.com", "Site URL should be available in IpSplitTunnelingModel");
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestSettingsSignals)
|
||||
#include "testSettingsSignals.moc"
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class TestSignalOrder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
m_coreController->m_serversRepository->invalidateCache();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), QString());
|
||||
}
|
||||
}
|
||||
|
||||
void testSignalOrderOnImport() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
|
||||
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
|
||||
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
|
||||
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult.config);
|
||||
|
||||
QVERIFY2(importFinishedSpy.count() == 1, "importFinished signal should be emitted");
|
||||
QVERIFY2(serverAddedSpy.count() == 1, "serverAdded signal should be emitted");
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged signal should NOT be emitted (default is already 0)");
|
||||
|
||||
QVERIFY2(serverAddedSpy.at(0).count() > 0, "serverAdded should have arguments");
|
||||
}
|
||||
|
||||
void testSignalOrderOnRemoveDefault() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
|
||||
|
||||
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult1.config);
|
||||
auto importResult2 = m_coreController->m_importCoreController->extractConfigFromData(xrayKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult2.config);
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default should be index 1");
|
||||
|
||||
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(1));
|
||||
|
||||
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 1, "defaultServerChanged signal should be emitted when removing default server");
|
||||
QVERIFY2(defaultServerChangedSpy.at(0).at(0).toString() == m_coreController->m_serversRepository->defaultServerId(),
|
||||
"defaultServerChanged should emit new default server id");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default server index should be 0");
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestSignalOrder)
|
||||
#include "testSignalOrder.moc"
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QDebug>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
#include <QModelIndex>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
#include "core/controllers/selfhosted/importController.h"
|
||||
#include "ui/models/serversModel.h"
|
||||
#include "ui/models/containersModel.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
|
||||
using namespace amnezia;
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class TestUiServersModelAndController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
QJsonObject createAwg2Config()
|
||||
{
|
||||
QJsonObject clientConfig;
|
||||
clientConfig[configKey::mtu] = protocols::awg::defaultMtu;
|
||||
clientConfig[configKey::junkPacketCount] = protocols::awg::defaultJunkPacketCount;
|
||||
clientConfig[configKey::junkPacketMinSize] = protocols::awg::defaultJunkPacketMinSize;
|
||||
clientConfig[configKey::junkPacketMaxSize] = protocols::awg::defaultJunkPacketMaxSize;
|
||||
clientConfig[configKey::specialJunk1] = protocols::awg::defaultSpecialJunk1;
|
||||
clientConfig[configKey::specialJunk2] = protocols::awg::defaultSpecialJunk2;
|
||||
clientConfig[configKey::specialJunk3] = protocols::awg::defaultSpecialJunk3;
|
||||
clientConfig[configKey::specialJunk4] = protocols::awg::defaultSpecialJunk4;
|
||||
clientConfig[configKey::specialJunk5] = protocols::awg::defaultSpecialJunk5;
|
||||
clientConfig[configKey::clientPrivKey] = "test_client_private_key";
|
||||
clientConfig[configKey::clientPubKey] = "test_client_public_key";
|
||||
clientConfig[configKey::serverPubKey] = "test_server_public_key";
|
||||
clientConfig[configKey::pskKey] = "test_psk_key";
|
||||
clientConfig[configKey::clientIp] = "10.8.1.2";
|
||||
clientConfig[configKey::allowedIps] = QJsonArray::fromStringList({"0.0.0.0/0"});
|
||||
|
||||
QJsonObject awgConfig;
|
||||
awgConfig[configKey::lastConfig] = QString(QJsonDocument(clientConfig).toJson());
|
||||
awgConfig[configKey::port] = protocols::awg::defaultPort;
|
||||
awgConfig[configKey::transportProto] = "udp";
|
||||
awgConfig[configKey::protocolVersion] = protocols::awg::awgV2;
|
||||
awgConfig[configKey::subnetAddress] = protocols::wireguard::defaultSubnetAddress;
|
||||
awgConfig[configKey::junkPacketCount] = protocols::awg::defaultJunkPacketCount;
|
||||
awgConfig[configKey::junkPacketMinSize] = protocols::awg::defaultJunkPacketMinSize;
|
||||
awgConfig[configKey::junkPacketMaxSize] = protocols::awg::defaultJunkPacketMaxSize;
|
||||
awgConfig[configKey::initPacketJunkSize] = protocols::awg::defaultInitPacketJunkSize;
|
||||
awgConfig[configKey::responsePacketJunkSize] = protocols::awg::defaultResponsePacketJunkSize;
|
||||
awgConfig[configKey::cookieReplyPacketJunkSize] = protocols::awg::defaultCookieReplyPacketJunkSize;
|
||||
awgConfig[configKey::transportPacketJunkSize] = protocols::awg::defaultTransportPacketJunkSize;
|
||||
awgConfig[configKey::initPacketMagicHeader] = protocols::awg::defaultInitPacketMagicHeader;
|
||||
awgConfig[configKey::responsePacketMagicHeader] = protocols::awg::defaultResponsePacketMagicHeader;
|
||||
awgConfig[configKey::underloadPacketMagicHeader] = protocols::awg::defaultUnderloadPacketMagicHeader;
|
||||
awgConfig[configKey::transportPacketMagicHeader] = protocols::awg::defaultTransportPacketMagicHeader;
|
||||
awgConfig[configKey::specialJunk1] = protocols::awg::defaultSpecialJunk1;
|
||||
awgConfig[configKey::specialJunk2] = protocols::awg::defaultSpecialJunk2;
|
||||
awgConfig[configKey::specialJunk3] = protocols::awg::defaultSpecialJunk3;
|
||||
awgConfig[configKey::specialJunk4] = protocols::awg::defaultSpecialJunk4;
|
||||
awgConfig[configKey::specialJunk5] = protocols::awg::defaultSpecialJunk5;
|
||||
awgConfig[configKey::isThirdPartyConfig] = true;
|
||||
|
||||
QJsonObject container;
|
||||
container[configKey::container] = "amnezia-awg";
|
||||
container[configKey::awg] = awgConfig;
|
||||
|
||||
QJsonArray containers;
|
||||
containers.append(container);
|
||||
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = containers;
|
||||
config[configKey::defaultContainer] = "amnezia-awg";
|
||||
config[configKey::description] = "AWG2 Test Server";
|
||||
config[configKey::hostName] = "test.example.com";
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
QJsonObject createServerDescriptionTestConfig(bool withAmneziaDns)
|
||||
{
|
||||
QJsonObject config = createAwg2Config();
|
||||
config[configKey::description] = "Server 1";
|
||||
if (withAmneziaDns) {
|
||||
config[configKey::dns1] = protocols::dns::amneziaDnsIp;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), QString());
|
||||
}
|
||||
}
|
||||
|
||||
void testUiServersModelAndControllerRoles() {
|
||||
QJsonObject testConfig = createAwg2Config();
|
||||
|
||||
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
|
||||
|
||||
m_coreController->m_importCoreController->importConfig(testConfig);
|
||||
|
||||
QVERIFY2(importFinishedSpy.count() == 1, "importFinished signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "Server should be imported");
|
||||
|
||||
int serverIndex = m_coreController->m_serversRepository->defaultServerIndex();
|
||||
QVERIFY2(serverIndex == 0, "Default server index should be 0");
|
||||
|
||||
if (m_coreController->m_serversModel) {
|
||||
QVERIFY2(m_coreController->m_serversModel->rowCount() == 1, "ServersModel should have 1 row");
|
||||
|
||||
QModelIndex serverModelIndex = m_coreController->m_serversModel->index(0, 0);
|
||||
QVERIFY2(serverModelIndex.isValid(), "Server model index should be valid");
|
||||
|
||||
QString serverName = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::NameRole).toString();
|
||||
QVERIFY2(serverName == "AWG2 Test Server", QString("Server name should be 'AWG2 Test Server', got '%1'").arg(serverName).toUtf8().constData());
|
||||
|
||||
QString serverDescription = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::ServerDescriptionRole).toString();
|
||||
QVERIFY2(serverDescription.contains("test.example.com"), QString("Server description should contain hostname, got '%1'").arg(serverDescription).toUtf8().constData());
|
||||
|
||||
QString hostName = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::HostNameRole).toString();
|
||||
QVERIFY2(hostName == "test.example.com", "Host name should match");
|
||||
|
||||
bool isDefault = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::IsDefaultRole).toBool();
|
||||
QVERIFY2(isDefault == true, "Server should be default");
|
||||
|
||||
bool hasInstalledContainers = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::HasInstalledContainers).toBool();
|
||||
QVERIFY2(hasInstalledContainers == true, "Server should have installed containers");
|
||||
|
||||
bool hasWriteAccess = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::HasWriteAccessRole).toBool();
|
||||
QVERIFY2(hasWriteAccess == false, "Server should not have write access for imported config");
|
||||
|
||||
int defaultContainerRole = m_coreController->m_serversModel->data(serverModelIndex, ServersModel::DefaultContainerRole).toInt();
|
||||
DockerContainer expectedContainer = DockerContainer::Awg;
|
||||
QVERIFY2(defaultContainerRole == static_cast<int>(expectedContainer), "Default container should be Awg");
|
||||
}
|
||||
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(
|
||||
m_coreController->m_serversUiController->getServerId(0));
|
||||
|
||||
QString hostName = "test.example.com";
|
||||
|
||||
QString collapsedDescription = m_coreController->m_serversUiController->getDefaultServerDescriptionCollapsed();
|
||||
QString expectedCollapsed = "AmneziaWG (version 2) | " + hostName;
|
||||
QVERIFY2(collapsedDescription == expectedCollapsed,
|
||||
QString("Collapsed description should be '%1', got '%2'").arg(expectedCollapsed, collapsedDescription).toUtf8().constData());
|
||||
|
||||
QString expandedDescription = m_coreController->m_serversUiController->getDefaultServerDescriptionExpanded();
|
||||
QString expectedExpanded = hostName;
|
||||
QVERIFY2(expandedDescription == expectedExpanded,
|
||||
QString("Expanded description should be '%1', got '%2'").arg(expectedExpanded, expandedDescription).toUtf8().constData());
|
||||
}
|
||||
|
||||
if (m_coreController->m_containersModel) {
|
||||
|
||||
int awgContainerIndex = -1;
|
||||
for (int i = 0; i < ContainerUtils::allContainers().size(); ++i) {
|
||||
DockerContainer container = ContainerUtils::allContainers().at(i);
|
||||
if (container == DockerContainer::Awg) {
|
||||
awgContainerIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QVERIFY2(awgContainerIndex >= 0, "Awg container index should be found");
|
||||
|
||||
QModelIndex containerModelIndex = m_coreController->m_containersModel->index(awgContainerIndex, 0);
|
||||
QVERIFY2(containerModelIndex.isValid(), "Container model index should be valid");
|
||||
|
||||
bool isInstalled = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::IsInstalledRole).toBool();
|
||||
QVERIFY2(isInstalled == true, "Awg container should be installed");
|
||||
|
||||
bool isVpnContainer = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::IsVpnContainerRole).toBool();
|
||||
QVERIFY2(isVpnContainer == true, "Awg container should be VPN container");
|
||||
|
||||
QString containerName = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::NameRole).toString();
|
||||
QString expectedContainerName = ContainerUtils::containerHumanNames().value(DockerContainer::Awg);
|
||||
QVERIFY2(containerName == expectedContainerName, QString("Container name should be '%1', got '%2'").arg(expectedContainerName, containerName).toUtf8().constData());
|
||||
|
||||
QString containerDescription = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::DescriptionRole).toString();
|
||||
QString expectedDescription = ContainerUtils::containerDescriptions().value(DockerContainer::Awg);
|
||||
QVERIFY2(containerDescription == expectedDescription, QString("Container description should match, got '%1'").arg(containerDescription).toUtf8().constData());
|
||||
|
||||
QString detailedDescription = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::DetailedDescriptionRole).toString();
|
||||
QString expectedDetailedDescription = ContainerUtils::containerDetailedDescriptions().value(DockerContainer::Awg);
|
||||
QVERIFY2(detailedDescription == expectedDetailedDescription, QString("Container detailed description should match, got '%1'").arg(detailedDescription).toUtf8().constData());
|
||||
|
||||
int serviceType = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::ServiceTypeRole).toInt();
|
||||
QVERIFY2(serviceType == static_cast<int>(ProtocolEnumNS::ServiceType::Vpn), "Service type should be Vpn");
|
||||
|
||||
bool isSupported = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::IsSupportedRole).toBool();
|
||||
QVERIFY2(isSupported == true, "Container should be supported");
|
||||
|
||||
bool isShareable = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::IsShareableRole).toBool();
|
||||
QVERIFY2(isShareable == true, "Container should be shareable");
|
||||
|
||||
QJsonObject containerConfig = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::ConfigRole).toJsonObject();
|
||||
QVERIFY2(!containerConfig.isEmpty(), "Container config should not be empty");
|
||||
QVERIFY2(containerConfig.value(configKey::container).toString() == "amnezia-awg", "Container config should have correct container type");
|
||||
|
||||
QJsonObject awgProtocolConfig = containerConfig.value(configKey::awg).toObject();
|
||||
QVERIFY2(!awgProtocolConfig.isEmpty(), "AWG protocol config should not be empty");
|
||||
|
||||
QString protocolVersion = awgProtocolConfig.value(configKey::protocolVersion).toString();
|
||||
QVERIFY2(protocolVersion == protocols::awg::awgV2, QString("Protocol version should be '%1', got '%2'").arg(protocols::awg::awgV2, protocolVersion).toUtf8().constData());
|
||||
|
||||
QString port = awgProtocolConfig.value(configKey::port).toString();
|
||||
QVERIFY2(port == protocols::awg::defaultPort, QString("Port should be '%1', got '%2'").arg(protocols::awg::defaultPort, port).toUtf8().constData());
|
||||
|
||||
QString subnetAddress = awgProtocolConfig.value(configKey::subnetAddress).toString();
|
||||
QVERIFY2(subnetAddress == protocols::wireguard::defaultSubnetAddress, QString("Subnet address should be '%1', got '%2'").arg(protocols::wireguard::defaultSubnetAddress, subnetAddress).toUtf8().constData());
|
||||
|
||||
bool isThirdParty = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::IsThirdPartyConfigRole).toBool();
|
||||
QVERIFY2(isThirdParty == true, "Imported config should be third party config");
|
||||
|
||||
DockerContainer dockerContainer = static_cast<DockerContainer>(m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::DockerContainerRole).toInt());
|
||||
QVERIFY2(dockerContainer == DockerContainer::Awg, "Docker container should be Awg");
|
||||
|
||||
QString containerString = m_coreController->m_containersModel->data(containerModelIndex, ContainersModel::ContainerStringRole).toString();
|
||||
QVERIFY2(containerString == "amnezia-awg", "Container string should be amnezia-awg");
|
||||
}
|
||||
}
|
||||
|
||||
void testServerDescriptionFormat() {
|
||||
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
|
||||
|
||||
QJsonObject configNoDns = createServerDescriptionTestConfig(false);
|
||||
m_coreController->m_importCoreController->importConfig(configNoDns);
|
||||
QVERIFY2(importFinishedSpy.count() == 1, "importFinished should be emitted");
|
||||
m_coreController->m_appSettingsRepository->setUseAmneziaDns(false);
|
||||
QVector<ServerDescription> descriptionsNoDns = m_coreController->m_serversController->buildServerDescriptions(
|
||||
m_coreController->m_appSettingsRepository->useAmneziaDns());
|
||||
const QString defIdNoDns = m_coreController->m_serversRepository->defaultServerId();
|
||||
m_coreController->m_serversModel->updateModel(descriptionsNoDns, defIdNoDns);
|
||||
|
||||
QString descNoDns = m_coreController->m_serversModel->data(
|
||||
m_coreController->m_serversModel->index(0, 0), ServersModel::ServerDescriptionRole).toString();
|
||||
QVERIFY2(descNoDns == "test.example.com",
|
||||
QString("Without Amnezia DNS expected 'test.example.com', got '%1'").arg(descNoDns).toUtf8().constData());
|
||||
|
||||
m_coreController->m_serversRepository->clearServers();
|
||||
if (m_coreController->m_serversRepository->serversCount() > 0) {
|
||||
m_coreController->m_serversRepository->setDefaultServer(m_coreController->m_serversRepository->serverIdAt(0));
|
||||
}
|
||||
|
||||
QJsonObject configWithDns = createServerDescriptionTestConfig(true);
|
||||
m_coreController->m_importCoreController->importConfig(configWithDns);
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "Server should be imported");
|
||||
m_coreController->m_appSettingsRepository->setUseAmneziaDns(true);
|
||||
QVector<ServerDescription> descriptionsWithDns = m_coreController->m_serversController->buildServerDescriptions(
|
||||
m_coreController->m_appSettingsRepository->useAmneziaDns());
|
||||
const QString defIdWithDns = m_coreController->m_serversRepository->defaultServerId();
|
||||
m_coreController->m_serversModel->updateModel(descriptionsWithDns, defIdWithDns);
|
||||
|
||||
QString descWithDns = m_coreController->m_serversModel->data(
|
||||
m_coreController->m_serversModel->index(0, 0), ServersModel::ServerDescriptionRole).toString();
|
||||
QVERIFY2(descWithDns == "Amnezia DNS | test.example.com",
|
||||
QString("With Amnezia DNS expected 'Amnezia DNS | test.example.com', got '%1'").arg(descWithDns).toUtf8().constData());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestUiServersModelAndController)
|
||||
#include "testUiServersModelAndController.moc"
|
||||
@@ -102,6 +102,11 @@ SubscriptionUiController::SubscriptionUiController(ServersController* serversCon
|
||||
});
|
||||
}
|
||||
|
||||
bool SubscriptionUiController::isCaptchaAwaitingUser() const
|
||||
{
|
||||
return m_captchaState.isPending;
|
||||
}
|
||||
|
||||
bool SubscriptionUiController::exportVpnKey(const QString &serverId, const QString &fileName)
|
||||
{
|
||||
if (fileName.isEmpty()) {
|
||||
@@ -288,18 +293,105 @@ bool SubscriptionUiController::importFreeFromGateway()
|
||||
}
|
||||
|
||||
SubscriptionController::ProtocolData protocolData = m_subscriptionController->generateProtocolData(serviceProtocol);
|
||||
SubscriptionController::CaptchaInfo captchaInfo;
|
||||
|
||||
ErrorCode errorCode = m_subscriptionController->importServiceFromGateway(userCountryCode, serviceType,
|
||||
serviceProtocol, protocolData);
|
||||
serviceProtocol, protocolData,
|
||||
captchaInfo);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName()));
|
||||
return true;
|
||||
} else if (errorCode == ErrorCode::ApiCaptchaRequiredError && captchaInfo.isRequired) {
|
||||
m_captchaState.userCountryCode = userCountryCode;
|
||||
m_captchaState.serviceType = serviceType;
|
||||
m_captchaState.serviceProtocol = serviceProtocol;
|
||||
m_captchaState.openvpnPrivKey = protocolData.certPrivKey;
|
||||
m_captchaState.wireguardClientPrivKey = protocolData.wireGuardClientPrivKey;
|
||||
m_captchaState.wireguardClientPubKey = protocolData.wireGuardClientPubKey;
|
||||
m_captchaState.xrayUuid = protocolData.xrayUuid;
|
||||
m_captchaState.isPending = true;
|
||||
|
||||
emit captchaRequired(captchaInfo.captchaId, captchaInfo.captchaImageBase64,
|
||||
captchaInfo.hint.isEmpty() ? tr("Enter the digits from the image to continue") : captchaInfo.hint);
|
||||
return false;
|
||||
} else {
|
||||
emit errorOccurred(errorCode);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void SubscriptionUiController::onCaptchaSolved(const QString &captchaId, const QString &solution)
|
||||
{
|
||||
if (!m_captchaState.isPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
SubscriptionController::ProtocolData protocolData;
|
||||
protocolData.certPrivKey = m_captchaState.openvpnPrivKey;
|
||||
protocolData.wireGuardClientPrivKey = m_captchaState.wireguardClientPrivKey;
|
||||
protocolData.wireGuardClientPubKey = m_captchaState.wireguardClientPubKey;
|
||||
protocolData.xrayUuid = m_captchaState.xrayUuid;
|
||||
|
||||
SubscriptionController::CaptchaInfo retryCaptcha;
|
||||
ErrorCode errorCode = m_subscriptionController->resolveImportServiceCaptcha(
|
||||
m_captchaState.userCountryCode,
|
||||
m_captchaState.serviceType,
|
||||
m_captchaState.serviceProtocol,
|
||||
protocolData,
|
||||
captchaId,
|
||||
solution,
|
||||
&retryCaptcha);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
m_captchaState.isPending = false;
|
||||
emit captchaFlowDismissRequested();
|
||||
emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName()));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|
||||
|| errorCode == ErrorCode::ApiCaptchaRequiredError)
|
||||
&& retryCaptcha.isRequired) {
|
||||
emit captchaRequired(retryCaptcha.captchaId, retryCaptcha.captchaImageBase64,
|
||||
retryCaptcha.hint.isEmpty() ? tr("Enter the digits from the image to continue") : retryCaptcha.hint);
|
||||
return;
|
||||
}
|
||||
|
||||
m_captchaState.isPending = false;
|
||||
emit errorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void SubscriptionUiController::onRefreshCaptchaRequested()
|
||||
{
|
||||
if (!m_captchaState.isPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
SubscriptionController::ProtocolData protocolData;
|
||||
protocolData.certPrivKey = m_captchaState.openvpnPrivKey;
|
||||
protocolData.wireGuardClientPrivKey = m_captchaState.wireguardClientPrivKey;
|
||||
protocolData.wireGuardClientPubKey = m_captchaState.wireguardClientPubKey;
|
||||
protocolData.xrayUuid = m_captchaState.xrayUuid;
|
||||
|
||||
SubscriptionController::CaptchaInfo captchaInfo;
|
||||
|
||||
ErrorCode errorCode = m_subscriptionController->importServiceFromGateway(
|
||||
m_captchaState.userCountryCode,
|
||||
m_captchaState.serviceType,
|
||||
m_captchaState.serviceProtocol,
|
||||
protocolData,
|
||||
captchaInfo);
|
||||
|
||||
if (errorCode == ErrorCode::ApiCaptchaRequiredError && captchaInfo.isRequired) {
|
||||
emit captchaRequired(captchaInfo.captchaId, captchaInfo.captchaImageBase64,
|
||||
captchaInfo.hint.isEmpty() ? tr("Enter the digits from the image to continue") : captchaInfo.hint);
|
||||
} else if (errorCode != ErrorCode::NoError) {
|
||||
m_captchaState.isPending = false;
|
||||
emit errorOccurred(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
bool SubscriptionUiController::importTrialFromGateway(const QString &email)
|
||||
{
|
||||
emit trialEmailError(QString());
|
||||
@@ -383,8 +475,7 @@ bool SubscriptionUiController::deactivateExternalDevice(const QString &serverId,
|
||||
void SubscriptionUiController::validateConfig()
|
||||
{
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (!serverId.isEmpty() && m_serversController->isLegacyApiV1Server(serverId)) {
|
||||
emit unsupportedConnectDrawerRequested();
|
||||
if (serverId.isEmpty()) {
|
||||
emit configValidated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,10 @@ public slots:
|
||||
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
|
||||
bool isVlessProtocol(const QString &serverId);
|
||||
|
||||
bool isCaptchaAwaitingUser() const;
|
||||
void onCaptchaSolved(const QString &captchaId, const QString &solution);
|
||||
void onRefreshCaptchaRequested();
|
||||
|
||||
void removeApiConfig(const QString &serverId);
|
||||
|
||||
void removeServer(const QString &serverId);
|
||||
@@ -85,9 +89,23 @@ signals:
|
||||
void apiServerRemoved(const QString &message);
|
||||
|
||||
void vpnKeyExportReady();
|
||||
void captchaRequired(const QString &captchaId, const QString &captchaImageBase64, const QString &hint);
|
||||
void captchaFlowDismissRequested();
|
||||
|
||||
void unsupportedConnectDrawerRequested();
|
||||
|
||||
private:
|
||||
struct CaptchaState {
|
||||
QString userCountryCode;
|
||||
QString serviceType;
|
||||
QString serviceProtocol;
|
||||
QString openvpnPrivKey;
|
||||
QString wireguardClientPrivKey;
|
||||
QString wireguardClientPubKey;
|
||||
QString xrayUuid;
|
||||
bool isPending = false;
|
||||
} m_captchaState;
|
||||
|
||||
private:
|
||||
QList<QString> getQrCodes();
|
||||
int getQrCodesCount();
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
|
||||
ServersController* serversController,
|
||||
@@ -33,7 +35,7 @@ void ConnectionUiController::openConnection()
|
||||
ErrorCode errorCode = m_connectionController->openConnection(serverId);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit connectionErrorOccurred(errorCode);
|
||||
notifyConnectionBlocked(errorCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -130,10 +132,36 @@ void ConnectionUiController::toggleConnection()
|
||||
} else if (isConnected()) {
|
||||
closeConnection();
|
||||
} else {
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ErrorCode errorCode = m_connectionController->isConnectionSupported(serverId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
notifyConnectionBlocked(errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
emit prepareConfig();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionUiController::notifyConnectionBlocked(ErrorCode errorCode)
|
||||
{
|
||||
if (errorCode == ErrorCode::LegacyApiV1NotSupportedError) {
|
||||
emit unsupportedConnectDrawerRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorCode == ErrorCode::NoInstalledContainersError) {
|
||||
emit noInstalledContainers();
|
||||
return;
|
||||
}
|
||||
|
||||
emit connectionErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
bool ConnectionUiController::isConnectionInProgress() const
|
||||
{
|
||||
return m_isConnectionInProgress;
|
||||
@@ -143,3 +171,32 @@ bool ConnectionUiController::isConnected() const
|
||||
{
|
||||
return m_isConnected;
|
||||
}
|
||||
|
||||
bool ConnectionUiController::isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex,
|
||||
const QString &clientId) const
|
||||
{
|
||||
if (clientId.isEmpty() || (!isConnected() && !isConnectionInProgress())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_serversController->getDefaultServerId() != serverId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static_cast<int>(m_serversController->getDefaultContainer(serverId)) != containerIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto adminConfig = m_serversController->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString connectionClientId =
|
||||
adminConfig->containerConfig(static_cast<DockerContainer>(containerIndex)).protocolConfig.clientId();
|
||||
if (connectionClientId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return connectionClientId == clientId || connectionClientId.contains(clientId);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ public slots:
|
||||
void openConnection();
|
||||
void closeConnection();
|
||||
|
||||
bool isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex, const QString &clientId) const;
|
||||
|
||||
ErrorCode getLastConnectionError();
|
||||
void onConnectionStateChanged(Vpn::ConnectionState state);
|
||||
|
||||
@@ -48,9 +50,12 @@ signals:
|
||||
void connectButtonClicked();
|
||||
void preparingConfig();
|
||||
void prepareConfig();
|
||||
void unsupportedConnectDrawerRequested();
|
||||
void noInstalledContainers();
|
||||
|
||||
private:
|
||||
Vpn::ConnectionState getCurrentConnectionState();
|
||||
void notifyConnectionBlocked(ErrorCode errorCode);
|
||||
|
||||
ConnectionController* m_connectionController;
|
||||
ServersController* m_serversController;
|
||||
|
||||
@@ -12,7 +12,6 @@ LanguageUiController::LanguageUiController(SettingsController* settingsControlle
|
||||
void LanguageUiController::onAppLanguageChanged(const QLocale &locale)
|
||||
{
|
||||
emit updateTranslations(locale);
|
||||
emit translationsUpdated();
|
||||
}
|
||||
|
||||
void LanguageUiController::changeLanguage(const LanguageSettings::AvailableLanguageEnum language)
|
||||
|
||||
@@ -75,13 +75,7 @@ InstallUiController::InstallUiController(InstallController *installController,
|
||||
m_connectionController(connectionController)
|
||||
{
|
||||
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) {
|
||||
if (errorCode == ErrorCode::NoInstalledContainersError) {
|
||||
emit noInstalledContainers();
|
||||
} else {
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
});
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, &InstallUiController::installationErrorOccurred);
|
||||
}
|
||||
|
||||
InstallUiController::~InstallUiController()
|
||||
@@ -217,15 +211,13 @@ void InstallUiController::scanServerForInstalledContainers(const QString &server
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
|
||||
containerConfig.container = container;
|
||||
|
||||
|
||||
switch (protocolType) {
|
||||
case Proto::Awg: {
|
||||
containerConfig.protocolConfig = m_awgConfigModel->getProtocolConfig();
|
||||
@@ -271,6 +263,41 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallUiController::updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateClientConfig(serverId, container, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
m_protocolModel->updateModel(updatedConfig);
|
||||
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolType));
|
||||
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
||||
return;
|
||||
}
|
||||
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
@@ -305,13 +332,13 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
||||
QFuture<ErrorCode> future =
|
||||
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
|
||||
newConfigCopy]() mutable -> ErrorCode {
|
||||
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
return installController->updateServerConfig(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateContainer(serverId, container, oldContainerConfig, containerConfig);
|
||||
ErrorCode errorCode = m_installController->updateServerConfig(serverId, container, oldContainerConfig, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
|
||||
@@ -64,7 +64,8 @@ public slots:
|
||||
|
||||
void scanServerForInstalledContainers(const QString &serverId);
|
||||
|
||||
void updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
|
||||
void removeServer(const QString &serverId);
|
||||
void rebootServer(const QString &serverId);
|
||||
@@ -132,7 +133,6 @@ signals:
|
||||
void cachedProfileCleared(const QString &message);
|
||||
void apiConfigRemoved(const QString &message);
|
||||
|
||||
void noInstalledContainers();
|
||||
void configValidated(bool isValid);
|
||||
|
||||
private:
|
||||
@@ -162,6 +162,8 @@ private:
|
||||
QString m_privateKeyPassphrase;
|
||||
|
||||
void updateProtocolConfigModel(const QString &serverId, int containerIndex, int protocolIndex);
|
||||
|
||||
bool buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig);
|
||||
};
|
||||
|
||||
#endif // INSTALLUICONTROLLER_H
|
||||
|
||||
@@ -156,7 +156,17 @@ void ServersUiController::updateModel()
|
||||
|
||||
m_serversModel->updateModel(m_orderedServerDescriptions, defaultServerId);
|
||||
|
||||
updateContainersModel();
|
||||
if (!m_processedServerId.isEmpty()) {
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
} else {
|
||||
updateContainersModel();
|
||||
}
|
||||
}
|
||||
updateDefaultServerContainersModel();
|
||||
|
||||
if (hadServersFromGatewayBefore != hasServersFromGatewayNow) {
|
||||
@@ -350,19 +360,14 @@ void ServersUiController::setProcessedServerId(const QString &serverId)
|
||||
m_processedServerId = normalizedServerId;
|
||||
|
||||
if (newIndex >= 0) {
|
||||
updateContainersModel();
|
||||
|
||||
for (const auto &description : m_orderedServerDescriptions) {
|
||||
if (description.serverId != normalizedServerId) {
|
||||
continue;
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
if (description.isApiV2) {
|
||||
if (description.isCountrySelectionAvailable && !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
emit updateApiServicesModel();
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
updateContainersModel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,6 @@ signals:
|
||||
void processedContainerIndexChanged(int index);
|
||||
void hasServersFromGatewayApiChanged();
|
||||
void updateApiCountryModel();
|
||||
void updateApiServicesModel();
|
||||
|
||||
public:
|
||||
void updateModel();
|
||||
|
||||
@@ -22,12 +22,10 @@
|
||||
|
||||
SettingsUiController::SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
m_settingsController(settingsController),
|
||||
m_serversController(serversController),
|
||||
m_languageUiController(languageUiController)
|
||||
m_serversController(serversController)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsUiController::onNotificationStateChanged);
|
||||
@@ -157,13 +155,13 @@ void SettingsUiController::restoreAppConfigFromData(const QByteArray &data)
|
||||
{
|
||||
ErrorCode errorCode = m_settingsController->restoreAppConfigFromData(data);
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
emit appLanguageChanged(
|
||||
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageUiController->getCurrentLanguageIndex()));
|
||||
emit appLanguageChanged();
|
||||
|
||||
bool amneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled();
|
||||
emit amneziaDnsToggled(amneziaDnsEnabled);
|
||||
|
||||
emit restoreBackupFinished();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
} else {
|
||||
emit errorOccurred(errorCode);
|
||||
@@ -178,6 +176,7 @@ QString SettingsUiController::getAppVersion()
|
||||
void SettingsUiController::clearSettings()
|
||||
{
|
||||
m_settingsController->clearSettings();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
emit resetLanguageToSystem();
|
||||
|
||||
@@ -206,9 +205,8 @@ bool SettingsUiController::isAutoStartEnabled()
|
||||
void SettingsUiController::toggleAutoStart(bool enable)
|
||||
{
|
||||
m_settingsController->toggleAutoStart(enable);
|
||||
if (!enable) {
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
|
||||
bool SettingsUiController::isStartMinimizedEnabled()
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "core/controllers/settingsController.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
@@ -17,7 +15,6 @@ class SettingsUiController : public QObject
|
||||
public:
|
||||
explicit SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
|
||||
@@ -32,6 +29,7 @@ public:
|
||||
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
|
||||
|
||||
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
||||
Q_PROPERTY(bool autoStartEnabled READ isAutoStartEnabled NOTIFY autoStartChanged)
|
||||
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
||||
|
||||
public slots:
|
||||
@@ -122,7 +120,7 @@ signals:
|
||||
|
||||
void loggingDisableByWatcher();
|
||||
|
||||
void appLanguageChanged(const LanguageSettings::AvailableLanguageEnum language);
|
||||
void appLanguageChanged();
|
||||
void resetLanguageToSystem();
|
||||
|
||||
void onNotificationStateChanged();
|
||||
@@ -135,12 +133,12 @@ signals:
|
||||
void activityResumed();
|
||||
|
||||
void isHomeAdLabelVisibleChanged(bool visible);
|
||||
void autoStartChanged();
|
||||
void startMinimizedChanged();
|
||||
|
||||
private:
|
||||
SettingsController* m_settingsController;
|
||||
ServersController* m_serversController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,7 +30,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||
switch (role) {
|
||||
case SubscriptionStatusRole: {
|
||||
if (m_accountInfoData.configType == serverConfigUtils::ConfigType::AmneziaFreeV3) {
|
||||
return tr("Active");
|
||||
return QStringLiteral("<p><a style=\"color: #28c840;\">%1</a>").arg(tr("Active"));
|
||||
}
|
||||
|
||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate)
|
||||
|
||||
@@ -27,6 +27,7 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
|
||||
auto userData = client.value(configKey::userData).toObject();
|
||||
|
||||
switch (role) {
|
||||
case ClientIdRole: return client.value(configKey::clientId).toString();
|
||||
case ClientNameRole: return userData.value(configKey::clientName).toString();
|
||||
case CreationDateRole: return userData.value(configKey::creationDate).toString();
|
||||
case LatestHandshakeRole: return userData.value(configKey::latestHandshake).toString();
|
||||
@@ -62,6 +63,7 @@ void ClientManagementModel::updateClientName(int row, const QString &newName)
|
||||
QHash<int, QByteArray> ClientManagementModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[ClientIdRole] = "clientId";
|
||||
roles[ClientNameRole] = "clientName";
|
||||
roles[CreationDateRole] = "creationDate";
|
||||
roles[LatestHandshakeRole] = "latestHandshake";
|
||||
|
||||
@@ -10,7 +10,8 @@ class ClientManagementModel : public QAbstractListModel
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
ClientNameRole = Qt::UserRole + 1,
|
||||
ClientIdRole = Qt::UserRole + 1,
|
||||
ClientNameRole,
|
||||
CreationDateRole,
|
||||
LatestHandshakeRole,
|
||||
DataReceivedRole,
|
||||
|
||||
@@ -23,6 +23,10 @@ public:
|
||||
Q_INVOKABLE int containerFromString(const QString &container) const {
|
||||
return static_cast<int>(amnezia::ContainerUtils::containerFromString(container));
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool isUnsupportedContainer(int containerIndex) const {
|
||||
return amnezia::ContainerUtils::isUnsupportedContainer(static_cast<amnezia::DockerContainer>(containerIndex));
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CONTAINERPROPS_H
|
||||
|
||||
@@ -67,6 +67,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
|
||||
case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex);
|
||||
case IsSupportedRole: return ContainerUtils::isSupportedByCurrentPlatform(container);
|
||||
case IsShareableRole: return ContainerUtils::isShareable(container);
|
||||
case IsUnsupportedContainerRole: return ContainerUtils::isUnsupportedContainer(container);
|
||||
case IsVpnContainerRole: return ContainerUtils::containerService(container) == ServiceType::Vpn;
|
||||
case IsServiceContainerRole: return ContainerUtils::containerService(container) == ServiceType::Other;
|
||||
case IsIpsecRole: return container == DockerContainer::Ipsec;
|
||||
@@ -142,7 +143,8 @@ bool ContainersModel::hasInstalledProtocols()
|
||||
|
||||
bool ContainersModel::isInstallationAllowed(DockerContainer container)
|
||||
{
|
||||
return container != DockerContainer::Awg;
|
||||
return container != DockerContainer::Awg
|
||||
&& !ContainerUtils::isUnsupportedContainer(container);
|
||||
}
|
||||
|
||||
void ContainersModel::openContainerSettings(int containerIndex)
|
||||
@@ -176,6 +178,7 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
|
||||
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
|
||||
roles[IsSupportedRole] = "isSupported";
|
||||
roles[IsShareableRole] = "isShareable";
|
||||
roles[IsUnsupportedContainerRole] = "isUnsupportedContainer";
|
||||
roles[IsInstallationAllowedRole] = "isInstallationAllowed";
|
||||
roles[InstallPageOrderRole] = "installPageOrder";
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
IsSupportedRole,
|
||||
IsShareableRole,
|
||||
|
||||
IsUnsupportedContainerRole,
|
||||
|
||||
InstallPageOrderRole,
|
||||
|
||||
// Container type check roles
|
||||
|
||||
@@ -56,14 +56,17 @@ ListViewType {
|
||||
return
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, proxyDefaultServerContainersModel.mapToSource(index))
|
||||
} else {
|
||||
ServersUiController.processedContainerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
var containerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
|
||||
if (!isInstalled) {
|
||||
ServersUiController.processedContainerIndex = containerIndex
|
||||
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
|
||||
containersDropDown.closeTriggered()
|
||||
return
|
||||
}
|
||||
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, containerIndex)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -5,7 +5,6 @@ import QtQuick.Layouts
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import PageEnum 1.0
|
||||
import ContainerProps 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
263
client/ui/qml/Controls2/CaptchaDialogType.qml
Normal file
263
client/ui/qml/Controls2/CaptchaDialogType.qml
Normal file
@@ -0,0 +1,263 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import Style 1.0
|
||||
|
||||
import "."
|
||||
import "TextTypes"
|
||||
import "../Config"
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property string captchaId
|
||||
property string captchaImageBase64
|
||||
property string hint: qsTr("Enter the digits from the image to continue")
|
||||
|
||||
signal captchaSolved(string captchaId, string solution)
|
||||
signal refreshCaptchaRequested()
|
||||
|
||||
leftMargin: 25
|
||||
rightMargin: 25
|
||||
bottomMargin: 70 + SettingsController.safeAreaBottomMargin
|
||||
|
||||
width: parent.width - leftMargin - rightMargin
|
||||
|
||||
anchors.centerIn: parent
|
||||
modal: true
|
||||
closePolicy: Popup.NoAutoClose
|
||||
|
||||
Overlay.modal: Rectangle {
|
||||
color: AmneziaStyle.color.translucentMidnightBlack
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
timer.start()
|
||||
solutionField.textField.text = ""
|
||||
solutionField.textField.focus = true
|
||||
}
|
||||
|
||||
onCaptchaIdChanged: {
|
||||
if (opened) {
|
||||
solutionField.textField.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
onCaptchaImageBase64Changed: {
|
||||
if (opened) {
|
||||
solutionField.textField.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
FocusController.dropRootObject(root)
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: AmneziaStyle.color.slateGray
|
||||
radius: 22
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
interval: 200
|
||||
onTriggered: {
|
||||
FocusController.pushRootObject(root)
|
||||
FocusController.setFocusItem(solutionField.textField)
|
||||
}
|
||||
repeat: false
|
||||
running: true
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
implicitWidth: contentLayout.implicitWidth
|
||||
implicitHeight: contentLayout.implicitHeight
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 20
|
||||
anchors.rightMargin: 20
|
||||
anchors.topMargin: 20
|
||||
anchors.bottomMargin: 20
|
||||
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
id: titleText
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
|
||||
text: root.hint
|
||||
wrapMode: Text.WordWrap
|
||||
color: AmneziaStyle.color.paleGray
|
||||
font.pixelSize: 18
|
||||
font.weight: Font.Bold
|
||||
font.family: "PT Root UI VF"
|
||||
lineHeight: 24 + LanguageUiController.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignTop
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 200
|
||||
|
||||
Rectangle {
|
||||
id: imagePanel
|
||||
|
||||
anchors.fill: parent
|
||||
color: AmneziaStyle.color.pearlGray
|
||||
radius: 16
|
||||
|
||||
Image {
|
||||
id: captchaImage
|
||||
|
||||
anchors.centerIn: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
cache: false
|
||||
|
||||
Component.onCompleted: {
|
||||
if (captchaImageBase64 !== "") {
|
||||
source = "data:image/png;base64," + captchaImageBase64
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onCaptchaImageBase64Changed() {
|
||||
captchaImage.source = "data:image/png;base64," + root.captchaImageBase64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
running: captchaImage.status === Image.Loading
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: refreshHit
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 10
|
||||
width: 44
|
||||
height: 44
|
||||
radius: width / 2
|
||||
color: AmneziaStyle.color.charcoalGray
|
||||
|
||||
Image {
|
||||
id: refreshIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: 26
|
||||
height: 26
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
mipmap: true
|
||||
antialiasing: true
|
||||
source: "qrc:/images/controls/refresh-cw.svg"
|
||||
// Rasterize SVG at high resolution, then scale down — avoids blocky edges on HiDPI.
|
||||
readonly property real _dpr: (Window.window && Window.window.screen)
|
||||
? Window.window.screen.devicePixelRatio : 2.0
|
||||
readonly property int _raster: Math.ceil(64 * Math.min(Math.max(_dpr, 1.0), 4.0))
|
||||
sourceSize: Qt.size(_raster, _raster)
|
||||
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
layer.textureSize: Qt.size(_raster, _raster)
|
||||
layer.effect: ColorOverlay {
|
||||
color: AmneziaStyle.color.goldenApricot
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.refreshCaptchaRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: solutionField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
headerText: qsTr("Digits from the image")
|
||||
headerTextColor: AmneziaStyle.color.mutedGray
|
||||
|
||||
textField.placeholderText: qsTr("_ _ _ _ _ _")
|
||||
textField.placeholderTextColor: AmneziaStyle.color.mutedGray
|
||||
textField.inputMethodHints: Qt.ImhDigitsOnly | Qt.ImhNoPredictiveText
|
||||
textField.maximumLength: 6
|
||||
textField.font.letterSpacing: 2
|
||||
|
||||
textField.onAccepted: {
|
||||
submitIfNonEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 8
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: continueButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 52
|
||||
|
||||
text: qsTr("Continue")
|
||||
defaultColor: AmneziaStyle.color.paleGray
|
||||
hoveredColor: AmneziaStyle.color.lightGray
|
||||
pressedColor: AmneziaStyle.color.mutedGray
|
||||
textColor: AmneziaStyle.color.midnightBlack
|
||||
|
||||
clickedFunc: function() {
|
||||
submitIfNonEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: closeButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 52
|
||||
|
||||
text: qsTr("Close")
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
textColor: AmneziaStyle.color.paleGray
|
||||
borderWidth: 1
|
||||
borderColor: AmneziaStyle.color.mutedGray
|
||||
borderFocusedColor: AmneziaStyle.color.paleGray
|
||||
|
||||
clickedFunc: function() {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function submitIfNonEmpty() {
|
||||
const t = solutionField.textField.text.trim()
|
||||
if (t !== "") {
|
||||
root.captchaSolved(root.captchaId, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,36 @@ Menu {
|
||||
|
||||
popupType: Popup.Native
|
||||
|
||||
onAboutToShow: blocker.enabled = true
|
||||
onClosed: blocker.enabled = false
|
||||
property Item inputBlocker: null
|
||||
|
||||
Component {
|
||||
id: inputBlockerComponent
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
onAboutToShow: {
|
||||
if (!textObj || !textObj.window) {
|
||||
return
|
||||
}
|
||||
|
||||
const contentItem = textObj.window.contentItem
|
||||
if (!inputBlocker) {
|
||||
inputBlocker = inputBlockerComponent.createObject(contentItem)
|
||||
} else {
|
||||
inputBlocker.parent = contentItem
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (inputBlocker) {
|
||||
inputBlocker.destroy()
|
||||
inputBlocker = null
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("C&ut")
|
||||
@@ -31,11 +59,4 @@ Menu {
|
||||
enabled: textObj.length > 0
|
||||
onTriggered: textObj.selectAll()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: blocker
|
||||
z: 2
|
||||
enabled: false
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ PageType {
|
||||
|
||||
filters: [
|
||||
ValueFilter {
|
||||
roleName: "isCurrentlyProcessed"
|
||||
value: true
|
||||
roleName: "serverId"
|
||||
value: ServersUiController.processedServerId
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -440,8 +440,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
}
|
||||
|
||||
var noButtonFunction = function() {}
|
||||
|
||||
@@ -561,7 +561,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
}
|
||||
|
||||
var noButtonFunction = function() {}
|
||||
|
||||
@@ -434,7 +434,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
|
||||
@@ -128,8 +128,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
}
|
||||
var noButtonFunction = function() {}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
|
||||
@@ -129,7 +129,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
|
||||
@@ -112,7 +112,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -279,7 +279,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -17,6 +17,10 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
enableTimer: false
|
||||
|
||||
property bool portDirty: false
|
||||
|
||||
function formatTransport(value) {
|
||||
if (value === "raw") return "RAW (TCP)"
|
||||
if (value === "xhttp") return "XHTTP"
|
||||
@@ -39,8 +43,8 @@ PageType {
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
onActiveFocusChanged: {
|
||||
if (backButton.enabled && backButton.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
@@ -60,8 +64,6 @@ PageType {
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
property alias focusItemId: textFieldWithHeaderType.textField
|
||||
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
@@ -107,13 +109,32 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
enabled: listView.enabled
|
||||
headerText: qsTr("Port")
|
||||
textField.text: port
|
||||
|
||||
Binding {
|
||||
target: textFieldWithHeaderType.textField
|
||||
property: "text"
|
||||
value: port
|
||||
when: !textFieldWithHeaderType.textField.activeFocus
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
textField.maximumLength: 5
|
||||
textField.validator: IntValidator {
|
||||
bottom: 1; top: 65535
|
||||
}
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus && textField.text === "" && port !== "") {
|
||||
textField.text = port
|
||||
}
|
||||
}
|
||||
textField.onTextChanged: {
|
||||
root.portDirty = (textField.text !== port)
|
||||
}
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== port) port = textField.text
|
||||
if (textField.text !== port) {
|
||||
port = textField.text
|
||||
}
|
||||
root.portDirty = false
|
||||
}
|
||||
checkEmptyText: true
|
||||
}
|
||||
@@ -172,9 +193,8 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
visible: listView.enabled
|
||||
&& (XrayConfigModel.hasUnsavedChanges
|
||||
|| textFieldWithHeaderType.textField.text !== port)
|
||||
enabled: visible && textFieldWithHeaderType.errorText === ""
|
||||
&& (XrayConfigModel.hasUnsavedChanges || root.portDirty)
|
||||
enabled: visible && textFieldWithHeaderType.textField.text !== ""
|
||||
text: qsTr("Save")
|
||||
onClicked: function() {
|
||||
forceActiveFocus()
|
||||
@@ -193,7 +213,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) saveButton.forceActiveFocus()
|
||||
|
||||
@@ -742,7 +742,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -95,7 +95,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -211,7 +211,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -208,7 +208,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -179,7 +179,7 @@ PageType {
|
||||
function mtProxyScheduleUpdate(closePage) {
|
||||
var cp = closePage === undefined ? false : closePage
|
||||
Qt.callLater(function () {
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
|
||||
tempPort = portTextField.textField.text
|
||||
tempUsername = usernameTextField.textField.text
|
||||
tempPassword = passwordTextField.textField.text
|
||||
|
||||
@@ -154,7 +154,7 @@ PageType {
|
||||
function telemtScheduleUpdate(closePage) {
|
||||
var cp = closePage === undefined ? false : closePage
|
||||
Qt.callLater(function () {
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,12 @@ PageType {
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
textFormat: Text.RichText
|
||||
text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this URL.")
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
|
||||
@@ -30,6 +30,16 @@ PageType {
|
||||
root.isInAppPurchase = ApiAccountInfoModel.data("isInAppPurchase")
|
||||
}
|
||||
|
||||
function selectConnectionCountry(countryIndex, countryCode, countryName) {
|
||||
if (countryIndex === ApiCountryModel.currentIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
PageController.showBusyIndicator(true)
|
||||
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.updateSubscriptionState()
|
||||
}
|
||||
@@ -83,7 +93,7 @@ PageType {
|
||||
|
||||
model: ApiCountryModel
|
||||
|
||||
currentIndex: 0
|
||||
currentIndex: ApiCountryModel.currentIndex
|
||||
|
||||
ButtonGroup {
|
||||
id: containersRadioButtonGroup
|
||||
@@ -204,15 +214,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
if (index !== ApiCountryModel.currentIndex) {
|
||||
PageController.showBusyIndicator(true)
|
||||
var prevIndex = ApiCountryModel.currentIndex
|
||||
ApiCountryModel.currentIndex = index
|
||||
if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)) {
|
||||
ApiCountryModel.currentIndex = prevIndex
|
||||
}
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
root.selectConnectionCountry(index, countryCode, countryName)
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: {
|
||||
|
||||
@@ -108,9 +108,9 @@ PageType {
|
||||
text: qsTr("Auto start")
|
||||
descriptionText: qsTr("Launch the application every time the device is starts")
|
||||
|
||||
checked: SettingsController.isAutoStartEnabled()
|
||||
checked: SettingsController.autoStartEnabled
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.isAutoStartEnabled()) {
|
||||
if (checked !== SettingsController.autoStartEnabled) {
|
||||
SettingsController.toggleAutoStart(checked)
|
||||
}
|
||||
}
|
||||
@@ -154,10 +154,10 @@ PageType {
|
||||
text: qsTr("Start minimized")
|
||||
descriptionText: qsTr("Launch application minimized (works with autostart option turned on)")
|
||||
|
||||
enabled: SettingsController.isAutoStartEnabled()
|
||||
enabled: SettingsController.autoStartEnabled
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
|
||||
checked: SettingsController.isAutoStartEnabled() && SettingsController.startMinimized
|
||||
checked: SettingsController.autoStartEnabled && SettingsController.startMinimized
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.startMinimized) {
|
||||
SettingsController.toggleStartMinimized(checked)
|
||||
@@ -166,7 +166,7 @@ PageType {
|
||||
}
|
||||
|
||||
DividerType {
|
||||
visible: !GC.isMobile()
|
||||
visible: !GC.isMobile() && ServersUiController.hasServersFromGatewayApi
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
|
||||
@@ -36,17 +36,6 @@ PageType {
|
||||
function onRebootServerFinished(finishedMessage) {
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
function onRemoveAllContainersFinished(finishedMessage) {
|
||||
PageController.closePage() // close deInstalling page
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
function onRemoveContainerFinished(finishedMessage) {
|
||||
PageController.closePage() // close deInstalling page
|
||||
PageController.closePage() // close page with remove button
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -17,7 +17,8 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool isClearCacheVisible: ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex)
|
||||
property bool isUnsupportedContainer: ContainerProps.isUnsupportedContainer(ServersUiController.processedContainerIndex)
|
||||
property bool isClearCacheVisible: !isUnsupportedContainer && ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex)
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
@@ -52,10 +53,11 @@ PageType {
|
||||
Layout.bottomMargin: 32
|
||||
|
||||
headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings")
|
||||
descriptionText: root.isUnsupportedContainer ? qsTr("This protocol is no longer supported.") : ""
|
||||
}
|
||||
}
|
||||
|
||||
model: ProtocolsModel
|
||||
model: root.isUnsupportedContainer ? null : ProtocolsModel
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: delegateContent
|
||||
|
||||
@@ -130,6 +130,9 @@ PageType {
|
||||
PageController.showBusyIndicator(false)
|
||||
|
||||
if (!result) {
|
||||
if (SubscriptionUiController.isCaptchaAwaitingUser()) {
|
||||
return
|
||||
}
|
||||
var endpoint = ApiServicesModel.getStoreEndpoint()
|
||||
Qt.openUrlExternally(endpoint)
|
||||
PageController.closePage()
|
||||
|
||||
@@ -29,6 +29,10 @@ PageType {
|
||||
ValueFilter {
|
||||
roleName: "isInstallationAllowed"
|
||||
value: true
|
||||
},
|
||||
ValueFilter {
|
||||
roleName: "isUnsupportedContainer"
|
||||
value: false
|
||||
}
|
||||
]
|
||||
sorters: RoleSorter {
|
||||
|
||||
@@ -382,6 +382,10 @@ PageType {
|
||||
ValueFilter {
|
||||
roleName: "isShareable"
|
||||
value: true
|
||||
},
|
||||
ValueFilter {
|
||||
roleName: "isUnsupportedContainer"
|
||||
value: false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -396,9 +400,19 @@ PageType {
|
||||
target: serverSelector
|
||||
|
||||
function onServerSelectorIndexChanged() {
|
||||
var defaultContainer = proxyContainersModel.mapFromSource(ServersUiController.serverDefaultContainer(ServersUiController.processedServerId))
|
||||
if (!proxyContainersModel.count) {
|
||||
root.shareButtonEnabled = false
|
||||
return
|
||||
}
|
||||
|
||||
var defaultContainer = proxyContainersModel.mapFromSource(
|
||||
ServersUiController.serverDefaultContainer(ServersUiController.processedServerId))
|
||||
if (defaultContainer < 0) {
|
||||
defaultContainer = 0
|
||||
}
|
||||
|
||||
containerSelectorListView.selectedIndex = defaultContainer
|
||||
containerSelectorListView.positionViewAtIndex(selectedIndex, ListView.Beginning)
|
||||
containerSelectorListView.positionViewAtIndex(defaultContainer, ListView.Beginning)
|
||||
containerSelectorListView.triggerCurrentItem()
|
||||
}
|
||||
}
|
||||
@@ -837,11 +851,10 @@ PageType {
|
||||
var noButtonFunction = function() {
|
||||
}
|
||||
|
||||
var isActiveConfigForCurrentClient = ServersUiController.isDefaultServerCurrentlyProcessed()
|
||||
&& ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex
|
||||
|
||||
if ((ConnectionController.isConnectionInProgress || ConnectionController.isConnected)
|
||||
&& isActiveConfigForCurrentClient) {
|
||||
if (ConnectionController.isRevokeBlockedDuringActiveConnection(
|
||||
ServersUiController.processedServerId,
|
||||
ServersUiController.processedContainerIndex,
|
||||
clientId)) {
|
||||
PageController.showNotificationMessage("Unable to revoke current config during active connection")
|
||||
} else {
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
|
||||
@@ -105,6 +105,19 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
objectName: "connectionControllerConnections"
|
||||
|
||||
target: ConnectionController
|
||||
|
||||
function onNoInstalledContainers() {
|
||||
PageController.setTriggeredByConnectButton(true)
|
||||
|
||||
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardEasy)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
objectName: "installControllerConnections"
|
||||
|
||||
@@ -153,11 +166,19 @@ PageType {
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
function onNoInstalledContainers() {
|
||||
PageController.setTriggeredByConnectButton(true)
|
||||
function onRemoveAllContainersFinished(finishedMessage) {
|
||||
if (tabBarStackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageDeinstalling)) {
|
||||
PageController.closePage()
|
||||
}
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardEasy)
|
||||
function onRemoveContainerFinished(finishedMessage) {
|
||||
if (tabBarStackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageDeinstalling)) {
|
||||
PageController.closePage()
|
||||
}
|
||||
PageController.closePage()
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -205,6 +205,27 @@ Window {
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
objectName: "captchaDialogItem"
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
CaptchaDialogType {
|
||||
id: captchaDialog
|
||||
|
||||
onCaptchaSolved: function(captchaId, solution) {
|
||||
PageController.showBusyIndicator(true)
|
||||
Qt.callLater(function() {
|
||||
SubscriptionUiController.onCaptchaSolved(captchaId, solution)
|
||||
})
|
||||
}
|
||||
|
||||
onRefreshCaptchaRequested: function() {
|
||||
SubscriptionUiController.onRefreshCaptchaRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
objectName: "privateKeyPassphraseDrawerItem"
|
||||
|
||||
@@ -213,6 +234,8 @@ Window {
|
||||
DrawerType2 {
|
||||
id: privateKeyPassphraseDrawer
|
||||
|
||||
property bool isCloseByUser: false
|
||||
|
||||
anchors.fill: parent
|
||||
expandedHeight: root.height * 0.35 + PageController.safeAreaBottomMargin + PageController.imeHeight
|
||||
|
||||
@@ -232,6 +255,11 @@ Window {
|
||||
}
|
||||
|
||||
function onAboutToHide() {
|
||||
if (privateKeyPassphraseDrawer.isCloseByUser === false) {
|
||||
privateKeyPassphraseDrawer.isCloseByUser = true
|
||||
PageController.passphraseRequestDrawerClosed("")
|
||||
}
|
||||
|
||||
if (passphrase.textField.text !== "") {
|
||||
PageController.showBusyIndicator(true)
|
||||
}
|
||||
@@ -272,6 +300,7 @@ Window {
|
||||
text: qsTr("Save")
|
||||
|
||||
clickedFunc: function() {
|
||||
privateKeyPassphraseDrawer.isCloseByUser = true
|
||||
privateKeyPassphraseDrawer.closeTriggered()
|
||||
PageController.passphraseRequestDrawerClosed(passphrase.textField.text)
|
||||
}
|
||||
@@ -318,6 +347,27 @@ Window {
|
||||
function onSubscriptionExpiredOnServer() {
|
||||
subscriptionExpiredDrawer.openTriggered()
|
||||
}
|
||||
|
||||
function onCaptchaRequired(captchaId, captchaImageBase64, hint) {
|
||||
if (captchaDialog.opened) {
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
captchaDialog.captchaId = captchaId
|
||||
captchaDialog.captchaImageBase64 = captchaImageBase64
|
||||
captchaDialog.hint = hint
|
||||
captchaDialog.open()
|
||||
}
|
||||
|
||||
function onCaptchaFlowDismissRequested() {
|
||||
PageController.showBusyIndicator(false)
|
||||
captchaDialog.close()
|
||||
}
|
||||
|
||||
function onErrorOccurred(error) {
|
||||
if (captchaDialog.opened) {
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<file>Controls2/BackButtonType.qml</file>
|
||||
<file>Controls2/BasicButtonType.qml</file>
|
||||
<file>Controls2/BusyIndicatorType.qml</file>
|
||||
<file>Controls2/CaptchaDialogType.qml</file>
|
||||
<file>Controls2/CardType.qml</file>
|
||||
<file>Controls2/CardWithIconsType.qml</file>
|
||||
<file>Controls2/CheckBoxType.qml</file>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user