Compare commits

...

17 Commits

Author SHA1 Message Date
yp
56ab82f87f fix: Use shared OpenSSL on Android (#2736) 2026-06-16 10:57:32 +07:00
lunardunno
3984acbb44 feat: updating install_docker.sh script (#2661)
* Updating install_docker.sh script

Implementing a Docker service status check.
The Docker reinstall step has been removed due to the implementation of Docker service checking.
Implementing locale checking and assignment.
Implementation of execution of some actions through commands with sudo, to reduce delays caused by differences in the values ​​of the PATH variable for the root user and the user included in the sudo group.
Implementation of a verification step for the install containerization app to avoid installing unsupported podman-docker applications.

* adding message handling to install controller

Adding handling for "Containerization app is not supported" and "Service status not active" messages to the controller.

* Error Codes added

Error Codes added for ServerContainerizationNotSupported & DockerServiceNotActive

* Adding extended descriptions of new errors

* fix last line in errorCodes.h

* fix last line in errorStrings.cpp

* Changing the names of errors

* various changes in the script

The messages output for processing by the server controller have been changed: "Container runtime is not supported" and "Container runtime service is not running."
The redundant check and output of the "Packet manager not found" message, as well as the interruption of script execution, have been eliminated, as this situation is handled by the server controller at an earlier stage (check_server_is_busy.sh) and only there.
Added installation of the whish package if it is missing from the OS, for subsequent re-execution of the install_docker.sh and check_server_is_busy.sh scripts.
Implemented an alternative method for detecting the package manager if the whish package is initially missing from the OS.
The algorithm for setting the $pm variable (package manager) has been changed.

* processed phrases have been changed

The phrases processed by the server controller have been changed.

* Attempting to use "command -v"

Switching to using "command -v" instead of "which".

* "which" as main, "command" as backup.

* "which" as main, "command" as backup for check user

* which  LOCK_CMD with sudo

Run the "which" with sudo to check the $LOCK_CMD variable in case the user's PATH variable has incorrect values ​​if the user is not root and is only a member of the sudo group.

* suppressing sudo password prompt

* suppressing sudo password prompt

* suppressing sudo password prompt install_docker.sh

* Changing the phrase for check stdout

"sudo:" with "not found" instead of "command not found"

* Changing phrases for check stdout check_user_in_sudo.sh‎

* sudo|docker and not found, in one line

* check only sudoers
2026-06-15 22:28:38 +07:00
yp
cc404378f9 fix: remove only amnezia- prefixed docker volumes (#2728) 2026-06-15 13:12:19 +07:00
yp
594635e5cf fix: script remove docker volume (#2686)
* move sudo docker volume rm -f

* fix: remove unnecessary function

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-06-04 22:58:39 +08:00
vkamn
f9b106cf5b fix: various fixes (#2693)
* fix: fixed country model update

* fix: fixed context menu crush on ios

* fix: fixed passphrase dialog freeze

* fix: fixed country switch

* fix: fixed start minimized

* fix: fixed black screen after remove container

* refactor: return cloak and ss only for view

* fix: fixed default server change after improt while connected

* fix: divider visibility

* fix: fixed revoke admin user

* fix: fixed language restore after backup

* fix: link hover for tor settings page

* fix: fixed openvpn connecntion status

* fix: fixed free color status

* fix: fixed client config update

* chore: bump version
2026-06-04 22:45:53 +08:00
yp
a9861d18b7 fix: wrong index on xray pages (#2669)
* test crash xray

* fixed save config xray

* reset file

* fixed text port & reset file

* fixed textFieldWithHeaderType.textField
2026-06-01 12:22:54 +08:00
lunardunno
c14138f031 fix: deleting volumes when cleaning the server (#2673)
* Deleting volumes when cleaning the server

* force the remove volumes
2026-06-01 11:54:34 +08:00
yyy-amnezia
60686fde24 fix: link OpenVPNAdapter statically (#2645)
* fix(ios): link OpenVPNAdapter statically

* chore(conan): simplify openvpnadapter merge process and make everything via XCrun

---------

Co-authored-by: Yaroslav Gurov <ygurov@proton.me>
2026-05-30 13:59:35 +08:00
Yaroslav Gurov
bd0747296e fix: networkextension proper framework linking (#2668) 2026-05-28 23:09:49 +08:00
Yaroslav Gurov
ba61019a50 fix: enable bundled openssl for every platform except NE-based ones (#2660)
* fix: enable bundled openssl for every platform except NE-based ones

* fix(conan): trigger CI/CD on crutial cmake changes

* fix: install dylibs/dlls from conan and use proper RPATH

* fix: adjust windows runtime deps
2026-05-28 19:17:27 +08:00
vkamn
113f967006 fix: various fixes (#2664)
* fix: fixed nextAvailableServerName

* fix: fixed password request for ssh key
2026-05-28 15:14:04 +08:00
yp
bcee58b08b feat: add captcha (#2508)
* test capcha

* add test AMNEZIA_GATEWAY_PLAINTEXT_MOCK

* ref

* remove first QNetworkReply::NoError

* fixed macros

* fixed http code

* add test server

* fix cmake

* add CAPTCHA refreshed

* fixed captcha

* update QML Captha

* fixed crash app & up vercion & fix qml captha

* ver 4.9.0.1

* remove m_gatewayCaptchaStickyBase & outEffectiveRequestBase

* reset code PR

* remove mock & temp var AMNEZIA_LOCAL_GATEWAY

* ref code & remove AMNEZIA_LOCAL_GATEWAY

* remove check httpStatusCode & error

* add 408 status code

* fix update captca

* remove fallback на transport

* chore: add loader after captcha solved

* chore: remove logs from api utils

* chore: minor fixes

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-28 13:51:26 +08:00
MrMirDan
52de1acebf refactoring: move tests to separate repo (#2550)
* fix: AUTOMOC and AUTOUIC added

* update: native exports test

* update: export test

* update: vless serialization/deserialization test added

* update: language model and controller test

* update: sites UiController and Model test

* update: sites test

* update: app ui model and controller test

* update: allowed dns ui model and controller test

* update: env vars and removed some lines

* update: news ui model and controller test (incomplete)

* update: api services ui model and controller test (incompleted)

* update: job for tests (Linux)

* update: job for tests (Windows)

* update: proper artifact names

* update: added envs

* update: added 'get sources' and changed steps order

* update: tests jobs remake

* update: 'get sources' step and windows shell

* update: using ctest

* search for exe files

* changed path to run tests

* update: 'Build' step

* update: changed path to deploy qt dependencies

* update: dependencies only for tests executables

* update: ctest dir

* update: include ctest

* update: set dir for tests exe

* update: qt path

* update: serialization test

* update: removed api tests from cmake

* update: changed tests dir

* added ctest to client cmake

* update: installing msvc and additional checks

* removed mcvs install

* update: path to ssh.dll

* fixed issue with ssh path

* update: removed unneccessary step and line

* update: linux job step 'Run tests'

* update: linux 'Install dependencies'

* update: modified qtest include

* update: changed QVERIFY to QVERIFY2

* update: some qverify2 messages

* update: linux additional dependencies

* update: offscreen for linux tests

* update: MacOS tests job

* update: Android tests job

* update: rewrited env's, qtest include and clear clients in some tests

* update: added local vars file for tests

* proper path for some vars

* some fixes due merge

* update: windows tests deploy

* python and conan installation to tests jobs

* chore: minor fixes after merge with dev

* chore: move selfhosted admin tests to separate folder

* refactor: some rename

* chore: fixes after merge

* refactor: moved tests to separate repo

* refactor: remove tests from core controller

* chore: add more protected getters to core controller

* chore: add more protected getters

* chore: remove ctest

* chore: return xray model default values

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-28 12:31:27 +08:00
yp
027a12a1df fix: extended VLESS configuration (#2643)
* fixed vless

* fixed default var

* fixed save button

* remove comment

* fix: fixed header link in xray settings page

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-28 12:21:46 +08:00
yp
0a659a2d74 fix: various fixes for MTProxy & Telemt (#2653)
* fix color & fix enabled

* fixed remove base secret

* fix mtproxy/telemt 'base secret'

* fixed button back

* fixed loader

* fixed reload loader

* fixed dd secret

* fixed qml

* fix: fixed header link in mtproxy/telemt page

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-28 11:46:26 +08:00
vkamn
6f119cd083 fix: various fixes (#2662)
* fix: fixed dns processing

* fix: fixed proceesed index/id selection

* refactor: stop using the server index as state

* fix: fixed autostart and start minimized

* fix: fixed typo

* fix: add socks5 extractConfigFromContainer

* fix: remove unused currentContainerUpdated

* fix: fixed clear cached profile order
2026-05-28 10:57:08 +08:00
Yaroslav Gurov
1753aed3fc fix: use shared openssl on Android (#2657)
* feat(conan): clone openssl and patch it for Android

* fix(conan): build shared libssl for Android
2026-05-26 21:59:47 +08:00
147 changed files with 3769 additions and 3615 deletions

View File

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

View File

@@ -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")

View File

@@ -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 "")

View File

@@ -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"

View File

@@ -4,6 +4,7 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QThread>
#include <QUuid>
#include "logger.h"
@@ -137,117 +138,326 @@ amnezia::ProtocolConfig XrayConfigurator::processConfigWithLocalSettings(const a
return protocolConfig;
}
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
ErrorCode XrayConfigurator::uploadServerConfigJson(const ServerCredentials &credentials, DockerContainer container,
const DnsSettings &dnsSettings, const QJsonObject &serverConfig) const
{
// Generate new UUID for client
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
const QString updatedConfig = QJsonDocument(serverConfig).toJson();
ErrorCode errorCode = m_sshSession->uploadTextFileToContainer(
container, credentials, updatedConfig, amnezia::protocols::xray::serverConfigPath,
libssh::ScpOverwriteMode::ScpOverwriteExisting);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to upload updated config";
return errorCode;
}
// Get flow value from settings (default xtls-rprx-vision)
QString flowValue = "xtls-rprx-vision";
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
if (!xrayCfg->serverConfig.flow.isEmpty()) {
flowValue = xrayCfg->serverConfig.flow;
const QString restartScript = QStringLiteral("sudo docker restart $CONTAINER_NAME");
errorCode = m_sshSession->runScript(
credentials,
m_sshSession->replaceVars(restartScript,
amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns,
dnsSettings.secondaryDns)));
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to restart container";
}
return errorCode;
}
ErrorCode XrayConfigurator::readRealityKeyFiles(const DockerContainer container, const ServerCredentials &credentials,
QString &outPublicKey, QString &outShortId) const
{
outPublicKey.clear();
outShortId.clear();
auto readKeyFile = [&](const QString &path, QString &out) -> ErrorCode {
for (int attempt = 0; attempt < 3; ++attempt) {
ErrorCode fileError = ErrorCode::NoError;
out = QString::fromUtf8(m_sshSession->getTextFileFromContainer(container, credentials, path, fileError));
out.replace(QLatin1Char('\n'), QString());
out.replace(QLatin1Char('\r'), QString());
if (fileError == ErrorCode::NoError && !out.isEmpty()) {
return ErrorCode::NoError;
}
if (attempt < 2) {
QThread::msleep(500);
}
}
logger.error() << "Xray readRealityKeyFiles: failed path=" << path;
return ErrorCode::XrayRealityKeysReadFailed;
};
ErrorCode errorCode = readKeyFile(QString::fromLatin1(amnezia::protocols::xray::PublicKeyPath), outPublicKey);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
return readKeyFile(QString::fromLatin1(amnezia::protocols::xray::shortidPath), outShortId);
}
QJsonObject XrayConfigurator::mergeStreamSettingsForServerInbound(const XrayServerConfig &srv,
const QJsonObject &existingStreamSettings) const
{
QJsonObject streamSettings = buildStreamSettings(srv, QString());
if (srv.security != QLatin1String("reality")) {
return streamSettings;
}
const QJsonObject newRs = streamSettings[amnezia::protocols::xray::realitySettings].toObject();
QJsonObject oldRs = existingStreamSettings[amnezia::protocols::xray::realitySettings].toObject();
QJsonObject merged = oldRs.isEmpty() ? newRs : oldRs;
const QString siteEff = srv.site.isEmpty() ? QString::fromLatin1(amnezia::protocols::xray::defaultSite) : srv.site;
const QString sniEff = srv.sni.isEmpty() ? siteEff : srv.sni;
if (newRs.contains(amnezia::protocols::xray::fingerprint)) {
merged[amnezia::protocols::xray::fingerprint] = newRs[amnezia::protocols::xray::fingerprint];
}
merged[amnezia::protocols::xray::serverNames] = QJsonArray { sniEff };
if (!merged.contains(QStringLiteral("dest"))) {
merged[QStringLiteral("dest")] = siteEff + QStringLiteral(":443");
}
streamSettings[amnezia::protocols::xray::realitySettings] = merged;
return streamSettings;
}
ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials &credentials, DockerContainer container,
ContainerConfig &containerConfig, const DnsSettings &dnsSettings,
bool appendNewClient, QString *outClientId)
{
ErrorCode errorCode = ErrorCode::NoError;
const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>();
if (!xrayCfg) {
logger.error() << "Xray applyServerSettings: missing XrayProtocolConfig";
return ErrorCode::InternalError;
}
const XrayServerConfig &srv = xrayCfg->serverConfig;
if (srv.isThirdPartyConfig) {
logger.info() << "Xray applyServerSettings: skipped (third-party/native profile)";
if (outClientId && xrayCfg->hasClientConfig()) {
*outClientId = xrayCfg->clientConfig->id;
}
return ErrorCode::NoError;
}
logger.info() << "Xray applyServerSettings: start"
<< "container=" << static_cast<int>(container) << "host=" << credentials.hostName
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port
<< "appendClient=" << appendNewClient;
QString flowValue = srv.flow;
if (flowValue.isEmpty() && srv.security == QLatin1String("reality")) {
flowValue = QStringLiteral("xtls-rprx-vision");
}
QString realityPublicKey;
QString realityShortId;
if (srv.security == QLatin1String("reality")) {
errorCode = readRealityKeyFiles(container, credentials, realityPublicKey, realityShortId);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Xray applyServerSettings: readRealityKeyFiles failed, error="
<< static_cast<int>(errorCode);
return errorCode;
}
}
// Get current server config
QString currentConfig = m_sshSession->getTextFileFromContainer(
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to get server config file";
return "";
logger.error() << "Xray applyServerSettings: getTextFileFromContainer failed, error="
<< static_cast<int>(errorCode) << "path=" << amnezia::protocols::xray::serverConfigPath;
return errorCode;
}
logger.info() << "Xray applyServerSettings: read server config, bytes=" << currentConfig.size();
// Parse current config as JSON
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
if (doc.isNull() || !doc.isObject()) {
logger.error() << "Failed to parse server config JSON";
errorCode = ErrorCode::InternalError;
return "";
return ErrorCode::XrayServerConfigInvalid;
}
QJsonObject serverConfig = doc.object();
// Validate server config structure
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
logger.error() << "Server config missing 'inbounds' field";
errorCode = ErrorCode::InternalError;
return "";
return ErrorCode::XrayServerConfigInvalid;
}
QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray();
if (inbounds.isEmpty()) {
logger.error() << "Server config has empty 'inbounds' array";
errorCode = ErrorCode::InternalError;
return "";
return ErrorCode::XrayServerConfigInvalid;
}
QJsonObject inbound = inbounds[0].toObject();
if (!inbound.contains(amnezia::protocols::xray::settings)) {
logger.error() << "Inbound missing 'settings' field";
errorCode = ErrorCode::InternalError;
return "";
return ErrorCode::XrayServerConfigInvalid;
}
const QJsonObject existingStream = inbound[amnezia::protocols::xray::streamSettings].toObject();
inbound[amnezia::protocols::xray::streamSettings] = mergeStreamSettingsForServerInbound(srv, existingStream);
if (!srv.port.isEmpty()) {
inbound[amnezia::protocols::xray::port] = srv.port.toInt();
}
QJsonObject settings = inbound[amnezia::protocols::xray::settings].toObject();
if (!settings.contains(amnezia::protocols::xray::clients)) {
logger.error() << "Settings missing 'clients' field";
errorCode = ErrorCode::InternalError;
return "";
settings[amnezia::protocols::xray::clients] = QJsonArray {};
}
QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray();
QString clientId;
// Create configuration for new client
QJsonObject clientConfig {
{amnezia::protocols::xray::id, clientId},
};
clientConfig[amnezia::protocols::xray::id] = clientId;
if (!flowValue.isEmpty()) {
clientConfig[amnezia::protocols::xray::flow] = flowValue;
if (appendNewClient) {
clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
QJsonObject clientEntry;
clientEntry[amnezia::protocols::xray::id] = clientId;
if (!flowValue.isEmpty()) {
clientEntry[amnezia::protocols::xray::flow] = flowValue;
}
clients.append(clientEntry);
} else {
if (clients.isEmpty()) {
logger.error() << "Server config has no VLESS clients";
return ErrorCode::XrayServerNoVlessClients;
}
clientId = clients[0].toObject()[amnezia::protocols::xray::id].toString();
if (clientId.isEmpty()) {
logger.error() << "Server config VLESS client has empty id";
return ErrorCode::XrayServerNoVlessClients;
}
QJsonArray updatedClients;
for (const QJsonValue &v : clients) {
QJsonObject c = v.toObject();
if (flowValue.isEmpty()) {
c.remove(amnezia::protocols::xray::flow);
} else {
c[amnezia::protocols::xray::flow] = flowValue;
}
updatedClients.append(c);
}
clients = updatedClients;
}
clients.append(clientConfig);
// Update config
settings[amnezia::protocols::xray::clients] = clients;
inbound[amnezia::protocols::xray::settings] = settings;
inbounds[0] = inbound;
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
// Save updated config to server
QString updatedConfig = QJsonDocument(serverConfig).toJson();
errorCode = m_sshSession->uploadTextFileToContainer(
container,
credentials,
updatedConfig,
amnezia::protocols::xray::serverConfigPath,
libssh::ScpOverwriteMode::ScpOverwriteExisting
);
errorCode = uploadServerConfigJson(credentials, container, dnsSettings, serverConfig);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to upload updated config";
return "";
logger.error() << "Xray applyServerSettings: upload/restart failed, error=" << static_cast<int>(errorCode);
return errorCode;
}
logger.info() << "Xray applyServerSettings: server config uploaded and container restarted";
if (outClientId) {
*outClientId = clientId;
}
// Restart container
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
errorCode = m_sshSession->runScript(
credentials,
m_sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns))
);
XrayProtocolConfig updated =
buildClientProtocolConfig(credentials, container, srv, clientId, errorCode, realityPublicKey, realityShortId);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to restart container";
return "";
logger.error() << "Xray applyServerSettings: buildClientProtocolConfig failed, error="
<< static_cast<int>(errorCode);
return errorCode;
}
containerConfig.protocolConfig = updated;
logger.info() << "Xray applyServerSettings: done, clientId=" << clientId;
return ErrorCode::NoError;
}
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
ContainerConfig mutableConfig = containerConfig;
QString clientId;
const ErrorCode applyError =
applyServerSettingsToRemote(credentials, container, mutableConfig, dnsSettings, true, &clientId);
errorCode = applyError;
if (applyError != ErrorCode::NoError || clientId.isEmpty()) {
return QString();
}
return clientId;
}
XrayProtocolConfig XrayConfigurator::buildClientProtocolConfig(const ServerCredentials &credentials,
DockerContainer container,
const XrayServerConfig &srv, const QString &clientId,
ErrorCode &errorCode,
const QString &prefetchedRealityPublicKey,
const QString &prefetchedRealityShortId) const
{
QString xrayPublicKey = prefetchedRealityPublicKey;
QString xrayShortId = prefetchedRealityShortId;
if (srv.security == QLatin1String("reality")) {
if (xrayPublicKey.isEmpty() || xrayShortId.isEmpty()) {
errorCode = readRealityKeyFiles(container, credentials, xrayPublicKey, xrayShortId);
if (errorCode != ErrorCode::NoError) {
return {};
}
}
}
QJsonObject userObj;
userObj[amnezia::protocols::xray::id] = clientId;
userObj[amnezia::protocols::xray::encryption] = QStringLiteral("none");
if (!srv.flow.isEmpty()) {
userObj[amnezia::protocols::xray::flow] = srv.flow;
}
QJsonObject vnextEntry;
vnextEntry[amnezia::protocols::xray::address] = credentials.hostName;
vnextEntry[amnezia::protocols::xray::port] =
srv.port.isEmpty() ? QString(amnezia::protocols::xray::defaultPort).toInt() : srv.port.toInt();
vnextEntry[amnezia::protocols::xray::users] = QJsonArray { userObj };
QJsonObject outboundSettings;
outboundSettings[amnezia::protocols::xray::vnext] = QJsonArray { vnextEntry };
QJsonObject outbound;
outbound[QStringLiteral("protocol")] = QStringLiteral("vless");
outbound[amnezia::protocols::xray::settings] = outboundSettings;
QJsonObject streamObj = buildStreamSettings(srv, clientId);
if (srv.security == QLatin1String("reality")) {
QJsonObject rs = streamObj[amnezia::protocols::xray::realitySettings].toObject();
rs[amnezia::protocols::xray::publicKey] = xrayPublicKey;
rs[amnezia::protocols::xray::shortId] = xrayShortId;
rs[amnezia::protocols::xray::spiderX] = QString();
streamObj[amnezia::protocols::xray::realitySettings] = rs;
}
outbound[amnezia::protocols::xray::streamSettings] = streamObj;
QJsonObject inboundObj;
inboundObj[QStringLiteral("listen")] = amnezia::protocols::xray::defaultLocalListenAddr;
inboundObj[amnezia::protocols::xray::port] = amnezia::protocols::xray::defaultLocalProxyPort;
inboundObj[QStringLiteral("protocol")] = QStringLiteral("socks");
inboundObj[amnezia::protocols::xray::settings] = QJsonObject { { QStringLiteral("udp"), true } };
QJsonObject clientJson;
clientJson[QStringLiteral("log")] = QJsonObject { { QStringLiteral("loglevel"), QStringLiteral("error") } };
clientJson[amnezia::protocols::xray::inbounds] = QJsonArray { inboundObj };
clientJson[amnezia::protocols::xray::outbounds] = QJsonArray { outbound };
const QString config = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
XrayProtocolConfig protocolConfig;
protocolConfig.serverConfig = srv;
XrayClientConfig clientConfig;
clientConfig.nativeConfig = config;
clientConfig.localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort);
clientConfig.id = clientId;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
}
QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, const QString &clientId) const
{
QJsonObject streamSettings;
@@ -419,6 +629,13 @@ ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentia
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
if (xrayCfg->serverConfig.isThirdPartyConfig && xrayCfg->hasClientConfig()) {
logger.info() << "Xray createConfig: returning existing third-party client config without server SSH";
return *xrayCfg;
}
}
const XrayServerConfig *serverConfig = nullptr;
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
serverConfig = &xrayCfg->serverConfig;
@@ -441,93 +658,5 @@ ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentia
return XrayProtocolConfig{};
}
// Fetch server keys (Reality only)
QString xrayPublicKey;
QString xrayShortId;
if (srv.security == "reality") {
xrayPublicKey = m_sshSession->getTextFileFromContainer(container, credentials,
amnezia::protocols::xray::PublicKeyPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
logger.error() << "Failed to get public key";
if (errorCode == ErrorCode::NoError) {
errorCode = ErrorCode::InternalError;
}
return XrayProtocolConfig{};
}
xrayPublicKey.replace("\n", "");
xrayShortId = m_sshSession->getTextFileFromContainer(container, credentials,
amnezia::protocols::xray::shortidPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
logger.error() << "Failed to get short ID";
if (errorCode == ErrorCode::NoError) {
errorCode = ErrorCode::InternalError;
}
return XrayProtocolConfig{};
}
xrayShortId.replace("\n", "");
}
// Build outbound
QJsonObject userObj;
userObj[amnezia::protocols::xray::id] = xrayClientId;
userObj[amnezia::protocols::xray::encryption] = "none";
if (!srv.flow.isEmpty()) {
userObj[amnezia::protocols::xray::flow] = srv.flow;
}
QJsonObject vnextEntry;
vnextEntry[amnezia::protocols::xray::address] = credentials.hostName;
vnextEntry[amnezia::protocols::xray::port] = srv.port.toInt();
vnextEntry[amnezia::protocols::xray::users] = QJsonArray { userObj };
QJsonObject outboundSettings;
outboundSettings[amnezia::protocols::xray::vnext] = QJsonArray { vnextEntry };
QJsonObject outbound;
outbound["protocol"] = "vless";
outbound[amnezia::protocols::xray::settings] = outboundSettings;
// Build streamSettings
QJsonObject streamObj = buildStreamSettings(srv, xrayClientId);
// Inject Reality keys
if (srv.security == "reality") {
QJsonObject rs = streamObj[amnezia::protocols::xray::realitySettings].toObject();
rs[amnezia::protocols::xray::publicKey] = xrayPublicKey;
rs[amnezia::protocols::xray::shortId] = xrayShortId;
rs[amnezia::protocols::xray::spiderX] = "";
streamObj[amnezia::protocols::xray::realitySettings] = rs;
}
outbound[amnezia::protocols::xray::streamSettings] = streamObj;
// Build full client config
QJsonObject inboundObj;
inboundObj["listen"] = amnezia::protocols::xray::defaultLocalListenAddr;
inboundObj[amnezia::protocols::xray::port] = amnezia::protocols::xray::defaultLocalProxyPort;
inboundObj["protocol"] = "socks";
inboundObj[amnezia::protocols::xray::settings] = QJsonObject { { "udp", true } };
QJsonObject clientJson;
clientJson["log"] = QJsonObject { { "loglevel", "error" } };
clientJson[amnezia::protocols::xray::inbounds] = QJsonArray { inboundObj };
clientJson[amnezia::protocols::xray::outbounds] = QJsonArray { outbound };
QString config = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
// Return
XrayProtocolConfig protocolConfig;
protocolConfig.serverConfig = srv;
XrayClientConfig clientConfig;
clientConfig.nativeConfig = config;
qDebug() << "config:" << config;
clientConfig.localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort);
clientConfig.id = xrayClientId;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
return buildClientProtocolConfig(credentials, container, srv, xrayClientId, errorCode);
}

View File

@@ -23,12 +23,37 @@ public:
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
amnezia::ProtocolConfig protocolConfig) override;
amnezia::ErrorCode applyServerSettingsToRemote(const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container,
amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
bool appendNewClient,
QString *outClientId = nullptr);
private:
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode);
// Builds the native xray "streamSettings" JSON object from XrayServerConfig
amnezia::ErrorCode uploadServerConfigJson(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::DnsSettings &dnsSettings, const QJsonObject &serverConfig) const;
amnezia::XrayProtocolConfig buildClientProtocolConfig(const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container,
const amnezia::XrayServerConfig &srv,
const QString &clientId,
amnezia::ErrorCode &errorCode,
const QString &prefetchedRealityPublicKey = {},
const QString &prefetchedRealityShortId = {}) const;
amnezia::ErrorCode readRealityKeyFiles(amnezia::DockerContainer container,
const amnezia::ServerCredentials &credentials,
QString &outPublicKey,
QString &outShortId) const;
QJsonObject mergeStreamSettingsForServerInbound(const amnezia::XrayServerConfig &srv,
const QJsonObject &existingStreamSettings) const;
QJsonObject buildStreamSettings(const amnezia::XrayServerConfig &srv,
const QString &clientId) const;
};

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -6,9 +6,7 @@
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/utilities.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/serverConfigUtils.h"
#include "version.h"
#include "core/utils/containerEnum.h"
@@ -51,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;
@@ -67,13 +143,15 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
bool isApiConfig = false;
const auto kind = m_serversRepository->serverKind(serverId);
const QString primaryDns = m_appSettingsRepository->primaryDns();
const QString secondaryDns = m_appSettingsRepository->secondaryDns();
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = { cfg->dns1, cfg->dns2 };
dns = cfg->getDnsPair(m_appSettingsRepository->useAmneziaDns(), primaryDns, secondaryDns);
hostName = cfg->hostName;
description = cfg->description;
break;
@@ -83,7 +161,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = { cfg->dns1, cfg->dns2 };
dns = cfg->getDnsPair(primaryDns, secondaryDns);
hostName = cfg->hostName;
description = cfg->description;
break;
@@ -93,7 +171,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = { cfg->dns1, cfg->dns2 };
dns = cfg->getDnsPair(primaryDns, secondaryDns);
hostName = cfg->hostName;
description = cfg->description;
break;
@@ -105,7 +183,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = { cfg->dns1, cfg->dns2 };
dns = cfg->getDnsPair(primaryDns, secondaryDns);
hostName = cfg->hostName;
description = cfg->description;
configVersion = serverConfigUtils::ConfigSource::AmneziaGateway;
@@ -120,20 +198,6 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
return ErrorCode::InternalError;
}
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) {
if (m_appSettingsRepository->useAmneziaDns()) {
dns.first = protocols::dns::amneziaDnsIp;
} else {
dns.first = m_appSettingsRepository->primaryDns();
}
}
if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) {
dns.second = m_appSettingsRepository->secondaryDns();
}
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
containerConfigModel, container);

View File

@@ -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;

View File

@@ -178,7 +178,8 @@ void CoreController::initControllers()
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel,
#endif
m_sftpConfigModel, m_socks5ConfigModel, m_mtProxyConfigModel, m_telemtConfigModel, this);
m_sftpConfigModel, m_socks5ConfigModel, m_mtProxyConfigModel, m_telemtConfigModel,
m_connectionController, this);
setQmlContextProperty("InstallController", m_installUiController);
m_importController = new ImportUiController(m_importCoreController, this);
@@ -190,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);
@@ -212,15 +213,16 @@ 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);
m_subscriptionUiController = new SubscriptionUiController(m_serversController, m_apiServicesModel, m_servicesCatalogController, m_subscriptionController,
m_apiSubscriptionPlansModel, m_apiBenefitsModel, m_apiAccountInfoModel,
m_apiCountryModel, m_apiDevicesModel, m_settingsController, this);
m_apiCountryModel, m_apiDevicesModel, m_settingsController,
m_connectionController, this);
setQmlContextProperty("SubscriptionUiController", m_subscriptionUiController);
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
@@ -342,9 +344,6 @@ void CoreController::openConnectionByIndex(int serverIndex)
if (serverId.isEmpty()) {
return;
}
if (m_serversModel) {
m_serversModel->setProcessedServerIndex(serverIndex);
}
if (m_serversController) {
m_serversController->setDefaultServer(serverId);
}

View File

@@ -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();

View File

@@ -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"
@@ -125,9 +124,9 @@ void CoreSignalHandlers::initInstallControllerHandler()
{
connect(m_coreController->m_installController, &InstallController::serverIsBusy, m_coreController->m_installUiController, &InstallUiController::serverIsBusy);
connect(m_coreController->m_installUiController, &InstallUiController::cancelInstallation, m_coreController->m_installController, &InstallController::cancelInstallation);
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIndexChanged,
m_coreController->m_installUiController, [this](int serverIndex) {
if (serverIndex >= 0) {
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIdChanged,
m_coreController->m_installUiController, [this](const QString &serverId) {
if (!serverId.isEmpty()) {
m_coreController->m_installUiController->clearProcessedServerCredentials();
}
});
@@ -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()

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -20,6 +20,7 @@
#include "core/installers/sftpInstaller.h"
#include "core/installers/socks5Installer.h"
#include "core/installers/mtProxyInstaller.h"
#include "core/configurators/xrayConfigurator.h"
#include "core/installers/telemtInstaller.h"
#include "core/installers/torInstaller.h"
#include "core/installers/wireguardInstaller.h"
@@ -71,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,
@@ -119,9 +130,10 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return e;
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
sshSession.runScript(credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
amnezia::genBaseVars(credentials, container, QString(), QString())));
const amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString());
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";
@@ -146,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);
@@ -179,7 +191,17 @@ 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) {
const auto *oldXrayConfig = oldConfig.getXrayProtocolConfig();
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
if (oldXrayConfig && newXrayConfig) {
xrayServerSettingsChanged =
!oldXrayConfig->serverConfig.hasEqualServerSettings(newXrayConfig->serverConfig);
}
}
ErrorCode errorCode = ErrorCode::NoError;
if (reinstallRequired) {
@@ -191,6 +213,21 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
}
}
const bool skipXrayInboundSync =
newConfig.getXrayProtocolConfig() && newConfig.getXrayProtocolConfig()->serverConfig.isThirdPartyConfig;
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
XrayConfigurator xrayConfigurator(&sshSession);
qDebug() << "InstallController::updateServerConfig applying Xray server inbound sync, reinstall="
<< reinstallRequired;
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
if (errorCode != ErrorCode::NoError) {
qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
<< static_cast<int>(errorCode);
}
}
if (errorCode == ErrorCode::NoError) {
if (container == DockerContainer::MtProxy) {
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
@@ -205,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) {
@@ -216,9 +288,9 @@ void InstallController::clearCachedProfile(const QString &serverId, DockerContai
return;
}
adminConfig->clearCachedClientProfile(container);
const ContainerConfig containerConfigModel = adminConfig->containerConfig(container);
adminConfig->clearCachedClientProfile(container);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
emit clientRevocationRequested(serverId, containerConfigModel, container);
@@ -327,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;
@@ -638,12 +710,19 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
}
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
const auto* oldXrayConfig = oldConfig.getXrayProtocolConfig();
const auto* newXrayConfig = newConfig.getXrayProtocolConfig();
const auto *oldXrayConfig = oldConfig.getXrayProtocolConfig();
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
if (oldXrayConfig && newXrayConfig) {
if (oldXrayConfig->serverConfig.port != newXrayConfig->serverConfig.port)
const QString oldPort = oldXrayConfig->serverConfig.port.isEmpty()
? QString(protocols::xray::defaultPort)
: oldXrayConfig->serverConfig.port;
const QString newPort = newXrayConfig->serverConfig.port.isEmpty()
? QString(protocols::xray::defaultPort)
: newXrayConfig->serverConfig.port;
if (oldPort != newPort) {
return true;
}
}
}
@@ -757,8 +836,8 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
if (container == DockerContainer::Awg2) {
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = regex.match(stdOut);
QRegularExpression kernelVersionRegex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = kernelVersionRegex.match(stdOut);
if (match.hasMatch()) {
int majorVersion = match.captured(1).toInt();
int minorVersion = match.captured(2).toInt();
@@ -771,8 +850,19 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("command not found"))
if (stdOut.contains("Container runtime is not supported"))
return ErrorCode::ServerContainerRuntimeNotSupported;
QRegularExpression notFoundRegex(
R"(^.*(?:sudo:|docker:).*not found.*$)",
QRegularExpression::MultilineOption);
if (notFoundRegex.match(stdOut).hasMatch()) {
return ErrorCode::ServerDockerFailedError;
}
if (stdOut.contains("Container runtime service not running"))
return ErrorCode::ContainerRuntimeServiceNotRunning;
return error;
}
@@ -809,7 +899,7 @@ ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials,
return ErrorCode::ServerUserNotInSudo;
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
return ErrorCode::ServerUserDirectoryNotAccessible;
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
if (stdOut.contains(QRegularExpression(R"(\bsudoers\b)")) || stdOut.contains("is not allowed to") || stdOut.contains("can't do that"))
return ErrorCode::ServerUserNotAllowedInSudoers;
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
return ErrorCode::ServerUserPasswordRequired;
@@ -942,10 +1032,11 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
return ErrorCode::InternalError;
}
SshSession sshSession(this);
ErrorCode errorCode = sshSession.runScript(
credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
amnezia::genBaseVars(credentials, container, QString(), QString())));
const amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString());
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;
@@ -1130,7 +1221,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());
@@ -1200,28 +1291,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;
}
@@ -1425,7 +1514,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;
}
@@ -1450,7 +1539,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;
}

View File

@@ -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);

View File

@@ -217,6 +217,11 @@ void SettingsController::toggleAutoStart(bool enable)
bool SettingsController::isStartMinimizedEnabled() const
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
if (!isAutoStartEnabled()) {
return false;
}
#endif
return m_appSettingsRepository->isStartMinimized();
}
@@ -358,6 +363,6 @@ void SettingsController::disablePremV1MigrationReminder()
QString SettingsController::nextAvailableServerName() const
{
return m_appSettingsRepository->nextAvailableServerName();
return m_serversRepository->nextAvailableServerName();
}

View File

@@ -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();
});

View File

@@ -1,15 +1,17 @@
#include "socks5Installer.h"
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/utilities.h"
#include <QRegularExpression>
using namespace amnezia;
using namespace ProtocolUtils;
@@ -33,10 +35,29 @@ ContainerConfig Socks5Installer::generateConfig(DockerContainer container, int p
ErrorCode Socks5Installer::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, ContainerConfig &config)
{
Q_UNUSED(container);
Q_UNUSED(credentials);
Q_UNUSED(sshSession);
Q_UNUSED(config);
if (container != DockerContainer::Socks5Proxy || !sshSession) {
return ErrorCode::NoError;
}
Socks5ProxyProtocolConfig *socks5Config = config.getSocks5ProxyProtocolConfig();
if (!socks5Config) {
return ErrorCode::NoError;
}
ErrorCode readError = ErrorCode::NoError;
const QByteArray configRaw = sshSession->getTextFileFromContainer(
container, credentials, QString::fromUtf8(protocols::socks5Proxy::proxyConfigPath), readError);
if (readError != ErrorCode::NoError || configRaw.trimmed().isEmpty()) {
return ErrorCode::NoError;
}
const QString proxyConfig = QString::fromUtf8(configRaw);
static const QRegularExpression usernameAndPasswordRegExp(QStringLiteral("users (\\w+):CL:(\\w+)"));
const QRegularExpressionMatch usernameAndPasswordMatch = usernameAndPasswordRegExp.match(proxyConfig);
if (usernameAndPasswordMatch.hasMatch()) {
socks5Config->userName = usernameAndPasswordMatch.captured(1);
socks5Config->password = usernameAndPasswordMatch.captured(2);
}
return ErrorCode::NoError;
}

View File

@@ -13,6 +13,7 @@
#include "core/utils/api/apiUtils.h"
#include "core/models/api/apiConfig.h"
#include "core/models/api/authData.h"
#include "core/utils/networkUtilities.h"
namespace amnezia
{
@@ -67,6 +68,20 @@ ContainerConfig ApiV2ServerConfig::containerConfig(DockerContainer container) co
return containers.value(container);
}
QPair<QString, QString> ApiV2ServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
{
QString d1 = dns1;
QString d2 = dns2;
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
d1 = primaryDns;
}
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
d2 = secondaryDns;
}
return { d1, d2 };
}
QJsonObject ApiV2ServerConfig::toJson() const
{
QJsonObject obj;
@@ -80,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;
@@ -134,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();

View File

@@ -3,6 +3,7 @@
#include <QJsonObject>
#include <QMap>
#include <QPair>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
@@ -43,6 +44,9 @@ struct ApiV2ServerConfig {
bool isExternalPremium() const;
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const;
static ApiV2ServerConfig fromJson(const QJsonObject& json);
};

View File

@@ -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);

View File

@@ -108,35 +108,114 @@ QJsonObject XrayXhttpConfig::toJson() const
return obj;
}
namespace
{
XrayXhttpConfig clearedXhttpConfig()
{
XrayXhttpConfig c;
c.mode = QString();
c.host = QString();
c.path = QString();
c.headersTemplate = QString();
c.uplinkMethod = QString();
c.disableGrpc = false;
c.disableSse = false;
c.sessionPlacement = QString();
c.sessionKey = QString();
c.seqPlacement = QString();
c.seqKey = QString();
c.uplinkDataPlacement = QString();
c.uplinkDataKey = QString();
c.uplinkChunkSize = QString();
c.scMaxBufferedPosts = QString();
c.scMaxEachPostBytesMin = QString();
c.scMaxEachPostBytesMax = QString();
c.scMinPostsIntervalMsMin = QString();
c.scMinPostsIntervalMsMax = QString();
c.scStreamUpServerSecsMin = QString();
c.scStreamUpServerSecsMax = QString();
return c;
}
} // namespace
XrayXhttpConfig XrayXhttpConfig::fromJson(const QJsonObject &json)
{
XrayXhttpConfig c;
c.mode = json.value(configKey::xhttpMode).toString(protocols::xray::defaultXhttpMode);
c.host = json.value(configKey::xhttpHost).toString(protocols::xray::defaultSite);
c.path = json.value(configKey::xhttpPath).toString();
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString(protocols::xray::defaultXhttpHeadersTemplate);
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString(protocols::xray::defaultXhttpUplinkMethod);
c.disableGrpc = json.value(configKey::xhttpDisableGrpc).toBool(true);
c.disableSse = json.value(configKey::xhttpDisableSse).toBool(true);
if (json.isEmpty()) {
return clearedXhttpConfig();
}
c.sessionPlacement = json.value(configKey::xhttpSessionPlacement).toString(protocols::xray::defaultXhttpSessionPlacement);
c.sessionKey = json.value(configKey::xhttpSessionKey).toString();
c.seqPlacement = json.value(configKey::xhttpSeqPlacement).toString(protocols::xray::defaultXhttpSessionPlacement);
c.seqKey = json.value(configKey::xhttpSeqKey).toString();
c.uplinkDataPlacement = json.value(configKey::xhttpUplinkDataPlacement).toString(protocols::xray::defaultXhttpUplinkDataPlacement);
c.uplinkDataKey = json.value(configKey::xhttpUplinkDataKey).toString();
XrayXhttpConfig c = clearedXhttpConfig();
c.uplinkChunkSize = json.value(configKey::xhttpUplinkChunkSize).toString("0");
c.scMaxBufferedPosts = json.value(configKey::xhttpScMaxBufferedPosts).toString();
c.scMaxEachPostBytesMin = json.value(configKey::xhttpScMaxEachPostBytesMin).toString("1");
c.scMaxEachPostBytesMax = json.value(configKey::xhttpScMaxEachPostBytesMax).toString("100");
c.scMinPostsIntervalMsMin = json.value(configKey::xhttpScMinPostsIntervalMsMin).toString("100");
c.scMinPostsIntervalMsMax = json.value(configKey::xhttpScMinPostsIntervalMsMax).toString("800");
c.scStreamUpServerSecsMin = json.value(configKey::xhttpScStreamUpServerSecsMin).toString("1");
c.scStreamUpServerSecsMax = json.value(configKey::xhttpScStreamUpServerSecsMax).toString("100");
if (json.contains(configKey::xhttpMode)) {
c.mode = json.value(configKey::xhttpMode).toString();
}
if (json.contains(configKey::xhttpHost)) {
c.host = json.value(configKey::xhttpHost).toString();
}
if (json.contains(configKey::xhttpPath)) {
c.path = json.value(configKey::xhttpPath).toString();
}
if (json.contains(configKey::xhttpHeadersTemplate)) {
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString();
}
if (json.contains(configKey::xhttpUplinkMethod)) {
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString();
}
if (json.contains(configKey::xhttpDisableGrpc)) {
c.disableGrpc = json.value(configKey::xhttpDisableGrpc).toBool();
}
if (json.contains(configKey::xhttpDisableSse)) {
c.disableSse = json.value(configKey::xhttpDisableSse).toBool();
}
if (json.contains(configKey::xhttpSessionPlacement)) {
c.sessionPlacement = json.value(configKey::xhttpSessionPlacement).toString();
}
if (json.contains(configKey::xhttpSessionKey)) {
c.sessionKey = json.value(configKey::xhttpSessionKey).toString();
}
if (json.contains(configKey::xhttpSeqPlacement)) {
c.seqPlacement = json.value(configKey::xhttpSeqPlacement).toString();
}
if (json.contains(configKey::xhttpSeqKey)) {
c.seqKey = json.value(configKey::xhttpSeqKey).toString();
}
if (json.contains(configKey::xhttpUplinkDataPlacement)) {
c.uplinkDataPlacement = json.value(configKey::xhttpUplinkDataPlacement).toString();
}
if (json.contains(configKey::xhttpUplinkDataKey)) {
c.uplinkDataKey = json.value(configKey::xhttpUplinkDataKey).toString();
}
if (json.contains(configKey::xhttpUplinkChunkSize)) {
c.uplinkChunkSize = json.value(configKey::xhttpUplinkChunkSize).toString();
}
if (json.contains(configKey::xhttpScMaxBufferedPosts)) {
c.scMaxBufferedPosts = json.value(configKey::xhttpScMaxBufferedPosts).toString();
}
if (json.contains(configKey::xhttpScMaxEachPostBytesMin)) {
c.scMaxEachPostBytesMin = json.value(configKey::xhttpScMaxEachPostBytesMin).toString();
}
if (json.contains(configKey::xhttpScMaxEachPostBytesMax)) {
c.scMaxEachPostBytesMax = json.value(configKey::xhttpScMaxEachPostBytesMax).toString();
}
if (json.contains(configKey::xhttpScMinPostsIntervalMsMin)) {
c.scMinPostsIntervalMsMin = json.value(configKey::xhttpScMinPostsIntervalMsMin).toString();
}
if (json.contains(configKey::xhttpScMinPostsIntervalMsMax)) {
c.scMinPostsIntervalMsMax = json.value(configKey::xhttpScMinPostsIntervalMsMax).toString();
}
if (json.contains(configKey::xhttpScStreamUpServerSecsMin)) {
c.scStreamUpServerSecsMin = json.value(configKey::xhttpScStreamUpServerSecsMin).toString();
}
if (json.contains(configKey::xhttpScStreamUpServerSecsMax)) {
c.scStreamUpServerSecsMax = json.value(configKey::xhttpScStreamUpServerSecsMax).toString();
}
c.xPadding = XrayXPaddingConfig::fromJson(json.value("xPadding").toObject());
c.xmux = XrayXmuxConfig::fromJson(json.value("xmux").toObject());
if (json.contains(QLatin1String("xPadding"))) {
c.xPadding = XrayXPaddingConfig::fromJson(json.value(QLatin1String("xPadding")).toObject());
}
if (json.contains(QLatin1String("xmux"))) {
c.xmux = XrayXmuxConfig::fromJson(json.value(QLatin1String("xmux")).toObject());
}
return c;
}
@@ -156,12 +235,27 @@ QJsonObject XrayMkcpConfig::toJson() const
XrayMkcpConfig XrayMkcpConfig::fromJson(const QJsonObject &json)
{
XrayMkcpConfig c;
c.tti = json.value(configKey::mkcpTti).toString();
c.uplinkCapacity = json.value(configKey::mkcpUplinkCapacity).toString();
c.downlinkCapacity = json.value(configKey::mkcpDownlinkCapacity).toString();
c.readBufferSize = json.value(configKey::mkcpReadBufferSize).toString();
c.writeBufferSize = json.value(configKey::mkcpWriteBufferSize).toString();
c.congestion = json.value(configKey::mkcpCongestion).toBool(true);
if (json.isEmpty()) {
return c;
}
if (json.contains(configKey::mkcpTti)) {
c.tti = json.value(configKey::mkcpTti).toString();
}
if (json.contains(configKey::mkcpUplinkCapacity)) {
c.uplinkCapacity = json.value(configKey::mkcpUplinkCapacity).toString();
}
if (json.contains(configKey::mkcpDownlinkCapacity)) {
c.downlinkCapacity = json.value(configKey::mkcpDownlinkCapacity).toString();
}
if (json.contains(configKey::mkcpReadBufferSize)) {
c.readBufferSize = json.value(configKey::mkcpReadBufferSize).toString();
}
if (json.contains(configKey::mkcpWriteBufferSize)) {
c.writeBufferSize = json.value(configKey::mkcpWriteBufferSize).toString();
}
if (json.contains(configKey::mkcpCongestion)) {
c.congestion = json.value(configKey::mkcpCongestion).toBool();
}
return c;
}
@@ -208,8 +302,14 @@ QJsonObject XrayServerConfig::toJson() const
if (!transport.isEmpty()) {
obj[configKey::xrayTransport] = transport;
}
obj["xhttp"] = xhttp.toJson();
obj["mkcp"] = mkcp.toJson();
const QJsonObject xhttpObj = xhttp.toJson();
if (!xhttpObj.isEmpty()) {
obj[QStringLiteral("xhttp")] = xhttpObj;
}
const QJsonObject mkcpObj = mkcp.toJson();
if (!mkcpObj.isEmpty()) {
obj[QStringLiteral("mkcp")] = mkcpObj;
}
return obj;
}
@@ -225,20 +325,39 @@ XrayServerConfig XrayServerConfig::fromJson(const QJsonObject &json)
c.site = json.value(configKey::site).toString();
c.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false);
// New: Security
c.security = json.value(configKey::xraySecurity).toString(protocols::xray::defaultSecurity);
c.flow = json.value(configKey::xrayFlow).toString(protocols::xray::defaultFlow);
c.fingerprint = json.value(configKey::xrayFingerprint).toString(protocols::xray::defaultFingerprint);
if (c.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
c.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
if (json.contains(configKey::xraySecurity)) {
c.security = json.value(configKey::xraySecurity).toString();
}
if (json.contains(configKey::xrayFlow)) {
c.flow = json.value(configKey::xrayFlow).toString();
}
if (json.contains(configKey::xrayFingerprint)) {
c.fingerprint = json.value(configKey::xrayFingerprint).toString();
if (c.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
c.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
}
}
if (json.contains(configKey::xraySni)) {
c.sni = json.value(configKey::xraySni).toString();
}
if (json.contains(configKey::xrayAlpn)) {
c.alpn = json.value(configKey::xrayAlpn).toString();
}
if (json.contains(configKey::xrayTransport)) {
c.transport = json.value(configKey::xrayTransport).toString();
}
if (json.contains(QLatin1String("xhttp"))) {
const QJsonObject xhttpJson = json.value(QLatin1String("xhttp")).toObject();
if (!xhttpJson.isEmpty()) {
c.xhttp = XrayXhttpConfig::fromJson(xhttpJson);
}
}
if (json.contains(QLatin1String("mkcp"))) {
const QJsonObject mkcpJson = json.value(QLatin1String("mkcp")).toObject();
if (!mkcpJson.isEmpty()) {
c.mkcp = XrayMkcpConfig::fromJson(mkcpJson);
}
}
c.sni = json.value(configKey::xraySni).toString(protocols::xray::defaultSni);
c.alpn = json.value(configKey::xrayAlpn).toString(protocols::xray::defaultAlpn);
// New: Transport
c.transport = json.value(configKey::xrayTransport).toString(protocols::xray::defaultTransport);
c.xhttp = XrayXhttpConfig::fromJson(json.value("xhttp").toObject());
c.mkcp = XrayMkcpConfig::fromJson(json.value("mkcp").toObject());
return c;
}
@@ -251,7 +370,10 @@ bool XrayServerConfig::hasEqualServerSettings(const XrayServerConfig &other) con
&& flow == other.flow
&& transport == other.transport
&& fingerprint == other.fingerprint
&& sni == other.sni;
&& sni == other.sni
&& alpn == other.alpn
&& xhttp.toJson() == other.xhttp.toJson()
&& mkcp.toJson() == other.mkcp.toJson();
}
QJsonObject XrayClientConfig::toJson() const
@@ -351,9 +473,154 @@ XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject &json)
}
}
c.needsClientHydration =
c.hasClientConfig()
&& (!json.contains(configKey::xrayTransport) || c.serverConfig.isThirdPartyConfig);
if (c.needsClientHydration) {
c.hydrateServerConfigFromClientNative();
}
return c;
}
bool XrayProtocolConfig::hydrateServerConfigFromClientNative()
{
if (!clientConfig.has_value() || clientConfig->nativeConfig.isEmpty()) {
return false;
}
QJsonDocument doc = QJsonDocument::fromJson(clientConfig->nativeConfig.toUtf8());
if (doc.isNull() || !doc.isObject()) {
return false;
}
const QJsonObject root = doc.object();
const QJsonArray outbounds = root.value(protocols::xray::outbounds).toArray();
if (outbounds.isEmpty()) {
return false;
}
const QJsonObject outbound = outbounds[0].toObject();
const QJsonObject streamSettings = outbound.value(protocols::xray::streamSettings).toObject();
if (streamSettings.isEmpty()) {
return false;
}
XrayServerConfig &srv = serverConfig;
const QJsonObject settings = outbound.value(protocols::xray::settings).toObject();
const QJsonArray vnext = settings.value(protocols::xray::vnext).toArray();
if (!vnext.isEmpty()) {
const QJsonObject vnextEntry = vnext[0].toObject();
if (vnextEntry.contains(protocols::xray::port)) {
srv.port = QString::number(vnextEntry.value(protocols::xray::port).toInt());
}
const QJsonArray users = vnextEntry.value(protocols::xray::users).toArray();
if (!users.isEmpty()) {
srv.flow = users[0].toObject().value(protocols::xray::flow).toString();
}
}
const QString networkVal = streamSettings.value(protocols::xray::network).toString(QStringLiteral("tcp"));
if (networkVal == QLatin1String("xhttp")) {
srv.transport = QStringLiteral("xhttp");
} else if (networkVal == QLatin1String("kcp")) {
srv.transport = QStringLiteral("mkcp");
} else {
srv.transport = QStringLiteral("raw");
}
if (streamSettings.contains(protocols::xray::security)) {
srv.security = streamSettings.value(protocols::xray::security).toString();
}
if (srv.security == QLatin1String("reality")) {
const QJsonObject rs = streamSettings.value(protocols::xray::realitySettings).toObject();
srv.sni = rs.value(protocols::xray::serverName).toString();
srv.site = srv.sni.isEmpty() ? srv.site : srv.sni;
const QString fp = rs.value(protocols::xray::fingerprint).toString();
if (!fp.isEmpty()) {
srv.fingerprint = fp.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)
? QString::fromLatin1(protocols::xray::defaultFingerprint)
: fp;
}
}
if (srv.security == QLatin1String("tls")) {
const QJsonObject tls = streamSettings.value(QStringLiteral("tlsSettings")).toObject();
srv.sni = tls.value(protocols::xray::serverName).toString();
const QString fp = tls.value(protocols::xray::fingerprint).toString();
if (!fp.isEmpty()) {
srv.fingerprint = fp;
}
QStringList alpnList;
for (const QJsonValue &v : tls.value(QStringLiteral("alpn")).toArray()) {
alpnList << v.toString();
}
if (!alpnList.isEmpty()) {
srv.alpn = alpnList.join(QLatin1Char(','));
}
}
if (srv.transport == QLatin1String("xhttp")) {
const QJsonObject xhttpObj = streamSettings.value(QStringLiteral("xhttpSettings")).toObject();
QJsonObject xhttpJson;
const QString mode = xhttpObj.value(QStringLiteral("mode")).toString();
if (!mode.isEmpty()) {
if (mode == QLatin1String("auto")) {
xhttpJson[configKey::xhttpMode] = QStringLiteral("Auto");
} else if (mode == QLatin1String("packet-up")) {
xhttpJson[configKey::xhttpMode] = QStringLiteral("Packet-up");
} else if (mode == QLatin1String("stream-up")) {
xhttpJson[configKey::xhttpMode] = QStringLiteral("Stream-up");
} else if (mode == QLatin1String("stream-one")) {
xhttpJson[configKey::xhttpMode] = QStringLiteral("Stream-one");
} else {
xhttpJson[configKey::xhttpMode] = mode;
}
}
if (xhttpObj.contains(QStringLiteral("host"))) {
xhttpJson[configKey::xhttpHost] = xhttpObj.value(QStringLiteral("host")).toString();
}
if (xhttpObj.contains(QStringLiteral("path"))) {
xhttpJson[configKey::xhttpPath] = xhttpObj.value(QStringLiteral("path")).toString();
}
if (xhttpObj.contains(QStringLiteral("uplinkHTTPMethod"))) {
xhttpJson[configKey::xhttpUplinkMethod] = xhttpObj.value(QStringLiteral("uplinkHTTPMethod")).toString();
}
xhttpJson[configKey::xhttpDisableGrpc] = xhttpObj.value(QStringLiteral("noGRPCHeader")).toBool(true);
xhttpJson[configKey::xhttpDisableSse] = xhttpObj.value(QStringLiteral("noSSEHeader")).toBool(true);
srv.xhttp = XrayXhttpConfig::fromJson(xhttpJson);
}
if (srv.transport == QLatin1String("mkcp")) {
const QJsonObject kcpObj = streamSettings.value(QStringLiteral("kcpSettings")).toObject();
XrayMkcpConfig mk;
if (kcpObj.contains(QStringLiteral("tti"))) {
mk.tti = QString::number(kcpObj.value(QStringLiteral("tti")).toInt());
}
if (kcpObj.contains(QStringLiteral("uplinkCapacity"))) {
mk.uplinkCapacity = QString::number(kcpObj.value(QStringLiteral("uplinkCapacity")).toInt());
}
if (kcpObj.contains(QStringLiteral("downlinkCapacity"))) {
mk.downlinkCapacity = QString::number(kcpObj.value(QStringLiteral("downlinkCapacity")).toInt());
}
if (kcpObj.contains(QStringLiteral("readBufferSize"))) {
mk.readBufferSize = QString::number(kcpObj.value(QStringLiteral("readBufferSize")).toInt());
}
if (kcpObj.contains(QStringLiteral("writeBufferSize"))) {
mk.writeBufferSize = QString::number(kcpObj.value(QStringLiteral("writeBufferSize")).toInt());
}
if (kcpObj.contains(QStringLiteral("congestion"))) {
mk.congestion = kcpObj.value(QStringLiteral("congestion")).toBool(true);
}
srv.mkcp = mk;
}
needsClientHydration = false;
return true;
}
bool XrayProtocolConfig::hasClientConfig() const
{
return clientConfig.has_value();

View File

@@ -75,6 +75,7 @@ struct XrayXhttpConfig {
XrayXmuxConfig xmux;
QJsonObject toJson() const;
/// Reads only keys present in JSON (no Amnezia UI defaults). Use XrayConfigModel::applyDefaultsToServerConfig for UI.
static XrayXhttpConfig fromJson(const QJsonObject &json);
};
@@ -99,15 +100,13 @@ struct XrayServerConfig {
QString site;
bool isThirdPartyConfig = false;
// New: Security
QString security = protocols::xray::defaultSecurity;
QString flow = protocols::xray::defaultFlow;
QString fingerprint = protocols::xray::defaultFingerprint;
QString sni = protocols::xray::defaultSni;
QString alpn = protocols::xray::defaultAlpn;
QString security;
QString flow;
QString fingerprint;
QString sni;
QString alpn;
// New: Transport
QString transport = protocols::xray::defaultTransport;
QString transport;
XrayXhttpConfig xhttp;
XrayMkcpConfig mkcp;
@@ -139,6 +138,10 @@ struct XrayProtocolConfig {
bool hasClientConfig() const;
void setClientConfig(const XrayClientConfig &config);
void clearClientConfig();
bool needsClientHydration = false;
bool hydrateServerConfigFromClientNative();
};
} // namespace amnezia

View File

@@ -9,6 +9,7 @@
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/networkUtilities.h"
namespace amnezia
{
@@ -28,6 +29,25 @@ 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;
QString d2 = dns2;
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
d1 = primaryDns;
}
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
d2 = secondaryDns;
}
return { d1, d2 };
}
QJsonObject NativeServerConfig::toJson() const
{
QJsonObject obj;
@@ -35,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;
}
@@ -70,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();

View File

@@ -3,6 +3,7 @@
#include <QJsonObject>
#include <QMap>
#include <QPair>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
@@ -25,6 +26,11 @@ 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;
static NativeServerConfig fromJson(const QJsonObject& json);
};

View File

@@ -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();

View File

@@ -8,6 +8,7 @@
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/networkUtilities.h"
namespace amnezia
{
@@ -42,6 +43,26 @@ 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
{
QString d1 = dns1;
QString d2 = dns2;
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
d1 = primaryDns;
}
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
d2 = secondaryDns;
}
return { d1, d2 };
}
QJsonObject SelfHostedUserServerConfig::toJson() const
{
QJsonObject obj;
@@ -49,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;
}
@@ -84,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();

View File

@@ -3,6 +3,7 @@
#include <QJsonObject>
#include <QMap>
#include <QPair>
#include <optional>
#include "core/utils/containerEnum.h"
@@ -30,6 +31,11 @@ struct SelfHostedUserServerConfig {
std::optional<ServerCredentials> credentials() const;
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;
static SelfHostedUserServerConfig fromJson(const QJsonObject &json);
};

View File

@@ -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);

View File

@@ -29,6 +29,7 @@ protected slots:
void onReadyReadDataFromManagementServer();
private:
void cleanupResources();
QString configPath() const;
bool openVpnProcessIsRunning() const;
bool sendTermSignal();

View File

@@ -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);

View File

@@ -90,8 +90,6 @@ public:
bool restoreAppConfig(const QByteArray &cfg);
void clearSettings();
QString nextAvailableServerName() const;
QByteArray xraySavedConfigs() const;
void setXraySavedConfigs(const QByteArray &data);

View File

@@ -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);

View File

@@ -48,6 +48,8 @@ public:
void clearServers();
QString nextAvailableServerName() const;
void invalidateCache();
signals:

View File

@@ -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";

View File

@@ -15,6 +15,8 @@ namespace amnezia
Awg2,
WireGuard,
OpenVpn,
Cloak,
ShadowSocks,
Ipsec,
Xray,
SSXray,

View File

@@ -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))

View File

@@ -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);

View File

@@ -35,6 +35,11 @@ namespace amnezia
ServerCgroupMountpoint = 212,
DockerPullRateLimit = 213,
ServerLinuxKernelTooOld = 214,
XrayServerConfigInvalid = 215,
XrayServerNoVlessClients = 216,
XrayRealityKeysReadFailed = 217,
ServerContainerRuntimeNotSupported = 218,
ContainerRuntimeServiceNotRunning = 219,
// Ssh connection errors
SshRequestDeniedError = 300,
@@ -76,6 +81,7 @@ namespace amnezia
ImportBackupFileUseRestoreInstead = 903,
RestoreBackupInvalidError = 904,
LegacyApiV1NotSupportedError = 905,
LegacyContainerNotSupportedError = 906,
// Android errors
AndroidError = 1000,
@@ -98,6 +104,10 @@ namespace amnezia
ApiSubscriptionNotActiveError = 1114,
ApiNoPurchasedSubscriptionsError = 1115,
ApiTrialAlreadyUsedError = 1116,
ApiCaptchaRequiredError = 1117,
ApiCaptchaInvalidError = 1118,
ApiCaptchaRefreshError = 1119,
ApiRateLimitError = 1120,
// QFile errors
OpenError = 1200,
@@ -116,5 +126,3 @@ namespace amnezia
Q_DECLARE_METATYPE(amnezia::ErrorCode)
#endif // ERRORCODES_H

View File

@@ -30,6 +30,17 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
case(ErrorCode::XrayServerConfigInvalid):
errorMessage = QObject::tr("Server error: invalid or unreadable XRay server configuration");
break;
case(ErrorCode::XrayServerNoVlessClients):
errorMessage = QObject::tr("Server error: XRay server has no VLESS clients");
break;
case(ErrorCode::XrayRealityKeysReadFailed):
errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server");
break;
case(ErrorCode::ServerContainerRuntimeNotSupported): errorMessage = QObject::tr("Server error: The default container runtime available for installation on this server is not supported.\n Install Docker Engine on the server manually and try again."); break;
case(ErrorCode::ContainerRuntimeServiceNotRunning): errorMessage = QObject::tr("Container runtime error: The container runtime service is not running.\n Check the container runtime service on the server, or wait about a minute and try again."); break;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
@@ -60,6 +71,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;
@@ -84,6 +96,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;

View File

@@ -295,6 +295,8 @@ amnezia::ScriptVars amnezia::genMtProxyVars(const ContainerConfig &containerConf
vars.append({{"$MTPROXY_PORT", c.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : c.port}});
vars.append({{"$MTPROXY_SECRET", c.secret}});
vars.append({{"$MTPROXY_REGENERATE_SECRET",
c.secret.isEmpty() ? QStringLiteral("1") : QStringLiteral("0")}});
vars.append({{"$MTPROXY_TAG", c.tag}});
vars.append({{"$MTPROXY_TRANSPORT_MODE",
c.transportMode.isEmpty() ? QString(protocols::mtProxy::transportModeStandard)
@@ -350,6 +352,8 @@ amnezia::ScriptVars amnezia::genTelemtVars(const ContainerConfig &containerConfi
vars.append({ { "$TELEMT_TOML_TLS", faketls ? QLatin1String("true") : QLatin1String("false") } });
vars.append({ { "$TELEMT_PORT", c.port.isEmpty() ? QString(protocols::telemt::defaultPort) : c.port } });
vars.append({ { "$TELEMT_SECRET", c.secret } });
vars.append({ { "$TELEMT_REGENERATE_SECRET",
c.secret.isEmpty() ? QStringLiteral("1") : QStringLiteral("0") } });
vars.append({ { "$TELEMT_TAG", c.tag } });
QString tlsDomain = c.tlsDomain;
if (tlsDomain.isEmpty()) {

View File

@@ -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)

View File

@@ -1,7 +1,8 @@
if which apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
elif which yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
elif which zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
elif which pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
if command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
if which apt-get > /dev/null 2>&1 || command -v apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1 || command -v dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
elif which yum > /dev/null 2>&1 || command -v yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
elif which zypper > /dev/null 2>&1 || command -v zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
elif which pacman > /dev/null 2>&1 || command -v pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1;\
fi;\
if sudo -n which $LOCK_CMD > /dev/null 2>&1 || command -v $LOCK_CMD > /dev/null 2>&1; then sudo -n $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi

View File

@@ -1,8 +1,8 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then opt="--version";\
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then opt="--version";\
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then opt="--version";\
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then opt="--version";\
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then opt="--version";\
else pm="uname"; opt="-a";\
fi;\
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\

View File

@@ -1,25 +1,34 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
else echo "Packet manager not found"; exit 1; fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then silent_inst="-yq install --install-recommends"; what_pkg="-s install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then silent_inst="-yq install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then silent_inst="-y -q install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then silent_inst="-nq install"; what_pkg="--dry-run install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="suse";\
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then silent_inst="-S --noconfirm --noprogressbar --quiet"; what_pkg="-Sp"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, What pkg command: $what_pkg, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg, Language: $LANG";\
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\
if ! command -v docker > /dev/null 2>&1; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl enable --now docker; sleep 5;\
if ! sudo -n sh -c 'command -v which > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst which; fi;\
if ! sudo -n sh -c 'command -v fuser > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst psmisc; fi;\
if ! sudo -n sh -c 'command -v lsof > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst lsof; fi;\
if ! sudo -n sh -c 'command -v docker > /dev/null 2>&1'; then \
sudo -n $pm $check_pkgs;\
if ! sudo -n $pm $what_pkg $docker_pkg 2>/dev/null | grep -qi podman; then \
sudo -n $pm $silent_inst $docker_pkg;\
sleep 5; sudo -n systemctl enable --now docker; sleep 5;\
else \
echo "Container runtime is not supported";\
exit 1;\
fi;\
fi;\
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! command -v apparmor_parser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor; fi;\
if [ "$(sudo -n cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! sudo -n sh -c 'command -v apparmor_parser > /dev/null 2>&1'; then \
sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst apparmor;\
fi;\
fi;\
if [ "$(systemctl is-active docker)" != "active" ]; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl start docker; sleep 5;\
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then \
sleep 5; sudo -n systemctl start docker; sleep 5;\
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then echo "Container runtime service not running"; fi;\
fi;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
docker --version;\
sudo -n docker --version || docker --version;\
uname -sr

View File

@@ -4,8 +4,10 @@
curl -s https://core.telegram.org/getProxySecret -o /data/proxy-secret
curl -s https://core.telegram.org/getProxyConfig -o /data/proxy-multi.conf
# Determine secret: env var -> saved file -> generate new
if [ -n "$MTPROXY_SECRET" ]; then
# Determine secret: regenerate (fresh install) -> env var -> saved file -> generate new
if [ "$MTPROXY_REGENERATE_SECRET" = "1" ]; then
SECRET=$(openssl rand -hex 16)
elif [ -n "$MTPROXY_SECRET" ]; then
SECRET="$MTPROXY_SECRET"
elif [ -f /data/secret ]; then
SECRET=$(cat /data/secret)

View File

@@ -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 --format '{{.Name}}' | grep '^amnezia-' | xargs -r 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

View File

@@ -1,3 +1,3 @@
sudo docker stop $CONTAINER_NAME;\
sudo docker rm -fv $CONTAINER_NAME;\
sudo docker rmi $CONTAINER_NAME
sudo docker rmi $CONTAINER_NAME;

View File

@@ -4,8 +4,10 @@
echo "[*] Amnezia Telemt: configure script start"
mkdir -p /data/tlsfront
# Secret: substituted $TELEMT_SECRET -> saved file -> openssl (same rules as MTProxy configure)
if [ -n "$TELEMT_SECRET" ]; then
# Secret: regenerate (fresh install) -> env var -> saved file -> openssl
if [ "$TELEMT_REGENERATE_SECRET" = "1" ]; then
SECRET=$(openssl rand -hex 16)
elif [ -n "$TELEMT_SECRET" ]; then
SECRET="$TELEMT_SECRET"
elif [ -f /data/secret ]; then
SECRET=$(cat /data/secret)

View File

@@ -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)

View File

@@ -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"

View File

@@ -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>(), -1);
}
}
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"

View File

@@ -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>(), -1);
}
}
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"

View File

@@ -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>(), -1);
}
}
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"

View File

@@ -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>(), -1);
}
}
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"

View File

@@ -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>(), -1);
}
}
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"

View File

@@ -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>(), -1);
}
}
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"

View File

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

View File

@@ -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>(), -1);
}
}
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"

View File

@@ -1,265 +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);
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");
}
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"

View File

@@ -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>(), -1);
}
}
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"

View File

@@ -1,306 +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;
namespace {
int defaultServerRow(const QVector<ServerDescription> &descriptions, const QString &defaultServerId)
{
for (int i = 0; i < descriptions.size(); ++i) {
if (descriptions.at(i).serverId == defaultServerId) {
return i;
}
}
return -1;
}
} // namespace
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>(), -1);
}
}
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, defaultServerRow(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, defaultServerRow(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"

View File

@@ -65,6 +65,7 @@ SubscriptionUiController::SubscriptionUiController(ServersController* serversCon
ApiCountryModel* apiCountryModel,
ApiDevicesModel* apiDevicesModel,
SettingsController* settingsController,
ConnectionController* connectionController,
QObject *parent)
: QObject(parent),
m_serversController(serversController),
@@ -76,13 +77,34 @@ SubscriptionUiController::SubscriptionUiController(ServersController* serversCon
m_apiAccountInfoModel(apiAccountInfoModel),
m_apiCountryModel(apiCountryModel),
m_apiDevicesModel(apiDevicesModel),
m_settingsController(settingsController)
m_settingsController(settingsController),
m_connectionController(connectionController)
{
connect(m_apiServicesModel, &ApiServicesModel::serviceSelectionChanged, this, [this]() {
ApiServicesModel::ApiServicesData selectedServiceData = m_apiServicesModel->selectedServiceData();
m_apiSubscriptionPlansModel->updateModel(selectedServiceData.subscriptionPlansJson);
m_apiBenefitsModel->updateModel(selectedServiceData.benefits);
});
connect(this, &SubscriptionUiController::installServerFromApiFinished, this,
[this](const QString &, int preferredDefaultServerIndex) {
if (m_connectionController->isConnected()) {
return;
}
const int selectedServerIndex = preferredDefaultServerIndex >= 0
? preferredDefaultServerIndex
: (m_serversController->getServersCount() - 1);
const QString serverId = m_serversController->getServerId(selectedServerIndex);
if (!serverId.isEmpty()) {
m_serversController->setDefaultServer(serverId);
}
});
}
bool SubscriptionUiController::isCaptchaAwaitingUser() const
{
return m_captchaState.isPending;
}
bool SubscriptionUiController::exportVpnKey(const QString &serverId, const QString &fileName)
@@ -271,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());
@@ -366,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;
}

View File

@@ -5,6 +5,7 @@
#include "core/controllers/serversController.h"
#include "core/controllers/settingsController.h"
#include "core/controllers/connectionController.h"
#include "core/controllers/api/servicesCatalogController.h"
#include "core/controllers/api/subscriptionController.h"
#include "ui/models/api/apiSubscriptionPlansModel.h"
@@ -28,6 +29,7 @@ public:
ApiCountryModel* apiCountryModel,
ApiDevicesModel* apiDevicesModel,
SettingsController* settingsController,
ConnectionController* connectionController,
QObject *parent = nullptr);
Q_PROPERTY(QList<QString> qrCodes READ getQrCodes NOTIFY vpnKeyExportReady)
@@ -56,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);
@@ -83,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();
@@ -104,6 +124,7 @@ private:
ApiCountryModel* m_apiCountryModel;
ApiDevicesModel* m_apiDevicesModel;
SettingsController* m_settingsController;
ConnectionController* m_connectionController;
};
#endif // SUBSCRIPTIONUICONTROLLER_H

View File

@@ -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);
}

View File

@@ -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);
@@ -44,14 +46,16 @@ signals:
void connectionStateChanged();
void connectionErrorOccurred(ErrorCode errorCode);
void reconnectWithUpdatedContainer(const QString &message);
void connectButtonClicked();
void preparingConfig();
void prepareConfig();
void unsupportedConnectDrawerRequested();
void noInstalledContainers();
private:
Vpn::ConnectionState getCurrentConnectionState();
void notifyConnectionBlocked(ErrorCode errorCode);
ConnectionController* m_connectionController;
ServersController* m_serversController;

View File

@@ -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)

View File

@@ -12,6 +12,7 @@
#include "core/utils/api/apiUtils.h"
#include "core/controllers/selfhosted/installController.h"
#include "core/controllers/connectionController.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
@@ -51,6 +52,7 @@ InstallUiController::InstallUiController(InstallController *installController,
Socks5ProxyConfigModel *socks5ConfigModel,
MtProxyConfigModel* mtConfigModel,
TelemtConfigModel *telemtConfigModel,
ConnectionController *connectionController,
QObject *parent)
: QObject(parent),
m_installController(installController),
@@ -69,16 +71,11 @@ InstallUiController::InstallUiController(InstallController *installController,
m_sftpConfigModel(sftpConfigModel),
m_socks5ConfigModel(socks5ConfigModel),
m_mtProxyConfigModel(mtConfigModel),
m_telemtConfigModel(telemtConfigModel)
m_telemtConfigModel(telemtConfigModel),
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()
@@ -133,6 +130,10 @@ void InstallUiController::install(DockerContainer container, int port, Transport
finishMessage += tr("\nAdded containers that were already installed on the server");
}
if (!m_connectionController->isConnected()) {
m_serversController->setDefaultServer(newServerId);
}
emit installServerFinished(finishMessage);
} else {
const auto adminBefore = m_serversController->selfHostedAdminConfig(serverId);
@@ -172,7 +173,12 @@ void InstallUiController::install(DockerContainer container, int port, Transport
"All installed containers have been added to the application");
}
emit installContainerFinished(finishMessage, ContainerUtils::containerService(container) == ServiceType::Other);
const bool isServiceInstall = ContainerUtils::containerService(container) == ServiceType::Other;
if (!m_connectionController->isConnected() && !isServiceInstall) {
m_serversController->setDefaultContainer(serverId, container);
}
emit installContainerFinished(finishMessage, isServiceInstall);
}
}
@@ -205,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();
@@ -259,15 +263,54 @@ 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);
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
const bool asyncUpdate = container == DockerContainer::MtProxy || container == DockerContainer::Telemt
|| container == DockerContainer::Xray || container == DockerContainer::SSXray;
if (asyncUpdate) {
emit serverIsBusy(true);
auto *watcher = new QFutureWatcher<ErrorCode>(this);
const Proto protocolTypeCopy = protocolType;
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
[this, watcher, serverId, container, closePage]() {
[this, watcher, serverId, container, closePage, protocolTypeCopy]() {
const ErrorCode errorCode = watcher->result();
watcher->deleteLater();
emit serverIsBusy(false);
@@ -276,15 +319,8 @@ void InstallUiController::updateContainer(const QString &serverId, int container
const ContainerConfig updatedConfig =
m_serversController->getContainerConfig(serverId, container);
m_protocolModel->updateModel(updatedConfig);
const auto defaultContainer =
m_serversController->getDefaultContainer(serverId);
if ((serverId == m_serversController->getDefaultServerId())
&& (container == defaultContainer)) {
emit currentContainerUpdated();
} else {
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
}
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolTypeCopy));
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
} else {
emit installationErrorOccurred(errorCode);
}
@@ -295,25 +331,20 @@ void InstallUiController::updateContainer(const QString &serverId, int container
InstallController *installController = m_installController;
QFuture<ErrorCode> future =
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
newConfigCopy]() mutable -> ErrorCode {
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
newConfigCopy]() mutable -> ErrorCode {
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);
m_protocolModel->updateModel(updatedConfig);
const auto defaultContainer = m_serversController->getDefaultContainer(serverId);
if ((serverId == m_serversController->getDefaultServerId()) && (container == defaultContainer)) {
emit currentContainerUpdated();
} else {
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
}
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolType));
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
return;
}
@@ -427,6 +458,34 @@ void InstallUiController::removeContainer(const QString &serverId, int container
DockerContainer container = static_cast<DockerContainer>(containerIndex);
QString containerName = ContainerUtils::containerHumanNames().value(container);
const bool asyncRemove = container == DockerContainer::Xray || container == DockerContainer::SSXray;
if (asyncRemove) {
emit serverIsBusy(true);
auto *watcher = new QFutureWatcher<ErrorCode>(this);
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
[this, watcher, serverId, container, containerName, serverName]() {
const ErrorCode errorCode = watcher->result();
watcher->deleteLater();
emit serverIsBusy(false);
if (errorCode == ErrorCode::NoError) {
emit removeContainerFinished(
tr("%1 has been removed from the server '%2'").arg(containerName, serverName));
} else {
emit installationErrorOccurred(errorCode);
}
});
InstallController *installController = m_installController;
QFuture<ErrorCode> future = QtConcurrent::run(
[installController, serverId, container]() -> ErrorCode {
return installController->removeContainer(serverId, container);
});
watcher->setFuture(future);
return;
}
ErrorCode errorCode = m_installController->removeContainer(serverId, container);
if (errorCode == ErrorCode::NoError) {
@@ -517,6 +576,12 @@ void InstallUiController::setEncryptedPassphrase(QString passphrase)
void InstallUiController::addEmptyServer()
{
m_installController->addEmptyServer(m_processedServerCredentials);
if (!m_connectionController->isConnected()) {
const QString newServerId = m_serversController->getServerId(m_serversController->getServersCount() - 1);
if (!newServerId.isEmpty()) {
m_serversController->setDefaultServer(newServerId);
}
}
emit installServerFinished(tr("Server added successfully"));
}

View File

@@ -9,6 +9,7 @@
#include "core/utils/protocolEnum.h"
#include "core/controllers/serversController.h"
#include "core/controllers/settingsController.h"
#include "core/controllers/connectionController.h"
#include "core/controllers/selfhosted/usersController.h"
#include "core/controllers/selfhosted/installController.h"
#include "core/utils/errorCodes.h"
@@ -52,6 +53,7 @@ public:
Socks5ProxyConfigModel* socks5ConfigModel,
MtProxyConfigModel* mtConfigModel,
TelemtConfigModel* telemtConfigModel,
ConnectionController* connectionController,
QObject *parent = nullptr);
~InstallUiController();
@@ -62,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);
@@ -127,12 +130,9 @@ signals:
void serverIsBusy(const bool isBusy);
void cancelInstallation();
void currentContainerUpdated();
void cachedProfileCleared(const QString &message);
void apiConfigRemoved(const QString &message);
void noInstalledContainers();
void configValidated(bool isValid);
private:
@@ -155,12 +155,15 @@ private:
Socks5ProxyConfigModel* m_socks5ConfigModel;
MtProxyConfigModel* m_mtProxyConfigModel;
TelemtConfigModel* m_telemtConfigModel;
ConnectionController* m_connectionController;
ServerCredentials m_processedServerCredentials;
QString m_privateKeyPassphrase;
void updateProtocolConfigModel(const QString &serverId, int containerIndex, int protocolIndex);
bool buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig);
};
#endif // INSTALLUICONTROLLER_H

View File

@@ -31,6 +31,12 @@ bool descriptionsHaveGatewayServers(const QVector<ServerDescription> &list)
}
return false;
}
const ServerDescription &emptyServerDescription()
{
static const ServerDescription s_emptyDescription;
return s_emptyDescription;
}
} // namespace
ServersUiController::ServersUiController(ServersController* serversController,
SettingsController* settingsController,
@@ -100,8 +106,6 @@ void ServersUiController::setDefaultServer(const QString &serverId)
return;
}
m_serversController->setDefaultServer(serverId);
updateModel();
emit defaultServerIdChanged(serverId);
}
void ServersUiController::setDefaultContainer(const QString &serverId, int containerIndex)
@@ -120,12 +124,12 @@ void ServersUiController::toggleAmneziaDns(bool enabled)
updateModel();
}
void ServersUiController::onDefaultServerChanged(const QString &/*defaultServerId*/)
void ServersUiController::onDefaultServerChanged(const QString &defaultServerId)
{
updateModel();
setProcessedServerId(m_serversController->getDefaultServerId());
m_serversModel->setDefaultServerId(defaultServerId);
updateDefaultServerContainersModel();
emit defaultServerIdChanged(m_serversController->getDefaultServerId());
emit defaultServerIdChanged(defaultServerId);
}
void ServersUiController::updateModel()
@@ -136,29 +140,33 @@ void ServersUiController::updateModel()
const QString defaultServerId = m_serversController->getDefaultServerId();
const bool hadServersFromGatewayBefore = descriptionsHaveGatewayServers(m_orderedServerDescriptions);
const bool hasServersFromGatewayNow = descriptionsHaveGatewayServers(descriptions);
const int listCount = descriptions.size();
const int defaultRowInDescriptions = rowForServerId(descriptions, defaultServerId);
m_orderedServerDescriptions = descriptions;
if (listCount == 0) {
setProcessedServerId(QString());
} else if (m_processedServerIndex >= listCount) {
setProcessedServerId(defaultServerId);
if (m_orderedServerDescriptions.isEmpty()) {
if (!m_processedServerId.isEmpty()) {
setProcessedServerId(QString());
}
} else if (!m_processedServerId.isEmpty()) {
const int row = rowForServerId(m_orderedServerDescriptions, m_processedServerId);
if (row < 0) {
setProcessedServerId(defaultServerId);
} else {
setProcessedServerId(m_processedServerId);
setProcessedServerId(QString());
}
} else if (defaultRowInDescriptions >= 0) {
setProcessedServerId(defaultServerId);
}
m_serversModel->updateModel(m_orderedServerDescriptions, defaultRowInDescriptions);
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) {
@@ -166,7 +174,6 @@ void ServersUiController::updateModel()
}
emit defaultServerIdChanged(defaultServerId);
emit defaultServerIndexChanged(defaultServerIndex());
}
QString ServersUiController::getDefaultServerId() const
@@ -176,60 +183,35 @@ QString ServersUiController::getDefaultServerId() const
QString ServersUiController::getDefaultServerName() const
{
const QString defaultServerId = m_serversController->getDefaultServerId();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == defaultServerId) {
return description.serverName;
}
}
return QString();
return serverName(getDefaultServerId());
}
QString ServersUiController::getDefaultServerDefaultContainerName() const
{
const QString defaultServerId = m_serversController->getDefaultServerId();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == defaultServerId) {
return ContainerUtils::containerHumanNames().value(description.defaultContainer);
}
const auto &description = serverDescriptionById(getDefaultServerId());
if (description.serverId.isEmpty()) {
return QString();
}
return QString();
return ContainerUtils::containerHumanNames().value(description.defaultContainer);
}
QString ServersUiController::getDefaultServerDescriptionCollapsed() const
{
const QString defaultServerId = m_serversController->getDefaultServerId();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == defaultServerId) {
return description.collapsedServerDescription;
}
}
return QString();
return serverDescriptionById(getDefaultServerId()).collapsedServerDescription;
}
QString ServersUiController::getDefaultServerImagePathCollapsed() const
{
const QString defaultServerId = m_serversController->getDefaultServerId();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == defaultServerId) {
if (!description.isApiV2 || description.apiServerCountryCode.isEmpty()) {
return "";
}
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(description.apiServerCountryCode.toUpper());
}
const auto &description = serverDescriptionById(getDefaultServerId());
if (!description.isApiV2 || description.apiServerCountryCode.isEmpty()) {
return "";
}
return "";
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(description.apiServerCountryCode.toUpper());
}
QString ServersUiController::getDefaultServerDescriptionExpanded() const
{
const QString defaultServerId = m_serversController->getDefaultServerId();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == defaultServerId) {
return description.expandedServerDescription;
}
}
return QString();
return serverDescriptionById(getDefaultServerId()).expandedServerDescription;
}
bool ServersUiController::isDefaultServerDefaultContainerHasSplitTunneling() const
@@ -281,15 +263,75 @@ bool ServersUiController::isDefaultServerDefaultContainerHasSplitTunneling() con
bool ServersUiController::isDefaultServerFromApi() const
{
const QString defaultServerId = m_serversController->getDefaultServerId();
return isServerFromApi(getDefaultServerId());
}
bool ServersUiController::hasServerWithWriteAccess() const
{
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == defaultServerId) {
return description.isApiV2;
if (description.hasWriteAccess) {
return true;
}
}
return false;
}
QString ServersUiController::serverName(const QString &serverId) const
{
return serverDescriptionById(serverId).serverName;
}
QString ServersUiController::serverHostName(const QString &serverId) const
{
return serverDescriptionById(serverId).hostName;
}
int ServersUiController::serverDefaultContainer(const QString &serverId) const
{
const auto &description = serverDescriptionById(serverId);
return description.serverId.isEmpty() ? -1 : static_cast<int>(description.defaultContainer);
}
bool ServersUiController::isServerFromApi(const QString &serverId) const
{
return serverDescriptionById(serverId).isServerFromGatewayApi;
}
bool ServersUiController::isServerCountrySelectionAvailable(const QString &serverId) const
{
return serverDescriptionById(serverId).isCountrySelectionAvailable;
}
bool ServersUiController::isServerHasWriteAccess(const QString &serverId) const
{
return serverDescriptionById(serverId).hasWriteAccess;
}
bool ServersUiController::serverHasInstalledContainers(const QString &serverId) const
{
return serverDescriptionById(serverId).hasInstalledVpnContainers;
}
QString ServersUiController::serverAdEndpoint(const QString &serverId) const
{
return serverDescriptionById(serverId).adEndpoint;
}
bool ServersUiController::isServerRenewalAvailable(const QString &serverId) const
{
return serverDescriptionById(serverId).isRenewalAvailable;
}
bool ServersUiController::isServerSubscriptionExpired(const QString &serverId) const
{
return serverDescriptionById(serverId).isSubscriptionExpired;
}
bool ServersUiController::isServerSubscriptionExpiringSoon(const QString &serverId) const
{
return serverDescriptionById(serverId).isSubscriptionExpiringSoon;
}
int ServersUiController::getProcessedContainerIndex() const
{
return m_processedContainerIndex;
@@ -311,79 +353,31 @@ QString ServersUiController::getProcessedServerId() const
void ServersUiController::setProcessedServerId(const QString &serverId)
{
const int index = serverId.isEmpty() ? -1 : serverIndexForId(serverId);
if (!serverId.isEmpty() && index < 0) {
return;
}
const int newIndex = serverId.isEmpty() ? -1 : serverIndexForId(serverId);
const QString normalizedServerId = newIndex >= 0 ? serverId : QString();
if (m_processedServerIndex != index || m_processedServerId != serverId) {
m_processedServerIndex = index;
m_processedServerId = serverId;
m_serversModel->setProcessedServerIndex(index);
if (m_processedServerId != normalizedServerId) {
m_processedServerId = normalizedServerId;
if (index >= 0) {
updateContainersModel();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == serverId) {
setProcessedContainerIndex(static_cast<int>(description.defaultContainer));
break;
if (newIndex >= 0) {
if (isServerFromApi(m_processedServerId)) {
const auto &description = serverDescriptionById(m_processedServerId);
if (description.isApiV2 && description.isCountrySelectionAvailable
&& !description.apiAvailableCountries.isEmpty()) {
emit updateApiCountryModel();
}
}
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId != serverId) {
continue;
}
if (description.isApiV2) {
if (description.isCountrySelectionAvailable && !description.apiAvailableCountries.isEmpty()) {
emit updateApiCountryModel();
}
emit updateApiServicesModel();
}
break;
} else {
updateContainersModel();
}
}
emit processedServerIdChanged(m_processedServerId);
emit processedServerIndexChanged(m_processedServerIndex);
}
}
int ServersUiController::getProcessedServerIndex() const
{
return m_processedServerIndex;
}
void ServersUiController::setProcessedServerIndex(int index)
{
if (index < 0) {
setProcessedServerId(QString());
return;
}
const QString id = getServerId(index);
if (!id.isEmpty()) {
setProcessedServerId(id);
}
}
int ServersUiController::defaultServerIndex() const
{
return rowForServerId(m_orderedServerDescriptions, getDefaultServerId());
}
bool ServersUiController::processedServerIsPremium() const
{
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == m_processedServerId) {
return description.isPremium;
}
}
return false;
}
const ServerCredentials ServersUiController::getProcessedServerCredentials() const
{
return m_serversController->getServerCredentials(m_processedServerId);
return processedServerDescription().isPremium;
}
bool ServersUiController::isDefaultServerCurrentlyProcessed() const
@@ -393,18 +387,22 @@ bool ServersUiController::isDefaultServerCurrentlyProcessed() const
bool ServersUiController::isProcessedServerHasWriteAccess() const
{
ServerCredentials credentials = m_serversController->getServerCredentials(m_processedServerId);
return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty());
return isServerHasWriteAccess(m_processedServerId);
}
QString ServersUiController::getDefaultServerDescription(const QString &serverId) const
const ServerDescription &ServersUiController::processedServerDescription() const
{
return serverDescriptionById(m_processedServerId);
}
const ServerDescription &ServersUiController::serverDescriptionById(const QString &serverId) const
{
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId == serverId) {
return description.baseDescription;
return description;
}
}
return QString();
return emptyServerDescription();
}
bool ServersUiController::hasServersFromGatewayApi() const
@@ -467,6 +465,11 @@ int ServersUiController::getServerIndexById(const QString &serverId) const
return rowForServerId(m_orderedServerDescriptions, serverId);
}
int ServersUiController::getServersCount() const
{
return m_orderedServerDescriptions.size();
}
void ServersUiController::updateContainersModel()
{
if (m_processedServerId.isEmpty()) {

View File

@@ -19,7 +19,6 @@ class ServersUiController : public QObject
Q_OBJECT
Q_PROPERTY(QString defaultServerId READ getDefaultServerId NOTIFY defaultServerIdChanged)
Q_PROPERTY(int defaultServerIndex READ defaultServerIndex NOTIFY defaultServerIndexChanged)
Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerIdChanged)
Q_PROPERTY(QString defaultServerDefaultContainerName READ getDefaultServerDefaultContainerName NOTIFY defaultServerIdChanged)
@@ -30,9 +29,8 @@ class ServersUiController : public QObject
Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIdChanged)
Q_PROPERTY(QString processedServerId READ getProcessedServerId WRITE setProcessedServerId NOTIFY processedServerIdChanged)
Q_PROPERTY(int processedServerIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged)
Q_PROPERTY(int processedContainerIndex READ getProcessedContainerIndex WRITE setProcessedContainerIndex NOTIFY processedContainerIndexChanged)
Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerIndexChanged)
Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerIdChanged)
Q_PROPERTY(bool hasServersFromGatewayApi READ hasServersFromGatewayApi NOTIFY hasServersFromGatewayApiChanged)
@@ -72,20 +70,27 @@ public slots:
QString getDefaultServerDescriptionExpanded() const;
bool isDefaultServerDefaultContainerHasSplitTunneling() const;
bool isDefaultServerFromApi() const;
bool hasServerWithWriteAccess() const;
QString serverName(const QString &serverId) const;
QString serverHostName(const QString &serverId) const;
int serverDefaultContainer(const QString &serverId) const;
bool isServerFromApi(const QString &serverId) const;
bool isServerCountrySelectionAvailable(const QString &serverId) const;
bool isServerHasWriteAccess(const QString &serverId) const;
bool serverHasInstalledContainers(const QString &serverId) const;
QString serverAdEndpoint(const QString &serverId) const;
bool isServerRenewalAvailable(const QString &serverId) const;
bool isServerSubscriptionExpired(const QString &serverId) const;
bool isServerSubscriptionExpiringSoon(const QString &serverId) const;
QString getProcessedServerId() const;
void setProcessedServerId(const QString &serverId);
int getProcessedServerIndex() const;
void setProcessedServerIndex(int index);
int defaultServerIndex() const;
int getProcessedContainerIndex() const;
void setProcessedContainerIndex(int index);
bool processedServerIsPremium() const;
const ServerCredentials getProcessedServerCredentials() const;
bool isDefaultServerCurrentlyProcessed() const;
bool isProcessedServerHasWriteAccess() const;
@@ -97,25 +102,24 @@ public slots:
QString getServerId(int index) const;
int getServerIndexById(const QString &serverId) const;
int getServersCount() const;
QStringList getAllInstalledServicesName(int serverIndex) const;
signals:
void errorOccurred(const QString &errorMessage);
void finished(const QString &message);
void defaultServerIdChanged(const QString &serverId);
void defaultServerIndexChanged(int index);
void processedServerIdChanged(const QString &serverId);
void processedServerIndexChanged(int index);
void processedContainerIndexChanged(int index);
void hasServersFromGatewayApiChanged();
void updateApiCountryModel();
void updateApiServicesModel();
public:
void updateModel();
private:
QString getDefaultServerDescription(const QString &serverId) const;
const ServerDescription &serverDescriptionById(const QString &serverId) const;
const ServerDescription &processedServerDescription() const;
int serverIndexForId(const QString &serverId) const;
bool listHasServersFromGatewayApi() const;
@@ -130,7 +134,6 @@ private:
QVector<amnezia::ServerDescription> m_orderedServerDescriptions;
int m_processedServerIndex = -1;
QString m_processedServerId;
int m_processedContainerIndex = -1;
};

View File

@@ -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,14 @@ 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);
}
@@ -177,6 +176,8 @@ QString SettingsUiController::getAppVersion()
void SettingsUiController::clearSettings()
{
m_settingsController->clearSettings();
emit autoStartChanged();
emit startMinimizedChanged();
emit resetLanguageToSystem();
emit changeSettingsFinished(tr("All settings have been reset to default values"));
@@ -204,6 +205,8 @@ bool SettingsUiController::isAutoStartEnabled()
void SettingsUiController::toggleAutoStart(bool enable)
{
m_settingsController->toggleAutoStart(enable);
emit autoStartChanged();
emit startMinimizedChanged();
}
bool SettingsUiController::isStartMinimizedEnabled()

View File

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

View File

@@ -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)

View File

@@ -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";

View File

@@ -10,7 +10,8 @@ class ClientManagementModel : public QAbstractListModel
public:
enum Roles {
ClientNameRole = Qt::UserRole + 1,
ClientIdRole = Qt::UserRole + 1,
ClientNameRole,
CreationDateRole,
LatestHandshakeRole,
DataReceivedRole,

View File

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

View File

@@ -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";

View File

@@ -39,6 +39,8 @@ public:
IsSupportedRole,
IsShareableRole,
IsUnsupportedContainerRole,
InstallPageOrderRole,
// Container type check roles

View File

@@ -267,8 +267,13 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amne
m_container = container;
m_protocolConfig = protocolConfig;
if (m_protocolConfig.needsClientHydration) {
m_protocolConfig.hydrateServerConfigFromClientNative();
}
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
if (!m_protocolConfig.serverConfig.isThirdPartyConfig) {
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
}
m_originalProtocolConfig = m_protocolConfig;

View File

@@ -20,20 +20,25 @@
using namespace amnezia;
namespace {
int rowForServerId(const QVector<ServerDescription> &descriptions, const QString &serverId)
{
if (serverId.isEmpty()) {
return -1;
}
for (int i = 0; i < descriptions.size(); ++i) {
if (descriptions.at(i).serverId == serverId) {
return i;
}
}
return -1;
}
} // namespace
ServersModel::ServersModel(QObject *parent) : QAbstractListModel(parent)
{
connect(this, &ServersModel::defaultServerIndexChanged, this, &ServersModel::defaultServerNameChanged);
connect(this, &ServersModel::defaultServerIndexChanged, this, [this](const int serverIndex) {
if (serverIndex < 0 || serverIndex >= m_descriptions.size()) {
return;
}
auto defaultContainer = m_descriptions.at(serverIndex).defaultContainer;
emit ServersModel::defaultServerDefaultContainerChanged(defaultContainer);
emit ServersModel::defaultServerNameChanged();
});
connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged);
}
int ServersModel::rowCount(const QModelIndex &parent) const
@@ -56,52 +61,22 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
return row.serverName;
case ServerDescriptionRole:
return configVersion ? row.baseDescription : (row.baseDescription + row.hostName);
case CollapsedServerDescriptionRole:
return row.collapsedServerDescription;
case ExpandedServerDescriptionRole:
return row.expandedServerDescription;
case HostNameRole:
return row.hostName;
case CredentialsRole:
return QVariant::fromValue(serverCredentials(index.row()));
case ServerIdRole:
return row.serverId;
case CredentialsLoginRole:
return serverCredentials(index.row()).userName;
case IsDefaultRole:
return index.row() == m_defaultServerIndex;
case IsCurrentlyProcessedRole:
return index.row() == m_processedServerIndex;
return row.serverId == m_defaultServerId;
case HasWriteAccessRole:
return row.hasWriteAccess;
case ContainsAmneziaDnsRole:
return row.primaryDnsIsAmnezia;
case DefaultContainerRole:
return QVariant::fromValue(row.defaultContainer);
case HasInstalledContainers:
return row.hasInstalledVpnContainers;
case IsServerFromTelegramApiRole:
return false;
case IsServerFromGatewayApiRole:
return row.isServerFromGatewayApi;
case ApiConfigRole:
return QVariant();
case IsCountrySelectionAvailableRole:
return row.isCountrySelectionAvailable;
case ApiAvailableCountriesRole:
return row.apiAvailableCountries;
case ApiServerCountryCodeRole:
return row.apiServerCountryCode;
case HasAmneziaDns:
return row.primaryDnsIsAmnezia;
case IsAdVisibleRole:
return row.isAdVisible;
case AdHeaderRole:
return row.adHeader;
case AdDescriptionRole:
return row.adDescription;
case AdEndpointRole:
return row.adEndpoint;
case IsRenewalAvailableRole:
return row.isRenewalAvailable;
case IsSubscriptionExpiredRole:
return row.isSubscriptionExpired;
case IsSubscriptionExpiringSoonRole:
@@ -117,68 +92,32 @@ QVariant ServersModel::data(const int index, int role) const
return data(modelIndex, role);
}
void ServersModel::updateModel(const QVector<ServerDescription> &descriptions, int defaultServerIndex)
void ServersModel::updateModel(const QVector<ServerDescription> &descriptions,
const QString &defaultServerId)
{
beginResetModel();
m_descriptions = descriptions;
m_defaultServerIndex = defaultServerIndex;
m_defaultServerId = defaultServerId;
endResetModel();
emit defaultServerIndexChanged(m_defaultServerIndex);
emit processedServerChanged();
}
const int ServersModel::getDefaultServerIndex()
void ServersModel::setDefaultServerId(const QString &serverId)
{
return m_defaultServerIndex;
}
const int ServersModel::getServersCount()
{
return m_descriptions.size();
}
bool ServersModel::hasServerWithWriteAccess()
{
for (size_t i = 0; i < getServersCount(); i++) {
if (qvariant_cast<bool>(data(static_cast<int>(i), HasWriteAccessRole))) {
return true;
}
if (m_defaultServerId == serverId) {
return;
}
return false;
}
void ServersModel::setProcessedServerIndex(const int index)
{
if (m_processedServerIndex != index) {
m_processedServerIndex = index;
emit processedServerIndexChanged(m_processedServerIndex);
const int oldIndex = rowForServerId(m_descriptions, m_defaultServerId);
const int newIndex = rowForServerId(m_descriptions, serverId);
m_defaultServerId = serverId;
const QVector<int> roles = { IsDefaultRole };
if (oldIndex >= 0 && oldIndex < m_descriptions.size()) {
emit dataChanged(this->index(oldIndex), this->index(oldIndex), roles);
}
if (newIndex >= 0 && newIndex < m_descriptions.size()) {
emit dataChanged(this->index(newIndex), this->index(newIndex), roles);
}
}
const ServerCredentials ServersModel::getProcessedServerCredentials()
{
return serverCredentials(m_processedServerIndex);
}
bool ServersModel::isDefaultServerCurrentlyProcessed()
{
return m_defaultServerIndex == m_processedServerIndex;
}
bool ServersModel::isDefaultServerFromApi()
{
return data(m_defaultServerIndex, IsServerFromTelegramApiRole).toBool()
|| data(m_defaultServerIndex, IsServerFromGatewayApiRole).toBool();
}
bool ServersModel::isProcessedServerHasWriteAccess()
{
return qvariant_cast<bool>(data(m_processedServerIndex, HasWriteAccessRole));
}
bool ServersModel::isDefaultServerHasWriteAccess()
{
return qvariant_cast<bool>(data(m_defaultServerIndex, HasWriteAccessRole));
}
QHash<int, QByteArray> ServersModel::roleNames() const
@@ -187,41 +126,22 @@ QHash<int, QByteArray> ServersModel::roleNames() const
roles[NameRole] = "name";
roles[ServerDescriptionRole] = "serverDescription";
roles[CollapsedServerDescriptionRole] = "collapsedServerDescription";
roles[ExpandedServerDescriptionRole] = "expandedServerDescription";
roles[HostNameRole] = "hostName";
roles[ServerIdRole] = "serverId";
roles[CredentialsRole] = "credentials";
roles[CredentialsLoginRole] = "credentialsLogin";
roles[IsDefaultRole] = "isDefault";
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
roles[HasWriteAccessRole] = "hasWriteAccess";
roles[ContainsAmneziaDnsRole] = "containsAmneziaDns";
roles[DefaultContainerRole] = "defaultContainer";
roles[HasInstalledContainers] = "hasInstalledContainers";
roles[IsServerFromTelegramApiRole] = "isServerFromTelegramApi";
roles[IsServerFromGatewayApiRole] = "isServerFromGatewayApi";
roles[ApiConfigRole] = "apiConfig";
roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable";
roles[ApiAvailableCountriesRole] = "apiAvailableCountries";
roles[ApiServerCountryCodeRole] = "apiServerCountryCode";
roles[IsAdVisibleRole] = "isAdVisible";
roles[AdHeaderRole] = "adHeader";
roles[AdDescriptionRole] = "adDescription";
roles[AdEndpointRole] = "adEndpoint";
roles[IsRenewalAvailableRole] = "isRenewalAvailable";
roles[IsSubscriptionExpiredRole] = "isSubscriptionExpired";
roles[IsSubscriptionExpiringSoonRole] = "isSubscriptionExpiringSoon";
roles[HasAmneziaDns] = "hasAmneziaDns";
return roles;
}
@@ -233,40 +153,3 @@ ServerCredentials ServersModel::serverCredentials(int index) const
return m_descriptions.at(index).selfHostedSshCredentials;
}
bool ServersModel::isServerFromApi(const int serverIndex)
{
return data(serverIndex, IsServerFromTelegramApiRole).toBool()
|| data(serverIndex, IsServerFromGatewayApiRole).toBool();
}
QVariant ServersModel::getDefaultServerData(const QString roleString)
{
auto roles = roleNames();
for (auto it = roles.begin(); it != roles.end(); it++) {
if (QString(it.value()) == roleString) {
return data(m_defaultServerIndex, it.key());
}
}
return {};
}
QVariant ServersModel::getProcessedServerData(const QString &roleString)
{
auto roles = roleNames();
for (auto it = roles.begin(); it != roles.end(); it++) {
if (QString(it.value()) == roleString) {
return data(m_processedServerIndex, it.key());
}
}
return {};
}
bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
{
if (serverIndex < 0 || serverIndex >= m_descriptions.size()) {
return false;
}
return m_descriptions.at(serverIndex).hasInstalledVpnContainers;
}

View File

@@ -14,39 +14,22 @@ public:
enum Roles {
NameRole = Qt::UserRole + 1,
ServerDescriptionRole,
CollapsedServerDescriptionRole,
ExpandedServerDescriptionRole,
HostNameRole,
ServerIdRole,
CredentialsRole,
CredentialsLoginRole,
IsDefaultRole,
IsCurrentlyProcessedRole,
HasWriteAccessRole,
ContainsAmneziaDnsRole,
DefaultContainerRole,
HasInstalledContainers,
IsServerFromTelegramApiRole,
IsServerFromGatewayApiRole,
ApiConfigRole,
IsCountrySelectionAvailableRole,
ApiAvailableCountriesRole,
ApiServerCountryCodeRole,
IsAdVisibleRole,
AdHeaderRole,
AdDescriptionRole,
AdEndpointRole,
IsRenewalAvailableRole,
IsSubscriptionExpiredRole,
IsSubscriptionExpiringSoonRole,
HasAmneziaDns
};
ServersModel(QObject *parent = nullptr);
@@ -56,52 +39,19 @@ public:
QVariant data(const int index, int role = Qt::DisplayRole) const;
public slots:
const int getDefaultServerIndex();
bool isDefaultServerCurrentlyProcessed();
bool isDefaultServerFromApi();
bool isProcessedServerHasWriteAccess();
bool isDefaultServerHasWriteAccess();
bool hasServerWithWriteAccess();
const int getServersCount();
void setProcessedServerIndex(const int index);
const ServerCredentials getProcessedServerCredentials();
QVariant getProcessedServerData(const QString &roleString);
QVariant getDefaultServerData(const QString roleString);
bool isServerFromApi(const int serverIndex);
void updateModel(const QVector<amnezia::ServerDescription> &descriptions, int defaultServerIndex);
void updateModel(const QVector<amnezia::ServerDescription> &descriptions,
const QString &defaultServerId);
void setDefaultServerId(const QString &serverId);
protected:
QHash<int, QByteArray> roleNames() const override;
signals:
void processedServerIndexChanged(const int index);
void processedServerChanged();
void defaultServerIndexChanged(const int index);
void defaultServerNameChanged();
void defaultServerDescriptionChanged();
void defaultServerDefaultContainerChanged(const int containerIndex);
void updateApiCountryModel();
void updateApiServicesModel();
private:
ServerCredentials serverCredentials(int index) const;
bool serverHasInstalledContainers(const int serverIndex) const;
QVector<amnezia::ServerDescription> m_descriptions;
int m_defaultServerIndex = -1;
int m_processedServerIndex = -1;
QString m_defaultServerId;
};
#endif // SERVERSMODEL_H

View File

@@ -51,11 +51,11 @@ Rectangle {
}
Keys.onEnterPressed: {
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
Qt.openUrlExternally(ServersUiController.serverAdEndpoint(ServersUiController.defaultServerId))
}
Keys.onReturnPressed: {
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
Qt.openUrlExternally(ServersUiController.serverAdEndpoint(ServersUiController.defaultServerId))
}
RowLayout {
@@ -144,7 +144,7 @@ Rectangle {
onClicked: function() {
root.forceActiveFocus()
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
Qt.openUrlExternally(ServersUiController.serverAdEndpoint(ServersUiController.defaultServerId))
}
}
}

View File

@@ -182,7 +182,6 @@ Button {
}
onClicked: {
ServersUiController.setProcessedServerIndex(ServersUiController.defaultServerIndex)
ConnectionController.connectButtonClicked()
}

View File

@@ -13,7 +13,6 @@ Item {
onButtonStartChanged: {
if (buttonStart) {
ServersUiController.setProcessedServerIndex(ServersUiController.defaultServerIndex)
ConnectionController.connectButtonClicked()
}
}

View File

@@ -48,7 +48,7 @@ ListViewType {
showImage: !isInstalled
checkable: isInstalled && !ConnectionController.isConnected
checked: proxyDefaultServerContainersModel.mapToSource(index) === ServersModel.getDefaultServerData("defaultContainer")
checked: proxyDefaultServerContainersModel.mapToSource(index) === ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId)
onClicked: {
if (ConnectionController.isConnected && isInstalled) {
@@ -56,14 +56,17 @@ ListViewType {
return
}
if (checked) {
containersDropDown.closeTriggered()
ServersUiController.setDefaultContainer(ServersUiController.getServerId(ServersUiController.defaultServerIndex), 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 {

View File

@@ -46,7 +46,7 @@ DrawerType2 {
}
if (serverName.textField.text !== root.serverNameText) {
ServersUiController.editServerName(ServersUiController.getServerId(ServersUiController.processedServerIndex), serverName.textField.text);
ServersUiController.editServerName(ServersUiController.processedServerId, serverName.textField.text);
}
root.closeTriggered()
}

View File

@@ -17,7 +17,7 @@ import "../Config"
ListViewType {
id: root
property int selectedIndex: ServersUiController.defaultServerIndex
property int selectedIndex: ServersUiController.getServerIndexById(ServersUiController.defaultServerId)
anchors.top: serversMenuHeader.bottom
anchors.right: parent.right
@@ -29,8 +29,8 @@ ListViewType {
Connections {
target: ServersUiController
function onDefaultServerIndexChanged() {
root.selectedIndex = ServersUiController.defaultServerIndex
function onDefaultServerIdChanged() {
root.selectedIndex = ServersUiController.getServerIndexById(ServersUiController.defaultServerId)
}
}
@@ -106,14 +106,14 @@ ListViewType {
z: 1
onClicked: function() {
ServersUiController.processedServerIndex = index
ServersUiController.setProcessedServerId(serverId)
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
if (ServersUiController.isServerFromApi(ServersUiController.processedServerId)) {
if (ServersUiController.isServerCountrySelectionAvailable(ServersUiController.processedServerId)) {
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
} else {
PageController.showBusyIndicator(true)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.getServerId(ServersUiController.processedServerIndex), false)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.processedServerId, false)
PageController.showBusyIndicator(false)
if (!result) {
return

View File

@@ -5,7 +5,6 @@ import QtQuick.Layouts
import SortFilterProxyModel 0.2
import PageEnum 1.0
import ContainerProps 1.0
import "../Controls2"
import "../Controls2/TextTypes"
@@ -34,25 +33,25 @@ ListViewType {
if (isVpnContainer) {
// var isThirdPartyConfig = root.model.data(index, ContainersModel.IsThirdPartyConfigRole)
if (isThirdPartyConfig) {
InstallController.updateProtocols(ServersUiController.getServerId(ServersUiController.processedServerIndex), containerIndex)
InstallController.updateProtocols(ServersUiController.processedServerId, containerIndex)
PageController.goToPage(PageEnum.PageProtocolRaw)
return
}
}
if (isIpsec) {
InstallController.updateProtocols(ServersUiController.getServerId(ServersUiController.processedServerIndex), containerIndex)
InstallController.updateProtocols(ServersUiController.processedServerId, containerIndex)
PageController.goToPage(PageEnum.PageProtocolRaw)
} else if (isDns) {
PageController.goToPage(PageEnum.PageServiceDnsSettings)
} else if (isMtProxy) {
MtProxyConfigModel.updateModel(config)
PageController.goToPage(PageEnum.PageServiceMtProxySettings)
PageController.goToPage(PageEnum.PageServiceMtProxySettings, false)
} else if (isTelemt) {
TelemtConfigModel.updateModel(config)
PageController.goToPage(PageEnum.PageServiceTelemtSettings)
PageController.goToPage(PageEnum.PageServiceTelemtSettings, false)
} else {
InstallController.updateProtocols(ServersUiController.getServerId(ServersUiController.processedServerIndex), containerIndex)
InstallController.updateProtocols(ServersUiController.processedServerId, containerIndex)
PageController.goToPage(PageEnum.PageSettingsServerProtocol)
}

View File

@@ -15,7 +15,7 @@ DrawerType2 {
property bool isRenewalAvailable: false
onOpened: {
isRenewalAvailable = ServersModel.getDefaultServerData("isRenewalAvailable") && !ApiAccountInfoModel.data("isInAppPurchase")
isRenewalAvailable = ServersUiController.isServerRenewalAvailable(ServersUiController.defaultServerId) && !ApiAccountInfoModel.data("isInAppPurchase")
}
expandedStateContent: ColumnLayout {
@@ -43,7 +43,7 @@ DrawerType2 {
anchors.left: parent.left
anchors.right: parent.right
text: ServersModel.getDefaultServerData("name") + qsTr(" subscription has expired")
text: ServersUiController.serverName(ServersUiController.defaultServerId) + qsTr(" subscription has expired")
horizontalAlignment: Text.AlignLeft
}
}
@@ -76,7 +76,7 @@ DrawerType2 {
textColor: AmneziaStyle.color.midnightBlack
clickedFunc: function() {
SubscriptionUiController.getRenewalLink(ServersUiController.getServerId(ServersUiController.defaultServerIndex))
SubscriptionUiController.getRenewalLink(ServersUiController.defaultServerId)
}
}
@@ -96,7 +96,7 @@ DrawerType2 {
clickedFunc: function() {
PageController.showBusyIndicator(true)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.getServerId(ServersUiController.defaultServerIndex), false)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.defaultServerId, false)
PageController.showBusyIndicator(false)
if (result) {
root.closeTriggered()

View File

@@ -12,6 +12,8 @@ Item {
property int headerTextMaximumLineCount: 2
property int headerTextElide: Qt.ElideRight
property string descriptionText
property string descriptionLinkText
property string descriptionLinkUrl
property alias headerRow: headerRow
implicitWidth: content.implicitWidth
@@ -43,5 +45,26 @@ Item {
color: AmneziaStyle.color.mutedGray
visible: root.descriptionText !== ""
}
ParagraphTextType {
id: descriptionLink
Layout.topMargin: 16
Layout.fillWidth: true
text: root.descriptionLinkText !== "" && root.descriptionLinkUrl !== ""
? ("<a href=\"" + root.descriptionLinkUrl + "\" style=\"color: " + AmneziaStyle.color.goldenApricotString + ";\">" + root.descriptionLinkText + "</a>")
: ""
textFormat: Text.RichText
visible: root.descriptionLinkText !== ""
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
}
}

View 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)
}
}
}

View File

@@ -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
}
}

View File

@@ -25,8 +25,8 @@ PageType {
filters: [
ValueFilter {
roleName: "isCurrentlyProcessed"
value: true
roleName: "serverId"
value: ServersUiController.processedServerId
}
]
}

View File

@@ -344,14 +344,14 @@ PageType {
Keys.onReturnPressed: this.clicked()
onClicked: {
ServersUiController.setProcessedServerIndex(ServersUiController.defaultServerIndex)
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
if (ServersUiController.isServerFromApi(ServersUiController.processedServerId)) {
if (ServersUiController.isServerCountrySelectionAvailable(ServersUiController.processedServerId)) {
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
} else {
PageController.showBusyIndicator(true)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.getServerId(ServersUiController.processedServerIndex), false)
let result = SubscriptionUiController.getAccountInfo(ServersUiController.processedServerId, false)
PageController.showBusyIndicator(false)
if (!result) {
return
@@ -420,13 +420,13 @@ PageType {
target: ServersUiController
function onDefaultServerIndexChanged() {
function onDefaultServerIdChanged() {
updateContainersModelFilters()
}
}
function updateContainersModelFilters() {
if (ServersModel.isDefaultServerHasWriteAccess()) {
if (ServersUiController.isServerHasWriteAccess(ServersUiController.defaultServerId)) {
proxyDefaultServerContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters()
} else {
proxyDefaultServerContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters()

View File

@@ -435,13 +435,12 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
}
var noButtonFunction = function() {}

View File

@@ -555,13 +555,13 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
}
var noButtonFunction = function() {}

View File

@@ -428,13 +428,13 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
if (ConnectionController.isConnected && ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
}
var noButtonFunction = function() {
if (!GC.isMobile()) {

Some files were not shown because too many files have changed in this diff Show More