From 847bb6923b08d0d7f81bfdbbdbaa311aacc78397 Mon Sep 17 00:00:00 2001 From: vkamn Date: Thu, 30 Apr 2026 14:53:03 +0800 Subject: [PATCH] refactor: refactor the application to the mvvm architecture (#2009) * refactor: move business logic from servers model * refactor: move containersModel initialization * refactor: added protocol ui controller and removed settings class from protocols model * refactor: moved cli management to separate controller * refactor: moved app split to separate controller * refactor: moved site split to separate controller * refactor: moved allowed dns to separate controller * refactor: moved language logic to separate ui controller * refactor: removed Settings from devices model * refactor: moved configs and services api logit to separate core controller * refactor: added a layer with a repository between the storage and controllers * refactor: use child parent system instead of smart pointers for controllers and models initialization * refactor: moved install functions from server controller to install controller * refactor: install controller refactoring * chore: renamed exportController to exportUiController * refactor: separate export controller * refactor: removed VpnConfigurationsController * chore: renamed ServerController to SshSession * refactor: replaced ServerController to SshSession * chore: moved qml controllers to separate folder * chore: include fixes * chore: moved utils from core root to core/utils * chore: include fixes * chore: rename core/utils files to camelCase foramt * chore: include fixes * chore: moved some utils to api and selfhosted folders * chore: include fixes * chore: remove unused file * chore: moved serialization folder to core/utils * chore: include fixes * chore: moved some files from client root to core/utils * chore: include fixes * chore: moved ui utils to ui/utils folder * chore: include fixes * chore: move utils from root to ui/utils * chore: include fixes * chore: moved configurators to core/configurators * chore: include fixes * refactor: moved iap logic from ui controller to core * refactor: moved remaining core logic from ApiConfigsController to SubscriptionController * chore: rename apiNewsController to apiNewsUiController * refactor: moved core logic from news ui controller to core * chore: renamed apiConfigsController to subscriptionUiController * chore: include fixes * refactor: merge ApiSettingsController with SubscriptionUiController * chore: moved ui selfhosted controllers to separate folder * chore: include fixes * chore: rename connectionController to connectiomUiController * refactor: moved core logic from connectionUiController * chore: rename settingsController to settingsUiController * refactor: move core logic from settingsUiController * refactor: moved core controller signal/slot connections to separate class * fix: newsController fixes after refactoring * chore: rename model to camelCase * chore: include fixes * chore: remove unused code * chore: move selfhosted core to separate folder * chore: include fixes * chore: rename importController to importUiController * refactor: move core logic from importUiController * chore: minor fixes * chore: remove prem v1 migration * refactor: remove openvpn over cloak and openvpn over shadowsocks * refactor: removed protocolsForContainer function * refactor: add core models * refactor: replace json with c++ structs for server config * refactor: move getDnsPair to ServerConfigUtils * feat: add admin selfhosted config export test * feat: add multi import test * refactor: use coreController for tests * feat: add few simple tests * chore: qrepos in all core controllers * feat: add test for settings * refactor: remove repo dependency from configurators * chore: moved protocols to core folder * chore: include fixes * refactor: moved containersDefs, defs, apiDefs, protocolsDefs to different places * chore: include fixes * chore: build fixes * chore: build fixes * refactor: remove q repo and interface repo * feat: add test for ui servers model and controller * chore: renamed to camelCase * chore: include fixes * refactor: moved core logic from sites ui controller * fix: fixed api config processing * fix: fixed processed server index processing * refactor: protocol models now use c++ structs instead of json configs * refactor: servers model now use c++ struct instead of json config * fix: fixed default server index processing * fix: fix logs init * fix: fix secure settings load keys * chore: build fixes * fix: fixed clear settings * fix: fixed restore backup * fix: sshSession usage * fix: fixed export functions signatures * fix: return missing part from buildContainerWorker * fix: fixed server description on page home * refactor: add container config helpers functions * refactor: c++ structs instead of json * chore: add dns protocol config struct * refactor: move config utils functions to config structs * feat: add test for selfhosted server setup * refactor: separate resources.qrc * fix: fixed server rename * chore: return nameOverriddenByUser * fix: build fixes * fix: fixed models init * refactor: cleanup models usage * fix: fixed models init * chore: cleanup connections and functions signatures * chore: cleanup updateModel calls * feat: added cache to servers repo * chore: cleanup unused functions * chore: ssxray processing * chore: remove transportProtoWithDefault and portWithDefault functions * chore: removed proto types any and l2tp * refactor: moved some constants * fix: fixed native configs export * refactor: remove json from processConfigWith functions * fix: fixed processed server index usage * fix: qml warning fixes * chore: merge fixes * chore: update tests * fix: fixed xray config processing * fix: fixed split tunneling processing * chore: rename sites controllers and model * chore: rename fixes * chore: minor fixes * chore: remove ability to load backup from "file with connection settings" button * fix: fixed api device revoke * fix: remove full model update when renaming a user * fix: fixed premium/free server rename * fix: fixed selfhosted new server install * fix: fixed updateContainer function * fix: fixed revoke for external premium configs * feat: add native configs qr processing * chore: codestyle fixes * fix: fixed admin config create * chore: again remove ability to load backup from "file with connection settings" button * chore: minor fixes * fix: fixed variables initialization * fix: fixed qml imports * fix: minor fixes * fix: fix vpnConnection function calls * feat: add buckup error handling * fix: fixed admin config revok * fix: fixed selfhosted awg installation * fix: ad visability * feat: add empty check for primary dns * chore: minor fixes --- .clang-format-ignore | 2 +- client/CMakeLists.txt | 13 +- ...application.cpp => amneziaApplication.cpp} | 31 +- ...zia_application.h => amneziaApplication.h} | 8 +- client/android/build.gradle.kts | 1 - client/android/cloak/build.gradle.kts | 18 - client/android/cloak/src/main/kotlin/Cloak.kt | 45 - client/android/settings.gradle.kts | 1 - .../android/src/org/amnezia/vpn/VpnProto.kt | 11 +- client/android/xray/src/main/kotlin/Xray.kt | 4 +- client/cmake/android.cmake | 8 +- client/cmake/macos.cmake | 4 +- client/cmake/sources.cmake | 225 +- client/configurators/awg_configurator.cpp | 59 - client/configurators/awg_configurator.h | 18 - client/configurators/cloak_configurator.cpp | 51 - client/configurators/cloak_configurator.h | 20 - client/configurators/configurator_base.cpp | 26 - client/configurators/configurator_base.h | 33 - client/configurators/ikev2_configurator.h | 35 - client/configurators/openvpn_configurator.h | 43 - .../shadowsocks_configurator.cpp | 40 - .../configurators/shadowsocks_configurator.h | 19 - client/configurators/ssh_configurator.cpp | 112 - client/configurators/ssh_configurator.h | 22 - client/configurators/wireguard_configurator.h | 54 - client/configurators/xray_configurator.h | 23 - client/containers/containers_defs.h | 94 - client/core/configurators/awgConfigurator.cpp | 109 + client/core/configurators/awgConfigurator.h | 20 + .../core/configurators/configuratorBase.cpp | 50 + client/core/configurators/configuratorBase.h | 43 + .../configurators/ikev2Configurator.cpp} | 70 +- client/core/configurators/ikev2Configurator.h | 39 + .../configurators/openVpnConfigurator.cpp} | 153 +- .../core/configurators/openVpnConfigurator.h | 49 + .../configurators/wireguardConfigurator.cpp} | 167 +- .../configurators/wireguardConfigurator.h | 61 + .../configurators/xrayConfigurator.cpp} | 103 +- client/core/configurators/xrayConfigurator.h | 27 + .../core/controllers/allowedDnsController.cpp | 54 + .../core/controllers/allowedDnsController.h | 26 + .../core/controllers/api/newsController.cpp | 72 + client/core/controllers/api/newsController.h | 28 + .../api/servicesCatalogController.cpp | 248 ++ .../api/servicesCatalogController.h | 26 + .../api/subscriptionController.cpp | 1094 +++++++ .../controllers/api/subscriptionController.h | 122 + .../appSplitTunnelingController.cpp | 70 + .../controllers/appSplitTunnelingController.h | 32 + .../core/controllers/connectionController.cpp | 183 ++ .../core/controllers/connectionController.h | 78 + client/core/controllers/coreController.cpp | 447 ++- client/core/controllers/coreController.h | 220 +- .../core/controllers/coreSignalHandlers.cpp | 412 +++ client/core/controllers/coreSignalHandlers.h | 48 + client/core/controllers/gatewayController.cpp | 34 +- client/core/controllers/gatewayController.h | 4 +- .../ipSplitTunnelingController.cpp | 245 ++ .../controllers/ipSplitTunnelingController.h | 58 + .../selfhosted/exportController.cpp | 337 +++ .../controllers/selfhosted/exportController.h | 77 + .../selfhosted/importController.cpp | 762 +++++ .../controllers/selfhosted/importController.h | 91 + .../selfhosted/installController.cpp | 1179 ++++++++ .../selfhosted/installController.h | 117 + .../selfhosted/usersController.cpp | 807 ++++++ .../controllers/selfhosted/usersController.h | 76 + client/core/controllers/serverController.cpp | 887 ------ client/core/controllers/serverController.h | 87 - client/core/controllers/serversController.cpp | 205 ++ client/core/controllers/serversController.h | 96 + .../core/controllers/settingsController.cpp | 366 +++ client/core/controllers/settingsController.h | 112 + .../vpnConfigurationController.cpp | 146 - .../controllers/vpnConfigurationController.h | 37 - client/core/installers/awgInstaller.cpp | 200 ++ client/core/installers/awgInstaller.h | 21 + client/core/installers/installerBase.cpp | 116 + client/core/installers/installerBase.h | 32 + client/core/installers/openvpnInstaller.cpp | 73 + client/core/installers/openvpnInstaller.h | 17 + client/core/installers/sftpInstaller.cpp | 69 + client/core/installers/sftpInstaller.h | 18 + client/core/installers/socks5Installer.cpp | 42 + client/core/installers/socks5Installer.h | 18 + client/core/installers/torInstaller.cpp | 57 + client/core/installers/torInstaller.h | 17 + client/core/installers/wireguardInstaller.cpp | 51 + client/core/installers/wireguardInstaller.h | 17 + client/core/installers/xrayInstaller.cpp | 80 + client/core/installers/xrayInstaller.h | 17 + client/core/models/api/apiConfig.cpp | 223 ++ client/core/models/api/apiConfig.h | 77 + client/core/models/api/apiV1ServerConfig.cpp | 140 + client/core/models/api/apiV1ServerConfig.h | 47 + client/core/models/api/apiV2ServerConfig.cpp | 170 ++ client/core/models/api/apiV2ServerConfig.h | 52 + client/core/models/api/authData.cpp | 23 + client/core/models/api/authData.h | 24 + client/core/models/containerConfig.cpp | 147 + client/core/models/containerConfig.h | 73 + client/core/models/protocolConfig.cpp | 307 ++ client/core/models/protocolConfig.h | 88 + .../models/protocols/awgProtocolConfig.cpp | 345 +++ .../core/models/protocols/awgProtocolConfig.h | 103 + .../models/protocols/dnsProtocolConfig.cpp | 18 + .../core/models/protocols/dnsProtocolConfig.h | 17 + .../models/protocols/ikev2ProtocolConfig.cpp | 125 + .../models/protocols/ikev2ProtocolConfig.h | 48 + .../protocols/openVpnProtocolConfig.cpp | 164 ++ .../models/protocols/openVpnProtocolConfig.h | 55 + .../models/protocols/sftpProtocolConfig.cpp | 42 + .../models/protocols/sftpProtocolConfig.h | 22 + .../protocols/socks5ProxyProtocolConfig.cpp | 42 + .../protocols/socks5ProxyProtocolConfig.h | 22 + .../models/protocols/torProtocolConfig.cpp | 45 + .../core/models/protocols/torProtocolConfig.h | 27 + .../protocols/wireGuardProtocolConfig.cpp | 187 ++ .../protocols/wireGuardProtocolConfig.h | 60 + .../models/protocols/xrayProtocolConfig.cpp | 187 ++ .../models/protocols/xrayProtocolConfig.h | 48 + .../models/selfhosted/nativeServerConfig.cpp | 93 + .../models/selfhosted/nativeServerConfig.h | 34 + .../selfhosted/selfHostedServerConfig.cpp | 140 + .../selfhosted/selfHostedServerConfig.h | 45 + client/core/models/serverConfig.cpp | 234 ++ client/core/models/serverConfig.h | 92 + .../protocols/androidVpnProtocol.cpp} | 2 +- .../protocols/androidVpnProtocol.h} | 2 +- .../protocols/awgProtocol.cpp} | 2 +- .../protocols/awgProtocol.h} | 2 +- .../protocols/ikev2VpnProtocolWindows.cpp} | 13 +- .../protocols/ikev2VpnProtocolWindows.h} | 4 +- .../protocols/openVpnProtocol.cpp} | 23 +- .../protocols/openVpnProtocol.h} | 6 +- .../protocols/protocolUtils.cpp} | 117 +- client/core/protocols/protocolUtils.h | 49 + .../protocols/qmlRegisterProtocols.h} | 4 +- .../protocols/vpnProtocol.cpp} | 16 +- .../protocols/vpnProtocol.h} | 9 +- .../protocols/wireGuardProtocol.cpp} | 8 +- .../protocols/wireGuardProtocol.h} | 2 +- .../protocols/xrayProtocol.cpp} | 36 +- .../protocols/xrayProtocol.h} | 14 +- .../secureAppSettingsRepository.cpp | 453 +++ .../secureAppSettingsRepository.h | 121 + .../repositories/secureServersRepository.cpp | 248 ++ .../repositories/secureServersRepository.h | 64 + client/core/scripts_registry.cpp | 84 - client/core/scripts_registry.h | 44 - client/core/server_defs.cpp | 16 - client/core/server_defs.h | 15 - client/core/utils/api/apiEnums.h | 25 + client/core/{ => utils}/api/apiUtils.cpp | 27 +- client/core/{ => utils}/api/apiUtils.h | 8 +- client/core/utils/commonStructs.h | 63 + client/{ => core/utils}/constants.h | 0 client/core/utils/constants/apiConstants.h | 11 + .../apiDefs.h => utils/constants/apiKeys.h} | 75 +- client/core/utils/constants/configKeys.h | 127 + .../core/utils/constants/protocolConstants.h | 182 ++ client/core/utils/containerEnum.h | 36 + .../utils/containers/containerUtils.cpp} | 146 +- client/core/utils/containers/containerUtils.h | 56 + client/core/{defs.h => utils/errorCodes.h} | 44 +- .../errorStrings.cpp} | 8 +- .../{errorstrings.h => utils/errorStrings.h} | 2 +- .../installedAppsImageProvider.cpp | 0 .../{ => utils}/installedAppsImageProvider.h | 0 .../{ipcclient.cpp => utils/ipcClient.cpp} | 2 +- .../core/{ipcclient.h => utils/ipcClient.h} | 0 .../utils/managementServer.cpp} | 2 +- .../utils/managementServer.h} | 0 client/{ => core/utils}/migrations.cpp | 2 +- client/{ => core/utils}/migrations.h | 0 client/core/{ => utils}/networkUtilities.cpp | 6 - client/core/{ => utils}/networkUtilities.h | 1 - client/core/{ => utils}/osSignalHandler.cpp | 2 +- client/core/{ => utils}/osSignalHandler.h | 0 client/core/utils/protocolEnum.h | 50 + client/core/{ => utils}/qrCodeUtils.cpp | 0 client/core/{ => utils}/qrCodeUtils.h | 0 client/core/utils/routeModes.h | 38 + .../core/utils/selfhosted/scriptsRegistry.cpp | 292 ++ .../core/utils/selfhosted/scriptsRegistry.h | 68 + .../selfhosted/sshClient.cpp} | 2 +- .../selfhosted/sshClient.h} | 4 +- client/core/utils/selfhosted/sshSession.cpp | 243 ++ client/core/utils/selfhosted/sshSession.h | 54 + .../{ => utils}/serialization/inbound.cpp | 0 .../{ => utils}/serialization/outbound.cpp | 0 .../{ => utils}/serialization/serialization.h | 0 client/core/{ => utils}/serialization/ss.cpp | 2 +- client/core/{ => utils}/serialization/ssd.cpp | 2 +- .../core/{ => utils}/serialization/transfer.h | 0 .../core/{ => utils}/serialization/trojan.cpp | 0 .../core/{ => utils}/serialization/vless.cpp | 0 .../core/{ => utils}/serialization/vmess.cpp | 2 +- .../{ => utils}/serialization/vmess_new.cpp | 0 client/{ => core/utils}/utilities.cpp | 2 +- client/{ => core/utils}/utilities.h | 0 client/images/flagKit.qrc | 253 ++ client/images/images.qrc | 74 + client/main.cpp | 6 +- client/mozilla/localsocketcontroller.cpp | 129 +- .../platforms/android/android_controller.cpp | 4 +- client/platforms/android/android_controller.h | 2 +- .../ios/PacketTunnelProvider+OpenVPN.swift | 4 - client/platforms/ios/ios_controller.h | 3 +- client/platforms/ios/ios_controller.mm | 274 +- client/platforms/ios/iosnotificationhandler.h | 2 +- .../linux/daemon/linuxroutemonitor.cpp | 4 +- .../windows/daemon/windowsdaemon.cpp | 2 +- .../windows/daemon/windowsdaemontunnel.cpp | 2 +- client/protocols/openvpnovercloakprotocol.cpp | 117 - client/protocols/openvpnovercloakprotocol.h | 33 - client/protocols/protocols_defs.h | 334 --- client/protocols/shadowsocksvpnprotocol.cpp | 132 - client/protocols/shadowsocksvpnprotocol.h | 33 - client/resources.qrc | 516 ---- ...cure_qsettings.cpp => secureQSettings.cpp} | 18 +- .../{secure_qsettings.h => secureQSettings.h} | 5 +- .../server_scripts/openvpn_cloak/Dockerfile | 63 - .../openvpn_cloak/configure_container.sh | 77 - .../openvpn_cloak/run_container.sh | 27 - client/server_scripts/openvpn_cloak/start.sh | 34 - .../openvpn_cloak/template.ovpn | 38 - .../openvpn_shadowsocks/Dockerfile | 63 - .../configure_container.sh | 46 - .../openvpn_shadowsocks/run_container.sh | 28 - .../openvpn_shadowsocks/start.sh | 32 - .../openvpn_shadowsocks/template.ovpn | 39 - client/server_scripts/serverScripts.qrc | 58 + client/settings.cpp | 567 ---- client/settings.h | 264 -- client/tests/CMakeLists.txt | 155 + client/tests/testAdminSelfHostedExport.cpp | 147 + client/tests/testComplexOperations.cpp | 112 + client/tests/testDefaultServerChange.cpp | 125 + client/tests/testGatewayStacks.cpp | 75 + client/tests/testMultipleImports.cpp | 194 ++ client/tests/testSelfHostedServerSetup.cpp | 373 +++ client/tests/testServerEdgeCases.cpp | 106 + client/tests/testServerEdit.cpp | 112 + client/tests/testServersModelSync.cpp | 113 + client/tests/testSettingsSignals.cpp | 266 ++ client/tests/testSignalOrder.cpp | 87 + .../tests/testUiServersModelAndController.cpp | 294 ++ client/translations/amneziavpn_zh_CN.ts | 2563 +++++++++++------ client/ui/controllers/allowedDnsController.h | 35 - ...troller.cpp => allowedDnsUiController.cpp} | 42 +- .../ui/controllers/allowedDnsUiController.h | 37 + .../controllers/api/apiConfigsController.cpp | 1278 -------- .../ui/controllers/api/apiConfigsController.h | 82 - .../ui/controllers/api/apiNewsController.cpp | 69 - client/ui/controllers/api/apiNewsController.h | 34 - .../controllers/api/apiNewsUiController.cpp | 28 + .../ui/controllers/api/apiNewsUiController.h | 32 + .../controllers/api/apiSettingsController.cpp | 148 - .../controllers/api/apiSettingsController.h | 39 - .../api/servicesCatalogUiController.cpp | 68 + .../api/servicesCatalogUiController.h | 44 + .../api/subscriptionUiController.cpp | 483 ++++ .../api/subscriptionUiController.h | 103 + .../appSplitTunnelingController.cpp | 49 - .../controllers/appSplitTunnelingController.h | 31 - .../appSplitTunnelingUiController.cpp | 79 + .../appSplitTunnelingUiController.h | 48 + .../ui/controllers/connectionController.cpp | 182 -- client/ui/controllers/connectionController.h | 75 - .../ui/controllers/connectionUiController.cpp | 152 + .../ui/controllers/connectionUiController.h | 68 + client/ui/controllers/exportController.cpp | 392 --- client/ui/controllers/exportController.h | 71 - client/ui/controllers/importController.cpp | 773 ----- client/ui/controllers/importController.h | 94 - client/ui/controllers/importUiController.cpp | 203 ++ client/ui/controllers/importUiController.h | 68 + client/ui/controllers/installController.cpp | 1105 ------- client/ui/controllers/installController.h | 120 - .../ipSplitTunnelingUiController.cpp | 87 + .../ipSplitTunnelingUiController.h | 46 + .../ui/controllers/languageUiController.cpp | 125 + client/ui/controllers/languageUiController.h | 46 + .../controllers/{ => qml}/focusController.cpp | 2 +- .../controllers/{ => qml}/focusController.h | 2 +- .../{ => qml}/listViewFocusController.cpp | 2 +- .../{ => qml}/listViewFocusController.h | 0 .../controllers/{ => qml}/pageController.cpp | 108 +- .../ui/controllers/{ => qml}/pageController.h | 40 +- .../selfhosted/exportUiController.cpp | 116 + .../selfhosted/exportUiController.h | 59 + .../selfhosted/installUiController.cpp | 493 ++++ .../selfhosted/installUiController.h | 151 + client/ui/controllers/serversUiController.cpp | 491 ++++ client/ui/controllers/serversUiController.h | 115 + client/ui/controllers/settingsController.cpp | 535 ---- .../ui/controllers/settingsUiController.cpp | 351 +++ ...ngsController.h => settingsUiController.h} | 71 +- client/ui/controllers/sitesController.cpp | 134 - client/ui/controllers/sitesController.h | 36 - client/ui/controllers/systemController.cpp | 9 +- client/ui/controllers/systemController.h | 6 +- client/ui/models/allowedDnsModel.cpp | 39 + ...{allowed_dns_model.h => allowedDnsModel.h} | 12 +- client/ui/models/allowed_dns_model.cpp | 86 - client/ui/models/api/apiAccountInfoModel.cpp | 2 +- client/ui/models/api/apiAccountInfoModel.h | 4 +- client/ui/models/api/apiCountryModel.cpp | 4 +- client/ui/models/api/apiDevicesModel.cpp | 11 +- client/ui/models/api/apiDevicesModel.h | 9 +- client/ui/models/api/apiServicesModel.cpp | 2 +- client/ui/models/appSplitTunnelingModel.cpp | 66 +- client/ui/models/appSplitTunnelingModel.h | 32 +- client/ui/models/clientManagementModel.cpp | 901 +----- client/ui/models/clientManagementModel.h | 58 +- client/ui/models/containerProps.h | 29 + ...ntainers_model.cpp => containersModel.cpp} | 96 +- .../{containers_model.h => containersModel.h} | 28 +- client/ui/models/installedAppsModel.cpp | 17 +- client/ui/models/ipSplitTunnelingModel.cpp | 49 + client/ui/models/ipSplitTunnelingModel.h | 34 + client/ui/models/languageModel.cpp | 85 +- client/ui/models/languageModel.h | 23 +- client/ui/models/newsModel.cpp | 8 +- client/ui/models/newsModel.h | 6 +- client/ui/models/protocolProps.h | 17 + client/ui/models/protocols/awgConfigModel.cpp | 491 ++-- client/ui/models/protocols/awgConfigModel.h | 74 +- .../ui/models/protocols/cloakConfigModel.cpp | 78 - client/ui/models/protocols/cloakConfigModel.h | 40 - .../ui/models/protocols/ikev2ConfigModel.cpp | 47 +- client/ui/models/protocols/ikev2ConfigModel.h | 16 +- .../models/protocols/openvpnConfigModel.cpp | 160 +- .../ui/models/protocols/openvpnConfigModel.h | 19 +- .../protocols/shadowsocksConfigModel.cpp | 79 - .../models/protocols/shadowsocksConfigModel.h | 41 - .../models/protocols/wireguardConfigModel.cpp | 142 +- .../models/protocols/wireguardConfigModel.h | 33 +- .../ui/models/protocols/xrayConfigModel.cpp | 70 +- client/ui/models/protocols/xrayConfigModel.h | 18 +- client/ui/models/protocolsModel.cpp | 138 + .../{protocols_model.h => protocolsModel.h} | 30 +- client/ui/models/protocols_model.cpp | 115 - client/ui/models/serversModel.cpp | 425 +++ client/ui/models/serversModel.h | 112 + client/ui/models/servers_model.cpp | 1012 ------- client/ui/models/servers_model.h | 211 -- client/ui/models/services/sftpConfigModel.cpp | 65 +- client/ui/models/services/sftpConfigModel.h | 18 +- .../services/socks5ProxyConfigModel.cpp | 49 +- .../models/services/socks5ProxyConfigModel.h | 15 +- client/ui/models/services/torConfigModel.cpp | 77 + client/ui/models/services/torConfigModel.h | 41 + client/ui/models/sites_model.cpp | 145 - client/ui/models/sites_model.h | 59 - client/ui/qml/Components/AdLabel.qml | 6 +- client/ui/qml/Components/ConnectButton.qml | 2 +- client/ui/qml/Components/GamepadLoader.qml | 2 +- .../qml/Components/HomeContainersListView.qml | 6 +- .../Components/HomeSplitTunnelingDrawer.qml | 194 +- client/ui/qml/Components/QuestionDrawer.qml | 2 +- .../ui/qml/Components/RenameServerDrawer.qml | 2 +- .../qml/Components/SelectLanguageDrawer.qml | 4 +- client/ui/qml/Components/ServersListView.qml | 11 +- .../Components/SettingsContainersListView.qml | 35 +- client/ui/qml/Components/SmartScroll.qml | 8 +- .../Components/SubscriptionExpiredDrawer.qml | 2 +- .../qml/Components/SubscriptionPlanCard.qml | 3 +- client/ui/qml/Controls2/BaseHeaderType.qml | 4 +- client/ui/qml/Controls2/PopupType.qml | 4 +- .../qml/Controls2/TextTypes/BadgeTextType.qml | 2 +- .../Controls2/TextTypes/CaptionTextType.qml | 2 +- .../Controls2/TextTypes/Header1TextType.qml | 2 +- .../Controls2/TextTypes/Header2TextType.qml | 2 +- .../qml/Controls2/TextTypes/LabelTextType.qml | 2 +- .../Controls2/TextTypes/ListItemTitleType.qml | 2 +- .../Controls2/TextTypes/ParagraphTextType.qml | 2 +- .../qml/Controls2/TextTypes/SmallTextType.qml | 2 +- .../ui/qml/Controls2/TopCloseButtonType.qml | 2 +- .../ui/qml/Filters/ContainersModelFilters.qml | 10 +- client/ui/qml/Pages2/PageDeinstalling.qml | 2 +- client/ui/qml/Pages2/PageDevMenu.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 35 +- .../Pages2/PageProtocolAwgClientSettings.qml | 7 +- .../ui/qml/Pages2/PageProtocolAwgSettings.qml | 9 +- .../qml/Pages2/PageProtocolCloakSettings.qml | 218 -- .../Pages2/PageProtocolOpenVpnSettings.qml | 10 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 8 +- .../PageProtocolShadowSocksSettings.qml | 184 -- .../PageProtocolWireGuardClientSettings.qml | 7 +- .../Pages2/PageProtocolWireGuardSettings.qml | 11 +- .../qml/Pages2/PageProtocolXraySettings.qml | 10 +- .../ui/qml/Pages2/PageServiceDnsSettings.qml | 7 +- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 6 +- .../Pages2/PageServiceSocksProxySettings.qml | 5 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 10 +- client/ui/qml/Pages2/PageSettings.qml | 6 +- client/ui/qml/Pages2/PageSettingsAbout.qml | 6 +- .../PageSettingsApiAvailableCountries.qml | 8 +- .../ui/qml/Pages2/PageSettingsApiDevices.qml | 13 +- .../Pages2/PageSettingsApiInstructions.qml | 4 +- .../Pages2/PageSettingsApiNativeConfigs.qml | 10 +- .../qml/Pages2/PageSettingsApiServerInfo.qml | 34 +- .../Pages2/PageSettingsApiSubscriptionKey.qml | 18 +- .../ui/qml/Pages2/PageSettingsApiSupport.qml | 2 +- .../Pages2/PageSettingsAppSplitTunneling.qml | 23 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 8 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 6 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 2 +- client/ui/qml/Pages2/PageSettingsDns.qml | 333 +-- .../ui/qml/Pages2/PageSettingsKillSwitch.qml | 2 +- .../PageSettingsKillSwitchExceptions.qml | 3 +- client/ui/qml/Pages2/PageSettingsLogging.qml | 2 +- .../ui/qml/Pages2/PageSettingsNewsDetail.qml | 2 +- .../Pages2/PageSettingsNewsNotifications.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 29 +- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 4 +- .../qml/Pages2/PageSettingsServerProtocol.qml | 41 +- .../Pages2/PageSettingsServerProtocols.qml | 5 +- .../qml/Pages2/PageSettingsServerServices.qml | 5 +- .../ui/qml/Pages2/PageSettingsServersList.qml | 9 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 37 +- .../qml/Pages2/PageSetupWizardApiFreeInfo.qml | 10 +- .../Pages2/PageSetupWizardApiPremiumInfo.qml | 10 +- .../Pages2/PageSetupWizardApiServicesList.qml | 2 +- .../Pages2/PageSetupWizardApiTrialEmail.qml | 8 +- .../Pages2/PageSetupWizardConfigSource.qml | 13 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 6 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 11 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 14 +- .../PageSetupWizardProtocolSettings.qml | 14 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 9 +- .../ui/qml/Pages2/PageSetupWizardQrReader.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 4 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 2 +- .../qml/Pages2/PageSetupWizardViewConfig.qml | 11 +- client/ui/qml/Pages2/PageShare.qml | 94 +- client/ui/qml/Pages2/PageShareConnection.qml | 16 +- client/ui/qml/Pages2/PageShareFullAccess.qml | 336 +-- client/ui/qml/Pages2/PageStart.qml | 35 +- client/ui/qml/main2.qml | 8 +- client/ui/qml/qml.qrc | 135 + client/{ => ui}/utils/converter.h | 0 client/ui/{macos_util.h => utils/macosUtil.h} | 0 .../ui/{macos_util.mm => utils/macosUtil.mm} | 2 +- .../neNotificationHandler.h} | 8 +- .../notificationHandler.cpp} | 4 +- .../notificationHandler.h} | 2 +- client/ui/{ => utils}/pages.h | 0 .../{qautostart.cpp => utils/qAutoStart.cpp} | 2 +- .../ui/{qautostart.h => utils/qAutoStart.h} | 0 client/{ => ui}/utils/qmlUtils.cpp | 0 client/{ => ui}/utils/qmlUtils.h | 0 .../systemTrayNotificationHandler.cpp} | 2 +- .../systemTrayNotificationHandler.h} | 8 +- .../{vpnconnection.cpp => vpnConnection.cpp} | 177 +- client/{vpnconnection.h => vpnConnection.h} | 29 +- common/logger/logger.cpp | 12 +- ipc/ipc.h | 2 +- service/server/CMakeLists.txt | 12 +- service/server/killswitch.cpp | 31 +- service/server/killswitch.h | 2 +- service/server/main.cpp | 2 +- service/server/router_linux.cpp | 4 +- service/server/router_mac.cpp | 2 +- service/server/router_win.cpp | 2 +- service/server/xray.cpp | 2 +- 469 files changed, 25992 insertions(+), 17154 deletions(-) rename client/{amnezia_application.cpp => amneziaApplication.cpp} (93%) rename client/{amnezia_application.h => amneziaApplication.h} (91%) delete mode 100644 client/android/cloak/build.gradle.kts delete mode 100644 client/android/cloak/src/main/kotlin/Cloak.kt delete mode 100644 client/configurators/awg_configurator.cpp delete mode 100644 client/configurators/awg_configurator.h delete mode 100644 client/configurators/cloak_configurator.cpp delete mode 100644 client/configurators/cloak_configurator.h delete mode 100644 client/configurators/configurator_base.cpp delete mode 100644 client/configurators/configurator_base.h delete mode 100644 client/configurators/ikev2_configurator.h delete mode 100644 client/configurators/openvpn_configurator.h delete mode 100644 client/configurators/shadowsocks_configurator.cpp delete mode 100644 client/configurators/shadowsocks_configurator.h delete mode 100644 client/configurators/ssh_configurator.cpp delete mode 100644 client/configurators/ssh_configurator.h delete mode 100644 client/configurators/wireguard_configurator.h delete mode 100644 client/configurators/xray_configurator.h delete mode 100644 client/containers/containers_defs.h create mode 100644 client/core/configurators/awgConfigurator.cpp create mode 100644 client/core/configurators/awgConfigurator.h create mode 100644 client/core/configurators/configuratorBase.cpp create mode 100644 client/core/configurators/configuratorBase.h rename client/{configurators/ikev2_configurator.cpp => core/configurators/ikev2Configurator.cpp} (56%) create mode 100644 client/core/configurators/ikev2Configurator.h rename client/{configurators/openvpn_configurator.cpp => core/configurators/openVpnConfigurator.cpp} (59%) create mode 100644 client/core/configurators/openVpnConfigurator.h rename client/{configurators/wireguard_configurator.cpp => core/configurators/wireguardConfigurator.cpp} (50%) create mode 100644 client/core/configurators/wireguardConfigurator.h rename client/{configurators/xray_configurator.cpp => core/configurators/xrayConfigurator.cpp} (52%) create mode 100644 client/core/configurators/xrayConfigurator.h create mode 100644 client/core/controllers/allowedDnsController.cpp create mode 100644 client/core/controllers/allowedDnsController.h create mode 100644 client/core/controllers/api/newsController.cpp create mode 100644 client/core/controllers/api/newsController.h create mode 100644 client/core/controllers/api/servicesCatalogController.cpp create mode 100644 client/core/controllers/api/servicesCatalogController.h create mode 100644 client/core/controllers/api/subscriptionController.cpp create mode 100644 client/core/controllers/api/subscriptionController.h create mode 100644 client/core/controllers/appSplitTunnelingController.cpp create mode 100644 client/core/controllers/appSplitTunnelingController.h create mode 100644 client/core/controllers/connectionController.cpp create mode 100644 client/core/controllers/connectionController.h create mode 100644 client/core/controllers/coreSignalHandlers.cpp create mode 100644 client/core/controllers/coreSignalHandlers.h create mode 100644 client/core/controllers/ipSplitTunnelingController.cpp create mode 100644 client/core/controllers/ipSplitTunnelingController.h create mode 100644 client/core/controllers/selfhosted/exportController.cpp create mode 100644 client/core/controllers/selfhosted/exportController.h create mode 100644 client/core/controllers/selfhosted/importController.cpp create mode 100644 client/core/controllers/selfhosted/importController.h create mode 100644 client/core/controllers/selfhosted/installController.cpp create mode 100644 client/core/controllers/selfhosted/installController.h create mode 100644 client/core/controllers/selfhosted/usersController.cpp create mode 100644 client/core/controllers/selfhosted/usersController.h delete mode 100644 client/core/controllers/serverController.cpp delete mode 100644 client/core/controllers/serverController.h create mode 100644 client/core/controllers/serversController.cpp create mode 100644 client/core/controllers/serversController.h create mode 100644 client/core/controllers/settingsController.cpp create mode 100644 client/core/controllers/settingsController.h delete mode 100644 client/core/controllers/vpnConfigurationController.cpp delete mode 100644 client/core/controllers/vpnConfigurationController.h create mode 100644 client/core/installers/awgInstaller.cpp create mode 100644 client/core/installers/awgInstaller.h create mode 100644 client/core/installers/installerBase.cpp create mode 100644 client/core/installers/installerBase.h create mode 100644 client/core/installers/openvpnInstaller.cpp create mode 100644 client/core/installers/openvpnInstaller.h create mode 100644 client/core/installers/sftpInstaller.cpp create mode 100644 client/core/installers/sftpInstaller.h create mode 100644 client/core/installers/socks5Installer.cpp create mode 100644 client/core/installers/socks5Installer.h create mode 100644 client/core/installers/torInstaller.cpp create mode 100644 client/core/installers/torInstaller.h create mode 100644 client/core/installers/wireguardInstaller.cpp create mode 100644 client/core/installers/wireguardInstaller.h create mode 100644 client/core/installers/xrayInstaller.cpp create mode 100644 client/core/installers/xrayInstaller.h create mode 100644 client/core/models/api/apiConfig.cpp create mode 100644 client/core/models/api/apiConfig.h create mode 100644 client/core/models/api/apiV1ServerConfig.cpp create mode 100644 client/core/models/api/apiV1ServerConfig.h create mode 100644 client/core/models/api/apiV2ServerConfig.cpp create mode 100644 client/core/models/api/apiV2ServerConfig.h create mode 100644 client/core/models/api/authData.cpp create mode 100644 client/core/models/api/authData.h create mode 100644 client/core/models/containerConfig.cpp create mode 100644 client/core/models/containerConfig.h create mode 100644 client/core/models/protocolConfig.cpp create mode 100644 client/core/models/protocolConfig.h create mode 100644 client/core/models/protocols/awgProtocolConfig.cpp create mode 100644 client/core/models/protocols/awgProtocolConfig.h create mode 100644 client/core/models/protocols/dnsProtocolConfig.cpp create mode 100644 client/core/models/protocols/dnsProtocolConfig.h create mode 100644 client/core/models/protocols/ikev2ProtocolConfig.cpp create mode 100644 client/core/models/protocols/ikev2ProtocolConfig.h create mode 100644 client/core/models/protocols/openVpnProtocolConfig.cpp create mode 100644 client/core/models/protocols/openVpnProtocolConfig.h create mode 100644 client/core/models/protocols/sftpProtocolConfig.cpp create mode 100644 client/core/models/protocols/sftpProtocolConfig.h create mode 100644 client/core/models/protocols/socks5ProxyProtocolConfig.cpp create mode 100644 client/core/models/protocols/socks5ProxyProtocolConfig.h create mode 100644 client/core/models/protocols/torProtocolConfig.cpp create mode 100644 client/core/models/protocols/torProtocolConfig.h create mode 100644 client/core/models/protocols/wireGuardProtocolConfig.cpp create mode 100644 client/core/models/protocols/wireGuardProtocolConfig.h create mode 100644 client/core/models/protocols/xrayProtocolConfig.cpp create mode 100644 client/core/models/protocols/xrayProtocolConfig.h create mode 100644 client/core/models/selfhosted/nativeServerConfig.cpp create mode 100644 client/core/models/selfhosted/nativeServerConfig.h create mode 100644 client/core/models/selfhosted/selfHostedServerConfig.cpp create mode 100644 client/core/models/selfhosted/selfHostedServerConfig.h create mode 100644 client/core/models/serverConfig.cpp create mode 100644 client/core/models/serverConfig.h rename client/{protocols/android_vpnprotocol.cpp => core/protocols/androidVpnProtocol.cpp} (94%) rename client/{protocols/android_vpnprotocol.h => core/protocols/androidVpnProtocol.h} (94%) rename client/{protocols/awgprotocol.cpp => core/protocols/awgProtocol.cpp} (83%) rename client/{protocols/awgprotocol.h => core/protocols/awgProtocol.h} (89%) rename client/{protocols/ikev2_vpn_protocol_windows.cpp => core/protocols/ikev2VpnProtocolWindows.cpp} (96%) rename client/{protocols/ikev2_vpn_protocol_windows.h => core/protocols/ikev2VpnProtocolWindows.h} (96%) rename client/{protocols/openvpnprotocol.cpp => core/protocols/openVpnProtocol.cpp} (94%) rename client/{protocols/openvpnprotocol.h => core/protocols/openVpnProtocol.h} (93%) rename client/{protocols/protocols_defs.cpp => core/protocols/protocolUtils.cpp} (66%) create mode 100644 client/core/protocols/protocolUtils.h rename client/{protocols/qml_register_protocols.h => core/protocols/qmlRegisterProtocols.h} (90%) rename client/{protocols/vpnprotocol.cpp => core/protocols/vpnProtocol.cpp} (90%) rename client/{protocols/vpnprotocol.h => core/protocols/vpnProtocol.h} (90%) rename client/{protocols/wireguardprotocol.cpp => core/protocols/wireGuardProtocol.cpp} (88%) rename client/{protocols/wireguardprotocol.h => core/protocols/wireGuardProtocol.h} (96%) rename client/{protocols/xrayprotocol.cpp => core/protocols/xrayProtocol.cpp} (90%) rename client/{protocols/xrayprotocol.h => core/protocols/xrayProtocol.h} (73%) create mode 100644 client/core/repositories/secureAppSettingsRepository.cpp create mode 100644 client/core/repositories/secureAppSettingsRepository.h create mode 100644 client/core/repositories/secureServersRepository.cpp create mode 100644 client/core/repositories/secureServersRepository.h delete mode 100644 client/core/scripts_registry.cpp delete mode 100644 client/core/scripts_registry.h delete mode 100644 client/core/server_defs.cpp delete mode 100644 client/core/server_defs.h create mode 100644 client/core/utils/api/apiEnums.h rename client/core/{ => utils}/api/apiUtils.cpp (90%) rename client/core/{ => utils}/api/apiUtils.h (81%) create mode 100644 client/core/utils/commonStructs.h rename client/{ => core/utils}/constants.h (100%) create mode 100644 client/core/utils/constants/apiConstants.h rename client/core/{api/apiDefs.h => utils/constants/apiKeys.h} (76%) create mode 100644 client/core/utils/constants/configKeys.h create mode 100644 client/core/utils/constants/protocolConstants.h create mode 100644 client/core/utils/containerEnum.h rename client/{containers/containers_defs.cpp => core/utils/containers/containerUtils.cpp} (68%) create mode 100644 client/core/utils/containers/containerUtils.h rename client/core/{defs.h => utils/errorCodes.h} (78%) rename client/core/{errorstrings.cpp => utils/errorStrings.cpp} (94%) rename client/core/{errorstrings.h => utils/errorStrings.h} (85%) rename client/core/{ => utils}/installedAppsImageProvider.cpp (100%) rename client/core/{ => utils}/installedAppsImageProvider.h (100%) rename client/core/{ipcclient.cpp => utils/ipcClient.cpp} (99%) rename client/core/{ipcclient.h => utils/ipcClient.h} (100%) rename client/{managementserver.cpp => core/utils/managementServer.cpp} (98%) rename client/{managementserver.h => core/utils/managementServer.h} (100%) rename client/{ => core/utils}/migrations.cpp (98%) rename client/{ => core/utils}/migrations.h (100%) rename client/core/{ => utils}/networkUtilities.cpp (98%) rename client/core/{ => utils}/networkUtilities.h (95%) rename client/core/{ => utils}/osSignalHandler.cpp (99%) rename client/core/{ => utils}/osSignalHandler.h (100%) create mode 100644 client/core/utils/protocolEnum.h rename client/core/{ => utils}/qrCodeUtils.cpp (100%) rename client/core/{ => utils}/qrCodeUtils.h (100%) create mode 100644 client/core/utils/routeModes.h create mode 100644 client/core/utils/selfhosted/scriptsRegistry.cpp create mode 100644 client/core/utils/selfhosted/scriptsRegistry.h rename client/core/{sshclient.cpp => utils/selfhosted/sshClient.cpp} (99%) rename client/core/{sshclient.h => utils/selfhosted/sshClient.h} (94%) create mode 100644 client/core/utils/selfhosted/sshSession.cpp create mode 100644 client/core/utils/selfhosted/sshSession.h rename client/core/{ => utils}/serialization/inbound.cpp (100%) rename client/core/{ => utils}/serialization/outbound.cpp (100%) rename client/core/{ => utils}/serialization/serialization.h (100%) rename client/core/{ => utils}/serialization/ss.cpp (99%) rename client/core/{ => utils}/serialization/ssd.cpp (99%) rename client/core/{ => utils}/serialization/transfer.h (100%) rename client/core/{ => utils}/serialization/trojan.cpp (100%) rename client/core/{ => utils}/serialization/vless.cpp (100%) rename client/core/{ => utils}/serialization/vmess.cpp (99%) rename client/core/{ => utils}/serialization/vmess_new.cpp (100%) rename client/{ => core/utils}/utilities.cpp (99%) rename client/{ => core/utils}/utilities.h (100%) create mode 100644 client/images/flagKit.qrc create mode 100644 client/images/images.qrc delete mode 100644 client/protocols/openvpnovercloakprotocol.cpp delete mode 100644 client/protocols/openvpnovercloakprotocol.h delete mode 100644 client/protocols/protocols_defs.h delete mode 100644 client/protocols/shadowsocksvpnprotocol.cpp delete mode 100644 client/protocols/shadowsocksvpnprotocol.h delete mode 100644 client/resources.qrc rename client/{secure_qsettings.cpp => secureQSettings.cpp} (92%) rename client/{secure_qsettings.h => secureQSettings.h} (87%) delete mode 100644 client/server_scripts/openvpn_cloak/Dockerfile delete mode 100644 client/server_scripts/openvpn_cloak/configure_container.sh delete mode 100644 client/server_scripts/openvpn_cloak/run_container.sh delete mode 100644 client/server_scripts/openvpn_cloak/start.sh delete mode 100644 client/server_scripts/openvpn_cloak/template.ovpn delete mode 100644 client/server_scripts/openvpn_shadowsocks/Dockerfile delete mode 100644 client/server_scripts/openvpn_shadowsocks/configure_container.sh delete mode 100644 client/server_scripts/openvpn_shadowsocks/run_container.sh delete mode 100644 client/server_scripts/openvpn_shadowsocks/start.sh delete mode 100644 client/server_scripts/openvpn_shadowsocks/template.ovpn create mode 100644 client/server_scripts/serverScripts.qrc delete mode 100644 client/settings.cpp delete mode 100644 client/settings.h create mode 100644 client/tests/CMakeLists.txt create mode 100644 client/tests/testAdminSelfHostedExport.cpp create mode 100644 client/tests/testComplexOperations.cpp create mode 100644 client/tests/testDefaultServerChange.cpp create mode 100644 client/tests/testGatewayStacks.cpp create mode 100644 client/tests/testMultipleImports.cpp create mode 100644 client/tests/testSelfHostedServerSetup.cpp create mode 100644 client/tests/testServerEdgeCases.cpp create mode 100644 client/tests/testServerEdit.cpp create mode 100644 client/tests/testServersModelSync.cpp create mode 100644 client/tests/testSettingsSignals.cpp create mode 100644 client/tests/testSignalOrder.cpp create mode 100644 client/tests/testUiServersModelAndController.cpp delete mode 100644 client/ui/controllers/allowedDnsController.h rename client/ui/controllers/{allowedDnsController.cpp => allowedDnsUiController.cpp} (61%) create mode 100644 client/ui/controllers/allowedDnsUiController.h delete mode 100644 client/ui/controllers/api/apiConfigsController.cpp delete mode 100644 client/ui/controllers/api/apiConfigsController.h delete mode 100644 client/ui/controllers/api/apiNewsController.cpp delete mode 100644 client/ui/controllers/api/apiNewsController.h create mode 100644 client/ui/controllers/api/apiNewsUiController.cpp create mode 100644 client/ui/controllers/api/apiNewsUiController.h delete mode 100644 client/ui/controllers/api/apiSettingsController.cpp delete mode 100644 client/ui/controllers/api/apiSettingsController.h create mode 100644 client/ui/controllers/api/servicesCatalogUiController.cpp create mode 100644 client/ui/controllers/api/servicesCatalogUiController.h create mode 100644 client/ui/controllers/api/subscriptionUiController.cpp create mode 100644 client/ui/controllers/api/subscriptionUiController.h delete mode 100644 client/ui/controllers/appSplitTunnelingController.cpp delete mode 100644 client/ui/controllers/appSplitTunnelingController.h create mode 100644 client/ui/controllers/appSplitTunnelingUiController.cpp create mode 100644 client/ui/controllers/appSplitTunnelingUiController.h delete mode 100644 client/ui/controllers/connectionController.cpp delete mode 100644 client/ui/controllers/connectionController.h create mode 100644 client/ui/controllers/connectionUiController.cpp create mode 100644 client/ui/controllers/connectionUiController.h delete mode 100644 client/ui/controllers/exportController.cpp delete mode 100644 client/ui/controllers/exportController.h delete mode 100644 client/ui/controllers/importController.cpp delete mode 100644 client/ui/controllers/importController.h create mode 100644 client/ui/controllers/importUiController.cpp create mode 100644 client/ui/controllers/importUiController.h delete mode 100644 client/ui/controllers/installController.cpp delete mode 100644 client/ui/controllers/installController.h create mode 100644 client/ui/controllers/ipSplitTunnelingUiController.cpp create mode 100644 client/ui/controllers/ipSplitTunnelingUiController.h create mode 100644 client/ui/controllers/languageUiController.cpp create mode 100644 client/ui/controllers/languageUiController.h rename client/ui/controllers/{ => qml}/focusController.cpp (99%) rename client/ui/controllers/{ => qml}/focusController.h (96%) rename client/ui/controllers/{ => qml}/listViewFocusController.cpp (99%) rename client/ui/controllers/{ => qml}/listViewFocusController.h (100%) rename client/ui/controllers/{ => qml}/pageController.cpp (58%) rename client/ui/controllers/{ => qml}/pageController.h (77%) create mode 100644 client/ui/controllers/selfhosted/exportUiController.cpp create mode 100644 client/ui/controllers/selfhosted/exportUiController.h create mode 100755 client/ui/controllers/selfhosted/installUiController.cpp create mode 100644 client/ui/controllers/selfhosted/installUiController.h create mode 100644 client/ui/controllers/serversUiController.cpp create mode 100644 client/ui/controllers/serversUiController.h delete mode 100644 client/ui/controllers/settingsController.cpp create mode 100644 client/ui/controllers/settingsUiController.cpp rename client/ui/controllers/{settingsController.h => settingsUiController.h} (64%) delete mode 100644 client/ui/controllers/sitesController.cpp delete mode 100644 client/ui/controllers/sitesController.h create mode 100644 client/ui/models/allowedDnsModel.cpp rename client/ui/models/{allowed_dns_model.h => allowedDnsModel.h} (58%) delete mode 100644 client/ui/models/allowed_dns_model.cpp create mode 100644 client/ui/models/containerProps.h rename client/ui/models/{containers_model.cpp => containersModel.cpp} (51%) rename client/ui/models/{containers_model.h => containersModel.h} (65%) create mode 100644 client/ui/models/ipSplitTunnelingModel.cpp create mode 100644 client/ui/models/ipSplitTunnelingModel.h create mode 100644 client/ui/models/protocolProps.h delete mode 100644 client/ui/models/protocols/cloakConfigModel.cpp delete mode 100644 client/ui/models/protocols/cloakConfigModel.h delete mode 100644 client/ui/models/protocols/shadowsocksConfigModel.cpp delete mode 100644 client/ui/models/protocols/shadowsocksConfigModel.h create mode 100644 client/ui/models/protocolsModel.cpp rename client/ui/models/{protocols_model.h => protocolsModel.h} (54%) delete mode 100644 client/ui/models/protocols_model.cpp create mode 100644 client/ui/models/serversModel.cpp create mode 100644 client/ui/models/serversModel.h delete mode 100644 client/ui/models/servers_model.cpp delete mode 100644 client/ui/models/servers_model.h create mode 100644 client/ui/models/services/torConfigModel.cpp create mode 100644 client/ui/models/services/torConfigModel.h delete mode 100644 client/ui/models/sites_model.cpp delete mode 100644 client/ui/models/sites_model.h delete mode 100644 client/ui/qml/Pages2/PageProtocolCloakSettings.qml delete mode 100644 client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml create mode 100644 client/ui/qml/qml.qrc rename client/{ => ui}/utils/converter.h (100%) rename client/ui/{macos_util.h => utils/macosUtil.h} (100%) rename client/ui/{macos_util.mm => utils/macosUtil.mm} (99%) rename client/ui/{ne_notificationhandler.h => utils/neNotificationHandler.h} (84%) rename client/ui/{notificationhandler.cpp => utils/notificationHandler.cpp} (97%) rename client/ui/{notificationhandler.h => utils/notificationHandler.h} (97%) rename client/ui/{ => utils}/pages.h (100%) rename client/ui/{qautostart.cpp => utils/qAutoStart.cpp} (99%) rename client/ui/{qautostart.h => utils/qAutoStart.h} (100%) rename client/{ => ui}/utils/qmlUtils.cpp (100%) rename client/{ => ui}/utils/qmlUtils.h (100%) rename client/ui/{systemtray_notificationhandler.cpp => utils/systemTrayNotificationHandler.cpp} (99%) rename client/ui/{systemtray_notificationhandler.h => utils/systemTrayNotificationHandler.h} (90%) rename client/{vpnconnection.cpp => vpnConnection.cpp} (70%) rename client/{vpnconnection.h => vpnConnection.h} (65%) diff --git a/.clang-format-ignore b/.clang-format-ignore index 4019357f0..e06fd08c8 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -2,7 +2,7 @@ /client/3rd-prebuild /client/android /client/cmake -/client/core/serialization +/client/core/utils/serialization /client/daemon /client/fonts /client/images diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 1a7e91433..42e9ad566 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -62,7 +62,12 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID)) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep) endif() -qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) +qt6_add_resources(QRC ${QRC} + ${CMAKE_CURRENT_LIST_DIR}/images/images.qrc + ${CMAKE_CURRENT_LIST_DIR}/images/flagKit.qrc + ${CMAKE_CURRENT_LIST_DIR}/ui/qml/qml.qrc + ${CMAKE_CURRENT_LIST_DIR}/server_scripts/serverScripts.qrc +) # -- i18n begin set(CMAKE_AUTORCC ON) @@ -228,6 +233,12 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE) ) 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_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC}) # Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this). diff --git a/client/amnezia_application.cpp b/client/amneziaApplication.cpp similarity index 93% rename from client/amnezia_application.cpp rename to client/amneziaApplication.cpp index c6c8d672e..008cc345d 100644 --- a/client/amnezia_application.cpp +++ b/client/amneziaApplication.cpp @@ -1,4 +1,4 @@ -#include "amnezia_application.h" +#include "amneziaApplication.h" #include #include @@ -15,17 +15,17 @@ #include #include #include +#include +#include +#include "core/protocols/qmlRegisterProtocols.h" #include "logger.h" -#include "ui/controllers/pageController.h" +#include "ui/controllers/qml/pageController.h" #include "ui/models/installedAppsModel.h" #include "version.h" #include "platforms/ios/QRCodeReaderBase.h" - -#include "protocols/qml_register_protocols.h" -#include // for QQuickWindow -#include // for qobject_cast + bool AmneziaApplication::m_forceQuit = false; @@ -54,7 +54,7 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner); #endif - m_settings = std::shared_ptr(new Settings); + m_settings = new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, this); m_nam = new QNetworkAccessManager(this); } @@ -132,7 +132,7 @@ void AmneziaApplication::init() m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false); #endif - m_vpnConnection.reset(new VpnConnection(m_settings)); + m_vpnConnection.reset(new VpnConnection(nullptr, nullptr)); m_vpnConnection->moveToThread(&m_vpnConnectionThread); m_vpnConnectionThread.start(); @@ -153,16 +153,6 @@ void AmneziaApplication::init() m_coreController->setQmlRoot(); - bool enabled = m_settings->isSaveLogs(); -#ifndef Q_OS_ANDROID - if (enabled) { - if (!Logger::init(false)) { - qWarning() << "Initialization of debug subsystem failed"; - } - } -#endif - Logger::setServiceLogsEnabled(enabled); - #ifdef Q_OS_WIN //TODO if (m_parser.isSet(m_optAutostart)) m_coreController->pageController()->showOnStartup(); @@ -207,13 +197,11 @@ void AmneziaApplication::registerTypes() qRegisterMetaType("ServerCredentials"); qRegisterMetaType("DockerContainer"); + using namespace amnezia::ProtocolEnumNS; qRegisterMetaType("TransportProto"); qRegisterMetaType("Proto"); qRegisterMetaType("ServiceType"); - declareQmlProtocolEnum(); - declareQmlContainerEnum(); - qmlRegisterType("QRCodeReader", 1, 0, "QRCodeReader"); m_containerProps.reset(new ContainerProps()); @@ -227,6 +215,7 @@ void AmneziaApplication::registerTypes() qmlRegisterType("InstalledAppsModel", 1, 0, "InstalledAppsModel"); + amnezia::declareQmlProtocolEnum(); Vpn::declareQmlVpnConnectionStateEnum(); PageLoader::declareQmlPageEnum(); } diff --git a/client/amnezia_application.h b/client/amneziaApplication.h similarity index 91% rename from client/amnezia_application.h rename to client/amneziaApplication.h index c2f790354..33b262c7f 100644 --- a/client/amnezia_application.h +++ b/client/amneziaApplication.h @@ -14,8 +14,10 @@ #include #include "core/controllers/coreController.h" -#include "settings.h" -#include "vpnconnection.h" +#include "secureQSettings.h" +#include "vpnConnection.h" +#include "ui/models/containerProps.h" +#include "ui/models/protocolProps.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -51,7 +53,7 @@ public slots: private: static bool m_forceQuit; QQmlApplicationEngine *m_engine {}; - std::shared_ptr m_settings; + SecureQSettings* m_settings; QScopedPointer m_coreController; diff --git a/client/android/build.gradle.kts b/client/android/build.gradle.kts index 3c742621c..ff2d235eb 100644 --- a/client/android/build.gradle.kts +++ b/client/android/build.gradle.kts @@ -111,7 +111,6 @@ dependencies { implementation(project(":wireguard")) implementation(project(":awg")) implementation(project(":openvpn")) - implementation(project(":cloak")) implementation(project(":xray")) implementation(libs.androidx.core) implementation(libs.androidx.activity) diff --git a/client/android/cloak/build.gradle.kts b/client/android/cloak/build.gradle.kts deleted file mode 100644 index 99f1264aa..000000000 --- a/client/android/cloak/build.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -plugins { - id(libs.plugins.android.library.get().pluginId) - id(libs.plugins.kotlin.android.get().pluginId) -} - -kotlin { - jvmToolchain(17) -} - -android { - namespace = "org.amnezia.vpn.protocol.cloak" -} - -dependencies { - compileOnly(project(":utils")) - compileOnly(project(":protocolApi")) - implementation(project(":openvpn")) -} diff --git a/client/android/cloak/src/main/kotlin/Cloak.kt b/client/android/cloak/src/main/kotlin/Cloak.kt deleted file mode 100644 index d408fb197..000000000 --- a/client/android/cloak/src/main/kotlin/Cloak.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.amnezia.vpn.protocol.cloak - -import android.util.Base64 -import net.openvpn.ovpn3.ClientAPI_Config -import org.amnezia.vpn.protocol.openvpn.OpenVpn -import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary -import org.json.JSONObject - -class Cloak : OpenVpn() { - - override fun internalInit() { - super.internalInit() - if (!isInitialized) loadSharedLibrary(context, "ck-ovpn-plugin") - } - - override fun parseConfig(config: JSONObject): ClientAPI_Config { - val openVpnConfig = ClientAPI_Config() - - val openVpnConfigStr = config.getJSONObject("openvpn_config_data").getString("config") - val cloakConfigJson = checkCloakJson(config.getJSONObject("cloak_config_data")) - val cloakConfigStr = Base64.encodeToString(cloakConfigJson.toString().toByteArray(), Base64.DEFAULT) - - val configStr = "$openVpnConfigStr\n\n$cloakConfigStr\n\n" - - openVpnConfig.usePluggableTransports = true - openVpnConfig.content = configStr - return openVpnConfig - } - - private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject { - cloakConfigJson.put("NumConn", 1) - cloakConfigJson.put("ProxyMethod", "openvpn") - if (cloakConfigJson.has("port")) { - val port = cloakConfigJson["port"] - cloakConfigJson.remove("port") - cloakConfigJson.put("RemotePort", port) - } - if (cloakConfigJson.has("remote")) { - val remote = cloakConfigJson["remote"] - cloakConfigJson.remove("remote") - cloakConfigJson.put("RemoteHost", remote) - } - return cloakConfigJson - } -} diff --git a/client/android/settings.gradle.kts b/client/android/settings.gradle.kts index 68426ec84..1a146caae 100644 --- a/client/android/settings.gradle.kts +++ b/client/android/settings.gradle.kts @@ -35,7 +35,6 @@ include(":protocolApi") include(":wireguard") include(":awg") include(":openvpn") -include(":cloak") include(":xray") include(":xray:libXray") diff --git a/client/android/src/org/amnezia/vpn/VpnProto.kt b/client/android/src/org/amnezia/vpn/VpnProto.kt index 658e06acd..e1993ad01 100644 --- a/client/android/src/org/amnezia/vpn/VpnProto.kt +++ b/client/android/src/org/amnezia/vpn/VpnProto.kt @@ -2,7 +2,6 @@ package org.amnezia.vpn import org.amnezia.vpn.protocol.Protocol import org.amnezia.vpn.protocol.awg.Awg -import org.amnezia.vpn.protocol.cloak.Cloak import org.amnezia.vpn.protocol.openvpn.OpenVpn import org.amnezia.vpn.protocol.wireguard.Wireguard import org.amnezia.vpn.protocol.xray.Xray @@ -36,14 +35,6 @@ enum class VpnProto( override fun createProtocol(): Protocol = OpenVpn() }, - CLOAK( - "Cloak", - "org.amnezia.vpn:amneziaOpenVpnService", - OpenVpnService::class.java - ) { - override fun createProtocol(): Protocol = Cloak() - }, - XRAY( "XRay", "org.amnezia.vpn:amneziaXrayService", @@ -72,4 +63,4 @@ enum class VpnProto( companion object { fun get(protocolName: String): VpnProto = VpnProto.valueOf(protocolName.uppercase()) } -} \ No newline at end of file +} diff --git a/client/android/xray/src/main/kotlin/Xray.kt b/client/android/xray/src/main/kotlin/Xray.kt index e2e73fabf..e4da9c632 100644 --- a/client/android/xray/src/main/kotlin/Xray.kt +++ b/client/android/xray/src/main/kotlin/Xray.kt @@ -77,13 +77,13 @@ class Xray : Protocol() { return } - val xrayJsonConfig = config.optJSONObject("xray_config_data") + val xrayConfigData = config.optJSONObject("xray_config_data") ?: config.optJSONObject("ssxray_config_data") ?: throw BadConfigException("config_data not found") + val xrayJsonConfig = JSONObject(xrayConfigData.optString("config")) // Inject SOCKS5 auth before starting xray. Re-uses existing credentials if present. ensureInboundAuth(xrayJsonConfig) - val xrayConfig = parseConfig(config, xrayJsonConfig) (xrayJsonConfig.optJSONObject("log") ?: JSONObject().also { xrayJsonConfig.put("log", it) }) diff --git a/client/cmake/android.cmake b/client/cmake/android.cmake index af9a405a6..c0559cc1b 100644 --- a/client/cmake/android.cmake +++ b/client/cmake/android.cmake @@ -31,15 +31,15 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android) set(HEADERS ${HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h - ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h - ${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h + ${CMAKE_CURRENT_SOURCE_DIR}/core/protocols/androidVpnProtocol.h + ${CMAKE_CURRENT_SOURCE_DIR}/core/utils/installedAppsImageProvider.h ) set(SOURCES ${SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/core/protocols/androidVpnProtocol.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/core/utils/installedAppsImageProvider.cpp ) foreach(abi IN ITEMS ${QT_ANDROID_ABIS}) diff --git a/client/cmake/macos.cmake b/client/cmake/macos.cmake index 09c23b2d5..42bc16c8b 100644 --- a/client/cmake/macos.cmake +++ b/client/cmake/macos.cmake @@ -28,11 +28,11 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15) set(HEADERS ${HEADERS} - ${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.h + ${CMAKE_CURRENT_SOURCE_DIR}/ui/utils/macosUtil.h ) set(SOURCES ${SOURCES} - ${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm + ${CMAKE_CURRENT_SOURCE_DIR}/ui/utils/macosUtil.mm ) diff --git a/client/cmake/sources.cmake b/client/cmake/sources.cmake index cc6532894..f348ec8cd 100644 --- a/client/cmake/sources.cmake +++ b/client/cmake/sources.cmake @@ -1,34 +1,68 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..) set(HEADERS ${HEADERS} - ${CLIENT_ROOT_DIR}/migrations.h + ${CLIENT_ROOT_DIR}/core/utils/migrations.h ${CLIENT_ROOT_DIR}/../ipc/ipc.h - ${CLIENT_ROOT_DIR}/amnezia_application.h - ${CLIENT_ROOT_DIR}/containers/containers_defs.h - ${CLIENT_ROOT_DIR}/core/defs.h - ${CLIENT_ROOT_DIR}/core/errorstrings.h - ${CLIENT_ROOT_DIR}/core/scripts_registry.h - ${CLIENT_ROOT_DIR}/core/server_defs.h - ${CLIENT_ROOT_DIR}/core/api/apiDefs.h - ${CLIENT_ROOT_DIR}/core/qrCodeUtils.h + ${CLIENT_ROOT_DIR}/amneziaApplication.h + ${CLIENT_ROOT_DIR}/core/utils/errorCodes.h + ${CLIENT_ROOT_DIR}/core/utils/routeModes.h + ${CLIENT_ROOT_DIR}/core/utils/commonStructs.h + ${CLIENT_ROOT_DIR}/core/utils/containerEnum.h + ${CLIENT_ROOT_DIR}/core/utils/protocolEnum.h + ${CLIENT_ROOT_DIR}/core/utils/containers/containerUtils.h + ${CLIENT_ROOT_DIR}/core/protocols/protocolUtils.h + ${CLIENT_ROOT_DIR}/core/utils/constants/configKeys.h + ${CLIENT_ROOT_DIR}/core/utils/constants/protocolConstants.h + ${CLIENT_ROOT_DIR}/core/utils/constants/apiKeys.h + ${CLIENT_ROOT_DIR}/core/utils/constants/apiConstants.h + ${CLIENT_ROOT_DIR}/core/utils/api/apiEnums.h + ${CLIENT_ROOT_DIR}/core/utils/errorStrings.h + ${CLIENT_ROOT_DIR}/core/utils/selfhosted/scriptsRegistry.h + ${CLIENT_ROOT_DIR}/core/utils/qrCodeUtils.h ${CLIENT_ROOT_DIR}/core/controllers/coreController.h + ${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.h ${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h - ${CLIENT_ROOT_DIR}/core/controllers/serverController.h - ${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.h - ${CLIENT_ROOT_DIR}/protocols/protocols_defs.h - ${CLIENT_ROOT_DIR}/protocols/qml_register_protocols.h - ${CLIENT_ROOT_DIR}/ui/pages.h - ${CLIENT_ROOT_DIR}/ui/qautostart.h - ${CLIENT_ROOT_DIR}/protocols/vpnprotocol.h + ${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.h + ${CLIENT_ROOT_DIR}/core/controllers/serversController.h + ${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.h + ${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.h + ${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.h + ${CLIENT_ROOT_DIR}/core/controllers/selfhosted/importController.h + ${CLIENT_ROOT_DIR}/core/installers/installerBase.h + ${CLIENT_ROOT_DIR}/core/installers/awgInstaller.h + ${CLIENT_ROOT_DIR}/core/installers/wireguardInstaller.h + ${CLIENT_ROOT_DIR}/core/installers/openvpnInstaller.h + ${CLIENT_ROOT_DIR}/core/installers/xrayInstaller.h + ${CLIENT_ROOT_DIR}/core/installers/torInstaller.h + ${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.h + ${CLIENT_ROOT_DIR}/core/installers/socks5Installer.h + ${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.h + ${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.h + ${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.h + ${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.h + ${CLIENT_ROOT_DIR}/core/controllers/connectionController.h + ${CLIENT_ROOT_DIR}/core/controllers/settingsController.h + ${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.h + ${CLIENT_ROOT_DIR}/core/controllers/api/subscriptionController.h + ${CLIENT_ROOT_DIR}/core/controllers/api/newsController.h + ${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.h + ${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.h + ${CLIENT_ROOT_DIR}/core/protocols/qmlRegisterProtocols.h + ${CLIENT_ROOT_DIR}/ui/utils/pages.h + ${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.h + ${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.h ${CMAKE_CURRENT_BINARY_DIR}/version.h - ${CLIENT_ROOT_DIR}/core/sshclient.h - ${CLIENT_ROOT_DIR}/core/networkUtilities.h - ${CLIENT_ROOT_DIR}/core/serialization/serialization.h - ${CLIENT_ROOT_DIR}/core/serialization/transfer.h + ${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.h + ${CLIENT_ROOT_DIR}/core/utils/networkUtilities.h + ${CLIENT_ROOT_DIR}/core/utils/serialization/serialization.h + ${CLIENT_ROOT_DIR}/core/utils/serialization/transfer.h ${CLIENT_ROOT_DIR}/../common/logger/logger.h - ${CLIENT_ROOT_DIR}/utils/qmlUtils.h - ${CLIENT_ROOT_DIR}/core/api/apiUtils.h - ${CLIENT_ROOT_DIR}/core/osSignalHandler.h + ${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.h + ${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h + ${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.h + ${CLIENT_ROOT_DIR}/core/utils/utilities.h + ${CLIENT_ROOT_DIR}/core/utils/managementServer.h + ${CLIENT_ROOT_DIR}/core/utils/constants.h ) # Mozilla headres @@ -47,39 +81,64 @@ endif() if(NOT ANDROID) set(HEADERS ${HEADERS} - ${CLIENT_ROOT_DIR}/ui/notificationhandler.h + ${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.h ) endif() set(SOURCES ${SOURCES} - ${CLIENT_ROOT_DIR}/migrations.cpp - ${CLIENT_ROOT_DIR}/amnezia_application.cpp - ${CLIENT_ROOT_DIR}/containers/containers_defs.cpp - ${CLIENT_ROOT_DIR}/core/errorstrings.cpp - ${CLIENT_ROOT_DIR}/core/scripts_registry.cpp - ${CLIENT_ROOT_DIR}/core/server_defs.cpp - ${CLIENT_ROOT_DIR}/core/qrCodeUtils.cpp + ${CLIENT_ROOT_DIR}/core/utils/migrations.cpp + ${CLIENT_ROOT_DIR}/amneziaApplication.cpp + ${CLIENT_ROOT_DIR}/core/utils/errorStrings.cpp + ${CLIENT_ROOT_DIR}/core/utils/containers/containerUtils.cpp + ${CLIENT_ROOT_DIR}/core/protocols/protocolUtils.cpp + ${CLIENT_ROOT_DIR}/core/utils/selfhosted/scriptsRegistry.cpp + ${CLIENT_ROOT_DIR}/core/utils/qrCodeUtils.cpp ${CLIENT_ROOT_DIR}/core/controllers/coreController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.cpp ${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp - ${CLIENT_ROOT_DIR}/core/controllers/serverController.cpp - ${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.cpp - ${CLIENT_ROOT_DIR}/protocols/protocols_defs.cpp - ${CLIENT_ROOT_DIR}/ui/qautostart.cpp - ${CLIENT_ROOT_DIR}/protocols/vpnprotocol.cpp - ${CLIENT_ROOT_DIR}/core/sshclient.cpp - ${CLIENT_ROOT_DIR}/core/networkUtilities.cpp - ${CLIENT_ROOT_DIR}/core/serialization/outbound.cpp - ${CLIENT_ROOT_DIR}/core/serialization/inbound.cpp - ${CLIENT_ROOT_DIR}/core/serialization/ss.cpp - ${CLIENT_ROOT_DIR}/core/serialization/ssd.cpp - ${CLIENT_ROOT_DIR}/core/serialization/vless.cpp - ${CLIENT_ROOT_DIR}/core/serialization/trojan.cpp - ${CLIENT_ROOT_DIR}/core/serialization/vmess.cpp - ${CLIENT_ROOT_DIR}/core/serialization/vmess_new.cpp + ${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.cpp + ${CLIENT_ROOT_DIR}/core/controllers/serversController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/selfhosted/importController.cpp + ${CLIENT_ROOT_DIR}/core/installers/installerBase.cpp + ${CLIENT_ROOT_DIR}/core/installers/awgInstaller.cpp + ${CLIENT_ROOT_DIR}/core/installers/wireguardInstaller.cpp + ${CLIENT_ROOT_DIR}/core/installers/openvpnInstaller.cpp + ${CLIENT_ROOT_DIR}/core/installers/xrayInstaller.cpp + ${CLIENT_ROOT_DIR}/core/installers/torInstaller.cpp + ${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.cpp + ${CLIENT_ROOT_DIR}/core/installers/socks5Installer.cpp + ${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/connectionController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/settingsController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/api/subscriptionController.cpp + ${CLIENT_ROOT_DIR}/core/controllers/api/newsController.cpp + ${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.cpp + ${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.cpp + ${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.cpp + ${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.cpp + ${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.cpp + ${CLIENT_ROOT_DIR}/core/utils/networkUtilities.cpp + ${CLIENT_ROOT_DIR}/core/utils/serialization/outbound.cpp + ${CLIENT_ROOT_DIR}/core/utils/serialization/inbound.cpp + ${CLIENT_ROOT_DIR}/core/utils/serialization/ss.cpp + ${CLIENT_ROOT_DIR}/core/utils/serialization/ssd.cpp + ${CLIENT_ROOT_DIR}/core/utils/serialization/vless.cpp + ${CLIENT_ROOT_DIR}/core/utils/serialization/trojan.cpp + ${CLIENT_ROOT_DIR}/core/utils/serialization/vmess.cpp + ${CLIENT_ROOT_DIR}/core/utils/serialization/vmess_new.cpp ${CLIENT_ROOT_DIR}/../common/logger/logger.cpp - ${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp - ${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp - ${CLIENT_ROOT_DIR}/core/osSignalHandler.cpp + ${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp + ${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp + ${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp + ${CLIENT_ROOT_DIR}/core/utils/utilities.cpp + ${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp ) # Mozilla sources @@ -100,29 +159,41 @@ if(APPLE AND NOT IOS) list(APPEND HEADERS ${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h ${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h - ${CLIENT_ROOT_DIR}/ui/macos_util.h + ${CLIENT_ROOT_DIR}/ui/utils/macosUtil.h ) list(APPEND SOURCES ${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm ${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm - ${CLIENT_ROOT_DIR}/ui/macos_util.mm + ${CLIENT_ROOT_DIR}/ui/utils/macosUtil.mm ) endif() if(NOT ANDROID) set(SOURCES ${SOURCES} - ${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp + ${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.cpp ) endif() -file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.h) -file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.cpp) +set(COMMON_FILES_H + ${CLIENT_ROOT_DIR}/amneziaApplication.h + ${CLIENT_ROOT_DIR}/secureQSettings.h + ${CLIENT_ROOT_DIR}/vpnConnection.h +) + +set(COMMON_FILES_CPP + ${CLIENT_ROOT_DIR}/amneziaApplication.cpp + ${CLIENT_ROOT_DIR}/secureQSettings.cpp + ${CLIENT_ROOT_DIR}/vpnConnection.cpp +) file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.h) file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.cpp) -file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.h) -file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.cpp) +file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/configurators/*.h) +file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/configurators/*.cpp) + +file(GLOB_RECURSE CORE_MODELS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.h) +file(GLOB_RECURSE CORE_MODELS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.cpp) file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/models/*.h @@ -140,16 +211,21 @@ file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/controllers/*.h ${CLIENT_ROOT_DIR}/ui/controllers/api/*.h + ${CLIENT_ROOT_DIR}/ui/controllers/qml/*.h + ${CLIENT_ROOT_DIR}/ui/controllers/selfhosted/*.h ) file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/controllers/*.cpp ${CLIENT_ROOT_DIR}/ui/controllers/api/*.cpp + ${CLIENT_ROOT_DIR}/ui/controllers/qml/*.cpp + ${CLIENT_ROOT_DIR}/ui/controllers/selfhosted/*.cpp ) set(HEADERS ${HEADERS} ${COMMON_FILES_H} ${PAGE_LOGIC_H} ${CONFIGURATORS_H} + ${CORE_MODELS_H} ${UI_MODELS_H} ${UI_CONTROLLERS_H} ) @@ -157,17 +233,18 @@ set(SOURCES ${SOURCES} ${COMMON_FILES_CPP} ${PAGE_LOGIC_CPP} ${CONFIGURATORS_CPP} + ${CORE_MODELS_CPP} ${UI_MODELS_CPP} ${UI_CONTROLLERS_CPP} ) if(WIN32) set(HEADERS ${HEADERS} - ${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.h + ${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolWindows.h ) set(SOURCES ${SOURCES} - ${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.cpp + ${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolWindows.cpp ) set(RESOURCES ${RESOURCES} @@ -180,37 +257,33 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID)) add_compile_definitions(AMNEZIA_DESKTOP) set(HEADERS ${HEADERS} - ${CLIENT_ROOT_DIR}/core/ipcclient.h - ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h - ${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h - ${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h - ${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.h - ${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h - ${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h - ${CLIENT_ROOT_DIR}/protocols/awgprotocol.h + ${CLIENT_ROOT_DIR}/core/utils/ipcClient.h + ${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.h + ${CLIENT_ROOT_DIR}/core/protocols/openVpnProtocol.h + ${CLIENT_ROOT_DIR}/core/protocols/wireGuardProtocol.h + ${CLIENT_ROOT_DIR}/core/protocols/xrayProtocol.h + ${CLIENT_ROOT_DIR}/core/protocols/awgProtocol.h ${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h ) set(SOURCES ${SOURCES} - ${CLIENT_ROOT_DIR}/core/ipcclient.cpp + ${CLIENT_ROOT_DIR}/core/utils/ipcClient.cpp ${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp - ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp - ${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp - ${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp - ${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.cpp - ${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.cpp - ${CLIENT_ROOT_DIR}/protocols/xrayprotocol.cpp - ${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp + ${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.cpp + ${CLIENT_ROOT_DIR}/core/protocols/openVpnProtocol.cpp + ${CLIENT_ROOT_DIR}/core/protocols/wireGuardProtocol.cpp + ${CLIENT_ROOT_DIR}/core/protocols/xrayProtocol.cpp + ${CLIENT_ROOT_DIR}/core/protocols/awgProtocol.cpp ) endif() if(APPLE AND MACOS_NE) # Include only the tray notification handler in NE builds set(HEADERS ${HEADERS} - ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h + ${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.h ) set(SOURCES ${SOURCES} - ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp + ${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.cpp ) endif() diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp deleted file mode 100644 index 641145249..000000000 --- a/client/configurators/awg_configurator.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "awg_configurator.h" -#include "protocols/protocols_defs.h" - -#include -#include - -AwgConfigurator::AwgConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent) - : WireguardConfigurator(settings, serverController, true, parent) -{ -} - -QString AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, - ErrorCode &errorCode) -{ - QString config = WireguardConfigurator::createConfig(credentials, container, containerConfig, errorCode); - - QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); - QString awgConfig = jsonConfig.value(config_key::config).toString(); - - QMap configMap; - auto configLines = awgConfig.split("\n"); - for (auto &line : configLines) { - auto trimmedLine = line.trimmed(); - if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { - continue; - } else { - QStringList parts = trimmedLine.split(" = "); - if (parts.count() == 2) { - configMap.insert(parts[0].trimmed(), parts[1].trimmed()); - } - } - } - - jsonConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount); - jsonConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize); - jsonConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize); - jsonConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize); - jsonConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize); - jsonConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader); - jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); - jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); - jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); - - if (container == DockerContainer::Awg2) { - jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize); - jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize); - } - - jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1); - jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2); - jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3); - jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4); - jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5); - - jsonConfig[config_key::mtu] = - containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu); - - return QJsonDocument(jsonConfig).toJson(); -} diff --git a/client/configurators/awg_configurator.h b/client/configurators/awg_configurator.h deleted file mode 100644 index 301b927c7..000000000 --- a/client/configurators/awg_configurator.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef AWGCONFIGURATOR_H -#define AWGCONFIGURATOR_H - -#include - -#include "wireguard_configurator.h" - -class AwgConfigurator : public WireguardConfigurator -{ - Q_OBJECT -public: - AwgConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent = nullptr); - - QString createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode); -}; - -#endif // AWGCONFIGURATOR_H diff --git a/client/configurators/cloak_configurator.cpp b/client/configurators/cloak_configurator.cpp deleted file mode 100644 index fd0c03915..000000000 --- a/client/configurators/cloak_configurator.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "cloak_configurator.h" - -#include -#include -#include - -#include "containers/containers_defs.h" -#include "core/controllers/serverController.h" - -CloakConfigurator::CloakConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent) - : ConfiguratorBase(settings, serverController, parent) -{ -} - -QString CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, - ErrorCode &errorCode) -{ - QString cloakPublicKey = - m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckPublicKeyPath, errorCode); - cloakPublicKey.replace("\n", ""); - - if (errorCode != ErrorCode::NoError) { - return ""; - } - - QString cloakBypassUid = - m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckBypassUidKeyPath, errorCode); - cloakBypassUid.replace("\n", ""); - - if (errorCode != ErrorCode::NoError) { - return ""; - } - - QJsonObject config; - config.insert("Transport", "direct"); - config.insert("ProxyMethod", "openvpn"); - config.insert("EncryptionMethod", "aes-gcm"); - config.insert("UID", cloakBypassUid); - config.insert("PublicKey", cloakPublicKey); - config.insert("ServerName", "$FAKE_WEB_SITE_ADDRESS"); - config.insert("NumConn", 1); - config.insert("BrowserSig", "chrome"); - config.insert("StreamTimeout", 300); - config.insert("RemoteHost", credentials.hostName); - config.insert("RemotePort", "$CLOAK_SERVER_PORT"); - - QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(), - m_serverController->genVarsForScript(credentials, container, containerConfig)); - - return textCfg; -} diff --git a/client/configurators/cloak_configurator.h b/client/configurators/cloak_configurator.h deleted file mode 100644 index d117a821b..000000000 --- a/client/configurators/cloak_configurator.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef CLOAK_CONFIGURATOR_H -#define CLOAK_CONFIGURATOR_H - -#include - -#include "configurator_base.h" - -using namespace amnezia; - -class CloakConfigurator : public ConfiguratorBase -{ - Q_OBJECT -public: - CloakConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent = nullptr); - - QString createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode); -}; - -#endif // CLOAK_CONFIGURATOR_H diff --git a/client/configurators/configurator_base.cpp b/client/configurators/configurator_base.cpp deleted file mode 100644 index 3502538c0..000000000 --- a/client/configurators/configurator_base.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "configurator_base.h" - -ConfiguratorBase::ConfiguratorBase(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent) - : QObject { parent }, m_settings(settings), m_serverController(serverController) -{ -} - -QString ConfiguratorBase::processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, - QString &protocolConfigString) -{ - processConfigWithDnsSettings(dns, protocolConfigString); - return protocolConfigString; -} - -QString ConfiguratorBase::processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, - QString &protocolConfigString) -{ - processConfigWithDnsSettings(dns, protocolConfigString); - return protocolConfigString; -} - -void ConfiguratorBase::processConfigWithDnsSettings(const QPair &dns, QString &protocolConfigString) -{ - protocolConfigString.replace("$PRIMARY_DNS", dns.first); - protocolConfigString.replace("$SECONDARY_DNS", dns.second); -} diff --git a/client/configurators/configurator_base.h b/client/configurators/configurator_base.h deleted file mode 100644 index 2427b604a..000000000 --- a/client/configurators/configurator_base.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef CONFIGURATORBASE_H -#define CONFIGURATORBASE_H - -#include - -#include "containers/containers_defs.h" -#include "core/defs.h" -#include "core/controllers/serverController.h" -#include "settings.h" - -class ConfiguratorBase : public QObject -{ - Q_OBJECT -public: - explicit ConfiguratorBase(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent = nullptr); - - virtual QString createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode) = 0; - - virtual QString processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, - QString &protocolConfigString); - virtual QString processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, - QString &protocolConfigString); - -protected: - void processConfigWithDnsSettings(const QPair &dns, QString &protocolConfigString); - - std::shared_ptr m_settings; - QSharedPointer m_serverController; - -}; - -#endif // CONFIGURATORBASE_H diff --git a/client/configurators/ikev2_configurator.h b/client/configurators/ikev2_configurator.h deleted file mode 100644 index e3a852163..000000000 --- a/client/configurators/ikev2_configurator.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef IKEV2_CONFIGURATOR_H -#define IKEV2_CONFIGURATOR_H - -#include -#include - -#include "configurator_base.h" -#include "core/defs.h" - -class Ikev2Configurator : public ConfiguratorBase -{ - Q_OBJECT -public: - Ikev2Configurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent = nullptr); - - struct ConnectionData { - QByteArray clientCert; // p12 client cert - QByteArray caCert; // p12 server cert - QString clientId; - QString password; // certificate password - QString host; // host ip - }; - - QString createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode); - - QString genIkev2Config(const ConnectionData &connData); - QString genMobileConfig(const ConnectionData &connData); - QString genStrongSwanConfig(const ConnectionData &connData); - - ConnectionData prepareIkev2Config(const ServerCredentials &credentials, - DockerContainer container, ErrorCode &errorCode); -}; - -#endif // IKEV2_CONFIGURATOR_H diff --git a/client/configurators/openvpn_configurator.h b/client/configurators/openvpn_configurator.h deleted file mode 100644 index 48e3f4be7..000000000 --- a/client/configurators/openvpn_configurator.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef OPENVPN_CONFIGURATOR_H -#define OPENVPN_CONFIGURATOR_H - -#include -#include - -#include "configurator_base.h" -#include "core/defs.h" - -class OpenVpnConfigurator : public ConfiguratorBase -{ - Q_OBJECT -public: - OpenVpnConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent = nullptr); - - struct ConnectionData - { - QString clientId; - QString request; // certificate request - QString privKey; // client private key - QString clientCert; // client signed certificate - QString caCert; // server certificate - QString taKey; // tls-auth key - QString host; // host ip - }; - - QString createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode); - - QString processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, - QString &protocolConfigString); - QString processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, - QString &protocolConfigString); - - static ConnectionData createCertRequest(); - -private: - ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, - ErrorCode &errorCode); - ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId); -}; - -#endif // OPENVPN_CONFIGURATOR_H diff --git a/client/configurators/shadowsocks_configurator.cpp b/client/configurators/shadowsocks_configurator.cpp deleted file mode 100644 index fd6c48413..000000000 --- a/client/configurators/shadowsocks_configurator.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "shadowsocks_configurator.h" - -#include -#include -#include - -#include "containers/containers_defs.h" -#include "core/controllers/serverController.h" - -ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, - QObject *parent) - : ConfiguratorBase(settings, serverController, parent) -{ -} - -QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode) -{ - QString ssKey = - m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::shadowsocks::ssKeyPath, errorCode); - ssKey.replace("\n", ""); - - if (errorCode != ErrorCode::NoError) { - return ""; - } - - QJsonObject config; - config.insert("server", credentials.hostName); - config.insert("server_port", "$SHADOWSOCKS_SERVER_PORT"); - config.insert("local_port", "$SHADOWSOCKS_LOCAL_PORT"); - config.insert("password", ssKey); - config.insert("timeout", 60); - config.insert("method", "$SHADOWSOCKS_CIPHER"); - - QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(), - m_serverController->genVarsForScript(credentials, container, containerConfig)); - - // qDebug().noquote() << textCfg; - return textCfg; -} diff --git a/client/configurators/shadowsocks_configurator.h b/client/configurators/shadowsocks_configurator.h deleted file mode 100644 index b21b3be31..000000000 --- a/client/configurators/shadowsocks_configurator.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef SHADOWSOCKS_CONFIGURATOR_H -#define SHADOWSOCKS_CONFIGURATOR_H - -#include - -#include "configurator_base.h" -#include "core/defs.h" - -class ShadowSocksConfigurator : public ConfiguratorBase -{ - Q_OBJECT -public: - ShadowSocksConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent = nullptr); - - QString createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode); -}; - -#endif // SHADOWSOCKS_CONFIGURATOR_H diff --git a/client/configurators/ssh_configurator.cpp b/client/configurators/ssh_configurator.cpp deleted file mode 100644 index 8e190103d..000000000 --- a/client/configurators/ssh_configurator.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "ssh_configurator.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) - #include -#else - #include -#endif - -#include "core/server_defs.h" -#include "utilities.h" - -SshConfigurator::SshConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent) - : ConfiguratorBase(settings, serverController, parent) -{ -} - -QString SshConfigurator::convertOpenSShKey(const QString &key) -{ -#if !defined(Q_OS_IOS) && !defined(MACOS_NE) - QProcess p; - p.setProcessChannelMode(QProcess::MergedChannels); - - QTemporaryFile tmp; - #ifdef QT_DEBUG - tmp.setAutoRemove(false); - #endif - tmp.open(); - tmp.write(key.toUtf8()); - tmp.close(); - - // ssh-keygen -p -P "" -N "" -m pem -f id_ssh - - #ifdef Q_OS_WIN - p.setProcessEnvironment(prepareEnv()); - p.setProgram("cmd.exe"); - p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName())); - #else - p.setProgram("ssh-keygen"); - p.setArguments(QStringList() << "-p" - << "-P" - << "" - << "-N" - << "" - << "-m" - << "pem" - << "-f" << tmp.fileName()); - #endif - - p.start(); - p.waitForFinished(); - - qDebug().noquote() << "OpenVpnConfigurator::convertOpenSShKey" << p.exitCode() << p.exitStatus() << p.readAll(); - - tmp.open(); - - return tmp.readAll(); -#else - return key; -#endif -} - -// DEAD CODE. -void SshConfigurator::openSshTerminal(const ServerCredentials &credentials) -{ -#if !defined(Q_OS_IOS) && !defined(MACOS_NE) - QProcess *p = new QProcess(); - p->setProcessChannelMode(QProcess::SeparateChannels); - - #ifdef Q_OS_WIN - p->setProcessEnvironment(prepareEnv()); - p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe"); - - if (credentials.secretData.contains("PRIVATE KEY")) { - // todo: connect by key - // p->setNativeArguments(QString("%1@%2") - // .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); - } else { - p->setNativeArguments(QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); - } - #else - p->setProgram("/bin/bash"); - #endif - - p->startDetached(); -#endif -} - -QProcessEnvironment SshConfigurator::prepareEnv() -{ - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - QString pathEnvVar = env.value("PATH"); - -#ifdef Q_OS_WIN - pathEnvVar.clear(); - pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;"); - pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;"); -#elif defined(Q_OS_MACX) && !defined(MACOS_NE) - pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS"); -#endif - - env.insert("PATH", pathEnvVar); - // qDebug().noquote() << "ENV PATH" << pathEnvVar; - return env; -} diff --git a/client/configurators/ssh_configurator.h b/client/configurators/ssh_configurator.h deleted file mode 100644 index be8c0a3b7..000000000 --- a/client/configurators/ssh_configurator.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef SSH_CONFIGURATOR_H -#define SSH_CONFIGURATOR_H - -#include -#include - -#include "configurator_base.h" -#include "core/defs.h" - -class SshConfigurator : ConfiguratorBase -{ - Q_OBJECT -public: - SshConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent = nullptr); - - QProcessEnvironment prepareEnv(); - QString convertOpenSShKey(const QString &key); - void openSshTerminal(const ServerCredentials &credentials); - -}; - -#endif // SSH_CONFIGURATOR_H diff --git a/client/configurators/wireguard_configurator.h b/client/configurators/wireguard_configurator.h deleted file mode 100644 index a4302e3ed..000000000 --- a/client/configurators/wireguard_configurator.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef WIREGUARD_CONFIGURATOR_H -#define WIREGUARD_CONFIGURATOR_H - -#include -#include -#include - -#include "configurator_base.h" -#include "core/defs.h" -#include "core/scripts_registry.h" - -class WireguardConfigurator : public ConfiguratorBase -{ - Q_OBJECT -public: - WireguardConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, - bool isAwg, QObject *parent = nullptr); - - struct ConnectionData - { - QString clientPrivKey; // client private key - QString clientPubKey; // client public key - QString clientIP; // internal client IP address - QString serverPubKey; // tls-auth key - QString pskKey; // preshared key - QString host; // host ip - QString port; - }; - - QString createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode); - - QString processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, - QString &protocolConfigString); - QString processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, - QString &protocolConfigString); - - static ConnectionData genClientKeys(); - -private: - QList getIpsFromConf(const QString &input); - ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode); - - bool m_isAwg; - QString m_serverConfigPath; - QString m_serverPublicKeyPath; - QString m_serverPskKeyPath; - amnezia::ProtocolScriptType m_configTemplate; - QString m_protocolName; - QString m_defaultPort; -}; - -#endif // WIREGUARD_CONFIGURATOR_H diff --git a/client/configurators/xray_configurator.h b/client/configurators/xray_configurator.h deleted file mode 100644 index 8ed4e7752..000000000 --- a/client/configurators/xray_configurator.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef XRAY_CONFIGURATOR_H -#define XRAY_CONFIGURATOR_H - -#include - -#include "configurator_base.h" -#include "core/defs.h" - -class XrayConfigurator : public ConfiguratorBase -{ - Q_OBJECT -public: - XrayConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent = nullptr); - - QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, - ErrorCode &errorCode); - -private: - QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, - ErrorCode &errorCode); -}; - -#endif // XRAY_CONFIGURATOR_H diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h deleted file mode 100644 index 182a402b8..000000000 --- a/client/containers/containers_defs.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef CONTAINERS_DEFS_H -#define CONTAINERS_DEFS_H - -#include -#include - -#include "../protocols/protocols_defs.h" - -using namespace amnezia; - -namespace amnezia -{ - - namespace ContainerEnumNS - { - Q_NAMESPACE - enum DockerContainer { - None = 0, - Awg, - Awg2, - WireGuard, - OpenVpn, - Cloak, - ShadowSocks, - Ipsec, - Xray, - SSXray, - - // non-vpn - TorWebSite, - Dns, - Sftp, - Socks5Proxy - }; - Q_ENUM_NS(DockerContainer) - } // namespace ContainerEnumNS - - using namespace ContainerEnumNS; - using namespace ProtocolEnumNS; - - class ContainerProps : public QObject - { - Q_OBJECT - - public: - Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container); - Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container); - Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c); - Q_INVOKABLE static QString containerTypeToProtocolString(amnezia::DockerContainer c); - - Q_INVOKABLE static QList allContainers(); - - Q_INVOKABLE static QMap containerHumanNames(); - Q_INVOKABLE static QMap containerDescriptions(); - Q_INVOKABLE static QMap containerDetailedDescriptions(); - - // these protocols will be displayed in container settings - Q_INVOKABLE static QVector protocolsForContainer(amnezia::DockerContainer container); - - Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c); - - // binding between Docker container and main protocol of given container - // it may be changed fot future containers :) - Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c); - - Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c); - Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c); - - static bool isEasySetupContainer(amnezia::DockerContainer container); - static QString easySetupHeader(amnezia::DockerContainer container); - static QString easySetupDescription(amnezia::DockerContainer container); - static int easySetupOrder(amnezia::DockerContainer container); - - static bool isShareable(amnezia::DockerContainer container); - - static bool isAwgContainer(amnezia::DockerContainer container); - - - static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig); - - static int installPageOrder(amnezia::DockerContainer container); - }; - - static void declareQmlContainerEnum() - { - qmlRegisterUncreatableMetaObject(ContainerEnumNS::staticMetaObject, "ContainerEnum", 1, 0, "ContainerEnum", - "Error: only enums"); - } - -} // namespace amnezia - -QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c); - -#endif // CONTAINERS_DEFS_H diff --git a/client/core/configurators/awgConfigurator.cpp b/client/core/configurators/awgConfigurator.cpp new file mode 100644 index 000000000..068b47ffc --- /dev/null +++ b/client/core/configurators/awgConfigurator.cpp @@ -0,0 +1,109 @@ +#include "awgConfigurator.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/models/containerConfig.h" +#include "core/models/protocols/awgProtocolConfig.h" + +#include +#include + +using namespace amnezia; + +AwgConfigurator::AwgConfigurator(SshSession* sshSession, QObject *parent) + : WireguardConfigurator(sshSession, true, parent) +{ +} + +ProtocolConfig AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &containerConfig, + const DnsSettings &dnsSettings, + ErrorCode &errorCode) +{ + const AwgServerConfig* serverConfig = nullptr; + const AwgClientConfig* clientConfig = nullptr; + + if (auto* awgProtocolConfig = containerConfig.getAwgProtocolConfig()) { + serverConfig = &awgProtocolConfig->serverConfig; + if (awgProtocolConfig->clientConfig.has_value()) { + clientConfig = &awgProtocolConfig->clientConfig.value(); + } + } + + ProtocolConfig wireguardConfig = WireguardConfigurator::createConfig(credentials, container, containerConfig, dnsSettings, errorCode); + if (errorCode != ErrorCode::NoError) { + return AwgProtocolConfig{}; + } + + WireGuardProtocolConfig* wgConfig = wireguardConfig.as(); + if (!wgConfig || !wgConfig->clientConfig.has_value()) { + errorCode = ErrorCode::InternalError; + return AwgProtocolConfig{}; + } + + QString awgConfig = wgConfig->clientConfig->nativeConfig; + + QMap configMap; + auto configLines = awgConfig.split("\n"); + for (auto &line : configLines) { + auto trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + configMap.insert(parts[0].trimmed(), parts[1].trimmed()); + } + } + } + + AwgProtocolConfig protocolConfig; + if (serverConfig) { + protocolConfig.serverConfig = *serverConfig; + } + + AwgClientConfig newClientConfig; + newClientConfig.nativeConfig = awgConfig; + newClientConfig.hostName = wgConfig->clientConfig->hostName; + newClientConfig.port = wgConfig->clientConfig->port; + newClientConfig.clientIp = wgConfig->clientConfig->clientIp; + newClientConfig.clientPrivateKey = wgConfig->clientConfig->clientPrivateKey; + newClientConfig.clientPublicKey = wgConfig->clientConfig->clientPublicKey; + newClientConfig.serverPublicKey = wgConfig->clientConfig->serverPublicKey; + newClientConfig.presharedKey = wgConfig->clientConfig->presharedKey; + newClientConfig.clientId = wgConfig->clientConfig->clientId; + newClientConfig.allowedIps = wgConfig->clientConfig->allowedIps; + newClientConfig.persistentKeepAlive = wgConfig->clientConfig->persistentKeepAlive; + + QString mtu = protocols::awg::defaultMtu; + if (clientConfig && !clientConfig->mtu.isEmpty()) { + mtu = clientConfig->mtu; + } + newClientConfig.mtu = mtu; + + newClientConfig.junkPacketCount = configMap.value(configKey::junkPacketCount); + newClientConfig.junkPacketMinSize = configMap.value(configKey::junkPacketMinSize); + newClientConfig.junkPacketMaxSize = configMap.value(configKey::junkPacketMaxSize); + newClientConfig.initPacketJunkSize = configMap.value(configKey::initPacketJunkSize); + newClientConfig.responsePacketJunkSize = configMap.value(configKey::responsePacketJunkSize); + newClientConfig.initPacketMagicHeader = configMap.value(configKey::initPacketMagicHeader); + newClientConfig.responsePacketMagicHeader = configMap.value(configKey::responsePacketMagicHeader); + newClientConfig.underloadPacketMagicHeader = configMap.value(configKey::underloadPacketMagicHeader); + newClientConfig.transportPacketMagicHeader = configMap.value(configKey::transportPacketMagicHeader); + newClientConfig.specialJunk1 = configMap.value(configKey::specialJunk1); + newClientConfig.specialJunk2 = configMap.value(configKey::specialJunk2); + newClientConfig.specialJunk3 = configMap.value(configKey::specialJunk3); + newClientConfig.specialJunk4 = configMap.value(configKey::specialJunk4); + newClientConfig.specialJunk5 = configMap.value(configKey::specialJunk5); + + if (container == DockerContainer::Awg2) { + newClientConfig.cookieReplyPacketJunkSize = configMap.value(configKey::cookieReplyPacketJunkSize); + newClientConfig.transportPacketJunkSize = configMap.value(configKey::transportPacketJunkSize); + } + + newClientConfig.isObfuscationEnabled = false; + + protocolConfig.setClientConfig(newClientConfig); + + return protocolConfig; +} diff --git a/client/core/configurators/awgConfigurator.h b/client/core/configurators/awgConfigurator.h new file mode 100644 index 000000000..5aa21b381 --- /dev/null +++ b/client/core/configurators/awgConfigurator.h @@ -0,0 +1,20 @@ +#ifndef AWGCONFIGURATOR_H +#define AWGCONFIGURATOR_H + +#include + +#include "wireguardConfigurator.h" + +class AwgConfigurator : public WireguardConfigurator +{ + Q_OBJECT +public: + AwgConfigurator(SshSession* sshSession, QObject *parent = nullptr); + + amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, + const amnezia::ContainerConfig &containerConfig, + const amnezia::DnsSettings &dnsSettings, + amnezia::ErrorCode &errorCode) override; +}; + +#endif // AWGCONFIGURATOR_H diff --git a/client/core/configurators/configuratorBase.cpp b/client/core/configurators/configuratorBase.cpp new file mode 100644 index 000000000..bbeb2591a --- /dev/null +++ b/client/core/configurators/configuratorBase.cpp @@ -0,0 +1,50 @@ +#include "configuratorBase.h" + +#include "core/configurators/awgConfigurator.h" +#include "core/configurators/ikev2Configurator.h" +#include "core/configurators/openVpnConfigurator.h" +#include "core/configurators/wireguardConfigurator.h" +#include "core/configurators/xrayConfigurator.h" + +using namespace amnezia; + +ConfiguratorBase::ConfiguratorBase(SshSession* sshSession, QObject *parent) + : QObject { parent }, m_sshSession(sshSession) +{ +} + +QScopedPointer ConfiguratorBase::create(Proto protocol, + SshSession* sshSession) +{ + switch (protocol) { + case Proto::OpenVpn: return QScopedPointer(new OpenVpnConfigurator(sshSession)); + case Proto::WireGuard: return QScopedPointer(new WireguardConfigurator(sshSession, false)); + case Proto::Awg: return QScopedPointer(new AwgConfigurator(sshSession)); + case Proto::Ikev2: return QScopedPointer(new Ikev2Configurator(sshSession)); + case Proto::Xray: return QScopedPointer(new XrayConfigurator(sshSession)); + case Proto::SSXray: return QScopedPointer(new XrayConfigurator(sshSession)); + default: return QScopedPointer(); + } +} + +ProtocolConfig ConfiguratorBase::processConfigWithLocalSettings(const ConnectionSettings &settings, + ProtocolConfig protocolConfig) +{ + applyDnsToNativeConfig(settings.dns, protocolConfig); + return protocolConfig; +} + +ProtocolConfig ConfiguratorBase::processConfigWithExportSettings(const ExportSettings &settings, + ProtocolConfig protocolConfig) +{ + applyDnsToNativeConfig(settings.dns, protocolConfig); + return protocolConfig; +} + +void ConfiguratorBase::applyDnsToNativeConfig(const DnsSettings &dns, ProtocolConfig &protocolConfig) +{ + QString config = protocolConfig.nativeConfig(); + config.replace("$PRIMARY_DNS", dns.primaryDns); + config.replace("$SECONDARY_DNS", dns.secondaryDns); + protocolConfig.setNativeConfig(config); +} diff --git a/client/core/configurators/configuratorBase.h b/client/core/configurators/configuratorBase.h new file mode 100644 index 000000000..b4a5888b6 --- /dev/null +++ b/client/core/configurators/configuratorBase.h @@ -0,0 +1,43 @@ +#ifndef CONFIGURATORBASE_H +#define CONFIGURATORBASE_H + +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/models/containerConfig.h" +#include "core/models/protocolConfig.h" + +class SshSession; + +class ConfiguratorBase : public QObject +{ + Q_OBJECT +public: + explicit ConfiguratorBase(SshSession* sshSession, QObject *parent = nullptr); + + static QScopedPointer create(amnezia::Proto protocol, + SshSession* sshSession); + + virtual amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, + const amnezia::ContainerConfig &containerConfig, + const amnezia::DnsSettings &dnsSettings, + amnezia::ErrorCode &errorCode) = 0; + + virtual amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings, + amnezia::ProtocolConfig protocolConfig); + virtual amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings, + amnezia::ProtocolConfig protocolConfig); + +protected: + void applyDnsToNativeConfig(const amnezia::DnsSettings &dns, amnezia::ProtocolConfig &protocolConfig); + + SshSession* m_sshSession; +}; + +#endif // CONFIGURATORBASE_H diff --git a/client/configurators/ikev2_configurator.cpp b/client/core/configurators/ikev2Configurator.cpp similarity index 56% rename from client/configurators/ikev2_configurator.cpp rename to client/core/configurators/ikev2Configurator.cpp index 894a0e3df..dec6e4750 100644 --- a/client/configurators/ikev2_configurator.cpp +++ b/client/core/configurators/ikev2Configurator.cpp @@ -1,4 +1,4 @@ -#include "ikev2_configurator.h" +#include "ikev2Configurator.h" #include #include @@ -8,14 +8,16 @@ #include #include -#include "containers/containers_defs.h" -#include "core/controllers/serverController.h" -#include "core/scripts_registry.h" -#include "core/server_defs.h" -#include "utilities.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/selfhosted/sshSession.h" +#include "core/utils/selfhosted/scriptsRegistry.h" +#include "core/utils/utilities.h" +#include "core/models/protocols/ikev2ProtocolConfig.h" -Ikev2Configurator::Ikev2Configurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent) - : ConfiguratorBase(settings, serverController, parent) +Ikev2Configurator::Ikev2Configurator(SshSession* sshSession, QObject *parent) + : ConfiguratorBase(sshSession, parent) { } @@ -25,7 +27,6 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se Ikev2Configurator::ConnectionData connData; connData.host = credentials.hostName; connData.clientId = Utils::getRandomString(16); - connData.password = Utils::getRandomString(16); connData.password = ""; QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12"; @@ -39,14 +40,14 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se "--extKeyUsage serverAuth,clientAuth -8 \"%1\"") .arg(connData.clientId); - errorCode = m_serverController->runContainerScript(credentials, container, scriptCreateCert); + errorCode = m_sshSession->runContainerScript(credentials, container, scriptCreateCert); QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"").arg(connData.password).arg(connData.clientId).arg(certFileName); - errorCode = m_serverController->runContainerScript(credentials, container, scriptExportCert); + errorCode = m_sshSession->runContainerScript(credentials, container, scriptExportCert); - connData.clientCert = m_serverController->getTextFileFromContainer(container, credentials, certFileName, errorCode); - connData.caCert = m_serverController->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode); + connData.clientCert = m_sshSession->getTextFileFromContainer(container, credentials, certFileName, errorCode); + connData.caCert = m_sshSession->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode); qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size(); qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size(); @@ -54,26 +55,51 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se return connData; } -QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, - ErrorCode &errorCode) +ProtocolConfig Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &containerConfig, + const DnsSettings &dnsSettings, + ErrorCode &errorCode) { - Q_UNUSED(containerConfig) + const Ikev2ServerConfig* serverConfig = nullptr; + if (auto* ikev2Config = containerConfig.protocolConfig.as()) { + serverConfig = &ikev2Config->serverConfig; + } ConnectionData connData = prepareIkev2Config(credentials, container, errorCode); if (errorCode != ErrorCode::NoError) { - return ""; + return Ikev2ProtocolConfig{}; } - return genIkev2Config(connData); + QString configJson = genIkev2Config(connData); + QJsonDocument doc = QJsonDocument::fromJson(configJson.toUtf8()); + QJsonObject configObj = doc.object(); + + Ikev2ProtocolConfig protocolConfig; + if (serverConfig) { + protocolConfig.serverConfig = *serverConfig; + } else { + protocolConfig.serverConfig.hostName = connData.host; + } + + Ikev2ClientConfig clientConfig; + clientConfig.nativeConfig = configJson; + clientConfig.hostName = connData.host; + clientConfig.userName = connData.clientId; + clientConfig.cert = QString(connData.clientCert.toBase64()); + clientConfig.password = connData.password; + clientConfig.clientId = connData.clientId; + + protocolConfig.setClientConfig(clientConfig); + + return protocolConfig; } QString Ikev2Configurator::genIkev2Config(const ConnectionData &connData) { QJsonObject config; - config[config_key::hostName] = connData.host; - config[config_key::userName] = connData.clientId; - config[config_key::cert] = QString(connData.clientCert.toBase64()); - config[config_key::password] = connData.password; + config[configKey::hostName] = connData.host; + config[configKey::userName] = connData.clientId; + config[configKey::cert] = QString(connData.clientCert.toBase64()); + config[configKey::password] = connData.password; return QJsonDocument(config).toJson(); } diff --git a/client/core/configurators/ikev2Configurator.h b/client/core/configurators/ikev2Configurator.h new file mode 100644 index 000000000..931be73f2 --- /dev/null +++ b/client/core/configurators/ikev2Configurator.h @@ -0,0 +1,39 @@ +#ifndef IKEV2_CONFIGURATOR_H +#define IKEV2_CONFIGURATOR_H + +#include +#include + +#include "configuratorBase.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" + +class Ikev2Configurator : public ConfiguratorBase +{ + Q_OBJECT +public: + Ikev2Configurator(SshSession* sshSession, QObject *parent = nullptr); + + struct ConnectionData { + QByteArray clientCert; // p12 client cert + QByteArray caCert; // p12 server cert + QString clientId; + QString password; // certificate password + QString host; // host ip + }; + + amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, + const amnezia::ContainerConfig &containerConfig, + const amnezia::DnsSettings &dnsSettings, + amnezia::ErrorCode &errorCode) override; + + QString genIkev2Config(const ConnectionData &connData); + QString genMobileConfig(const ConnectionData &connData); + QString genStrongSwanConfig(const ConnectionData &connData); + + ConnectionData prepareIkev2Config(const amnezia::ServerCredentials &credentials, + amnezia::DockerContainer container, amnezia::ErrorCode &errorCode); +}; + +#endif // IKEV2_CONFIGURATOR_H diff --git a/client/configurators/openvpn_configurator.cpp b/client/core/configurators/openVpnConfigurator.cpp similarity index 59% rename from client/configurators/openvpn_configurator.cpp rename to client/core/configurators/openVpnConfigurator.cpp index 75c611d76..ab6dda4f5 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/core/configurators/openVpnConfigurator.cpp @@ -1,8 +1,9 @@ -#include "openvpn_configurator.h" +#include "openVpnConfigurator.h" #include #include #include +#include #include #include #include @@ -13,26 +14,34 @@ #include #endif -#include "core/networkUtilities.h" -#include "containers/containers_defs.h" -#include "core/controllers/serverController.h" -#include "core/scripts_registry.h" -#include "settings.h" -#include "utilities.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/utils/networkUtilities.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/selfhosted/sshSession.h" +#include "core/utils/selfhosted/scriptsRegistry.h" +#include "core/utils/utilities.h" +#include "core/models/protocols/openVpnProtocolConfig.h" + +using namespace amnezia; #include #include #include -OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, - QObject *parent) - : ConfiguratorBase(settings, serverController, parent) +OpenVpnConfigurator::OpenVpnConfigurator(SshSession* sshSession, QObject *parent) + : ConfiguratorBase(sshSession, parent) { } OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials, - DockerContainer container, ErrorCode &errorCode) + DockerContainer container, + const DnsSettings &dnsSettings, + ErrorCode &errorCode) { OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest(); connData.host = credentials.hostName; @@ -44,26 +53,26 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId); - errorCode = m_serverController->uploadTextFileToContainer(container, credentials, connData.request, reqFileName); + errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, connData.request, reqFileName); if (errorCode != ErrorCode::NoError) { return connData; } - errorCode = signCert(container, credentials, connData.clientId); + errorCode = signCert(container, credentials, dnsSettings, connData.clientId); if (errorCode != ErrorCode::NoError) { return connData; } connData.caCert = - m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode); - connData.clientCert = m_serverController->getTextFileFromContainer( + m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode); + connData.clientCert = m_sshSession->getTextFileFromContainer( container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode); if (errorCode != ErrorCode::NoError) { return connData; } - connData.taKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode); + connData.taKey = m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode); if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) { errorCode = ErrorCode::SshScpFailureError; @@ -72,15 +81,23 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co return connData; } -QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode) +ProtocolConfig OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const ContainerConfig &containerConfig, + const DnsSettings &dnsSettings, + ErrorCode &errorCode) { - QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), - m_serverController->genVarsForScript(credentials, container, containerConfig)); + const OpenVpnServerConfig* serverConfig = nullptr; + if (auto* openVpnProtocolConfig = containerConfig.getOpenVpnProtocolConfig()) { + serverConfig = &openVpnProtocolConfig->serverConfig; + } + + amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns); + vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig)); + QString config = m_sshSession->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), vars); - ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode); + ConnectionData connData = prepareOpenVpnConfig(credentials, container, dnsSettings, errorCode); if (errorCode != ErrorCode::NoError) { - return ""; + return OpenVpnProtocolConfig{}; } auto sanitizeStaticKey = [](const QString &key) { @@ -116,42 +133,45 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, config.replace("block-outside-dns", ""); #endif - QJsonObject jConfig; - jConfig[config_key::config] = config; - - jConfig[config_key::clientId] = connData.clientId; - - return QJsonDocument(jConfig).toJson(); + OpenVpnProtocolConfig protocolConfig; + if (serverConfig) { + protocolConfig.serverConfig = *serverConfig; + } + + OpenVpnClientConfig clientConfig; + clientConfig.nativeConfig = config; + clientConfig.clientId = connData.clientId; + clientConfig.blockOutsideDns = false; + + protocolConfig.setClientConfig(clientConfig); + + return protocolConfig; } -QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, - QString &protocolConfigString) +ProtocolConfig OpenVpnConfigurator::processConfigWithLocalSettings(const ConnectionSettings &settings, + ProtocolConfig protocolConfig) { - processConfigWithDnsSettings(dns, protocolConfigString); + applyDnsToNativeConfig(settings.dns, protocolConfig); - QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); - QString config = json[config_key::config].toString(); + QString config = protocolConfig.nativeConfig(); - if (!isApiConfig) { + if (!settings.isApiConfig) { QRegularExpression regex("redirect-gateway.*"); config.replace(regex, ""); - // We don't use secondary DNS if primary DNS is AmneziaDNS - if (dns.first.contains(protocols::dns::amneziaDnsIp)) { - QRegularExpression dnsRegex("dhcp-option DNS " + dns.second); + if (settings.dns.primaryDns.contains(protocols::dns::amneziaDnsIp)) { + QRegularExpression dnsRegex("dhcp-option DNS " + settings.dns.secondaryDns); config.replace(dnsRegex, ""); } - if (!m_settings->isSitesSplitTunnelingEnabled()) { + if (!settings.splitTunneling.isSitesSplitTunnelingEnabled) { config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); config.append("block-ipv6\n"); - } else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { - - // no redirect-gateway - } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + } else if (settings.splitTunneling.routeMode == RouteMode::VpnOnlyForwardSites) { + // no redirect-gateway + } else if (settings.splitTunneling.routeMode == RouteMode::VpnAllExceptSites) { #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); - // Prevent ipv6 leak #endif config.append("block-ipv6\n"); } @@ -162,64 +182,57 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPairapplicationDirPath()); - - config.append(dnsConf); + config.append(QString("\nscript-security 2\n" + "up %1/update-resolv-conf.sh\n" + "down %1/update-resolv-conf.sh\n") + .arg(qApp->applicationDirPath())); #endif - json[config_key::config] = config; - return QJsonDocument(json).toJson(); + protocolConfig.setNativeConfig(config); + return protocolConfig; } -QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, - QString &protocolConfigString) +ProtocolConfig OpenVpnConfigurator::processConfigWithExportSettings(const ExportSettings &settings, + ProtocolConfig protocolConfig) { - processConfigWithDnsSettings(dns, protocolConfigString); + applyDnsToNativeConfig(settings.dns, protocolConfig); - QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); - QString config = json[config_key::config].toString(); + QString config = protocolConfig.nativeConfig(); QRegularExpression regex("redirect-gateway.*"); config.replace(regex, ""); - // We don't use secondary DNS if primary DNS is AmneziaDNS - if (dns.first.contains(protocols::dns::amneziaDnsIp)) { - QRegularExpression dnsRegex("dhcp-option DNS " + dns.second); + if (settings.dns.primaryDns.contains(protocols::dns::amneziaDnsIp)) { + QRegularExpression dnsRegex("dhcp-option DNS " + settings.dns.secondaryDns); config.replace(dnsRegex, ""); } config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); - - // Prevent ipv6 leak config.append("block-ipv6\n"); - - // remove block-outside-dns for all exported configs config.replace("block-outside-dns", ""); - json[config_key::config] = config; - return QJsonDocument(json).toJson(); + protocolConfig.setNativeConfig(config); + return protocolConfig; } -ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId) +ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, + const DnsSettings &dnsSettings, QString clientId) { QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && " "easyrsa import-req %2/%3.req %3\"") - .arg(ContainerProps::containerToString(container)) + .arg(ContainerUtils::containerToString(container)) .arg(amnezia::protocols::openvpn::clientsDirPath) .arg(clientId); QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && " "easyrsa sign-req client %2\"") - .arg(ContainerProps::containerToString(container)) + .arg(ContainerUtils::containerToString(container)) .arg(clientId); QStringList scriptList { script_import, script_sign }; - QString script = m_serverController->replaceVars(scriptList.join("\n"), m_serverController->genVarsForScript(credentials, container)); + QString script = m_sshSession->replaceVars(scriptList.join("\n"), amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns)); - return m_serverController->runScript(credentials, script); + return m_sshSession->runScript(credentials, script); } OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() diff --git a/client/core/configurators/openVpnConfigurator.h b/client/core/configurators/openVpnConfigurator.h new file mode 100644 index 000000000..65d481e86 --- /dev/null +++ b/client/core/configurators/openVpnConfigurator.h @@ -0,0 +1,49 @@ +#ifndef OPENVPN_CONFIGURATOR_H +#define OPENVPN_CONFIGURATOR_H + +#include +#include + +#include "configuratorBase.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" + +class OpenVpnConfigurator : public ConfiguratorBase +{ + Q_OBJECT +public: + OpenVpnConfigurator(SshSession* sshSession, QObject *parent = nullptr); + + struct ConnectionData + { + QString clientId; + QString request; // certificate request + QString privKey; // client private key + QString clientCert; // client signed certificate + QString caCert; // server certificate + QString taKey; // tls-auth key + QString host; // host ip + }; + + amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, + const amnezia::ContainerConfig &containerConfig, + const amnezia::DnsSettings &dnsSettings, + amnezia::ErrorCode &errorCode) override; + + amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings, + amnezia::ProtocolConfig protocolConfig) override; + amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings, + amnezia::ProtocolConfig protocolConfig) override; + + static ConnectionData createCertRequest(); + +private: + ConnectionData prepareOpenVpnConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, + const amnezia::DnsSettings &dnsSettings, + amnezia::ErrorCode &errorCode); + amnezia::ErrorCode signCert(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials, + const amnezia::DnsSettings &dnsSettings, QString clientId); +}; + +#endif // OPENVPN_CONFIGURATOR_H diff --git a/client/configurators/wireguard_configurator.cpp b/client/core/configurators/wireguardConfigurator.cpp similarity index 50% rename from client/configurators/wireguard_configurator.cpp rename to client/core/configurators/wireguardConfigurator.cpp index 60da67c65..43a486c15 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/core/configurators/wireguardConfigurator.cpp @@ -1,4 +1,4 @@ -#include "wireguard_configurator.h" +#include "wireguardConfigurator.h" #include #include @@ -13,17 +13,26 @@ #include #include -#include "containers/containers_defs.h" -#include "core/controllers/serverController.h" -#include "core/scripts_registry.h" -#include "core/server_defs.h" -#include "settings.h" -#include "utilities.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/selfhosted/sshSession.h" +#include "core/utils/selfhosted/scriptsRegistry.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/utilities.h" +#include "core/models/containerConfig.h" +#include "core/models/protocols/wireGuardProtocolConfig.h" +#include "core/models/protocols/awgProtocolConfig.h" +#include -WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, - const QSharedPointer &serverController, bool isAwg, +using namespace amnezia; + +WireguardConfigurator::WireguardConfigurator(SshSession* sshSession, bool isAwg, QObject *parent) - : ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg) + : ConfiguratorBase(sshSession, parent), m_isAwg(isAwg) { m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath; @@ -33,8 +42,8 @@ WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath; m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template; - m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard; - m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort; + m_protocolName = m_isAwg ? configKey::awg : configKey::wireguard; + m_defaultPort = m_isAwg ? protocols::awg::defaultPort : protocols::wireguard::defaultPort; } WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() @@ -91,12 +100,21 @@ QList WireguardConfigurator::getIpsFromConf(const QString &input) WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, + const WireGuardServerConfig* serverConfig, + const AwgServerConfig* awgServerConfig, + const DnsSettings &dnsSettings, ErrorCode &errorCode) { WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); connData.host = credentials.hostName; - connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort); + + QString portStr = m_defaultPort; + if (serverConfig && !serverConfig->port.isEmpty()) { + portStr = serverConfig->port; + } else if (awgServerConfig && !awgServerConfig->port.isEmpty()) { + portStr = awgServerConfig->port; + } + connData.port = portStr; if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) { errorCode = ErrorCode::InternalError; @@ -114,7 +132,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon return ErrorCode::NoError; }; - errorCode = m_serverController->runContainerScript(credentials, container, getIpsScript, cbReadStdOut); + errorCode = m_sshSession->runContainerScript(credentials, container, getIpsScript, cbReadStdOut); if (errorCode != ErrorCode::NoError) { return connData; } @@ -123,11 +141,14 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon QHostAddress nextIp = [&] { QHostAddress result; QHostAddress lastIp; + QString subnetAddress = protocols::wireguard::defaultSubnetAddress; + if (serverConfig && !serverConfig->subnetAddress.isEmpty()) { + subnetAddress = serverConfig->subnetAddress; + } else if (awgServerConfig && !awgServerConfig->subnetAddress.isEmpty()) { + subnetAddress = awgServerConfig->subnetAddress; + } if (ips.empty()) { - lastIp.setAddress(containerConfig.value(m_protocolName) - .toObject() - .value(config_key::subnet_address) - .toString(protocols::wireguard::defaultSubnetAddress)); + lastIp.setAddress(subnetAddress); } else { lastIp = ips.last(); } @@ -145,13 +166,13 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon // Get keys connData.serverPubKey = - m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode); + m_sshSession->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode); connData.serverPubKey.replace("\n", ""); if (errorCode != ErrorCode::NoError) { return connData; } - connData.pskKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode); + connData.pskKey = m_sshSession->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode); connData.pskKey.replace("\n", ""); if (errorCode != ErrorCode::NoError) { @@ -165,7 +186,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon "AllowedIPs = %3/32\n\n") .arg(connData.clientPubKey, connData.pskKey, connData.clientIP); - errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, configPath, + errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, configPart, configPath, libssh::ScpOverwriteMode::ScpAppendToExisting); if (errorCode != ErrorCode::NoError) { @@ -178,23 +199,43 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon QString script = QString( "sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'").arg(bin, iface, configPath); - errorCode = m_serverController->runScript( + errorCode = m_sshSession->runScript( credentials, - m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container))); + m_sshSession->replaceVars(script, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns))); return connData; } -QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode) +ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const ContainerConfig &containerConfig, + const DnsSettings &dnsSettings, + ErrorCode &errorCode) { + const WireGuardServerConfig* wireguardServerConfig = nullptr; + const WireGuardClientConfig* wireguardClientConfig = nullptr; + const AwgServerConfig* awgServerConfig = nullptr; + const AwgClientConfig* awgClientConfig = nullptr; + + if (auto* wireGuardProtocolConfig = containerConfig.getWireGuardProtocolConfig()) { + wireguardServerConfig = &wireGuardProtocolConfig->serverConfig; + if (wireGuardProtocolConfig->clientConfig.has_value()) { + wireguardClientConfig = &wireGuardProtocolConfig->clientConfig.value(); + } + } else if (auto* awgProtocolConfig = containerConfig.getAwgProtocolConfig()) { + awgServerConfig = &awgProtocolConfig->serverConfig; + if (awgProtocolConfig->clientConfig.has_value()) { + awgClientConfig = &awgProtocolConfig->clientConfig.value(); + } + } + + amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns); + vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig)); QString scriptData = amnezia::scriptData(m_configTemplate, container); - QString config = m_serverController->replaceVars( - scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig)); + QString config = m_sshSession->replaceVars(scriptData, vars); - ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); + ConnectionData connData = prepareWireguardConfig(credentials, container, wireguardServerConfig, awgServerConfig, dnsSettings, errorCode); if (errorCode != ErrorCode::NoError) { - return ""; + return WireGuardProtocolConfig{}; } config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", connData.clientPrivKey); @@ -202,40 +243,46 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey); config.replace("$WIREGUARD_PSK", connData.pskKey); - const QJsonObject &wireguarConfig = containerConfig.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject(); - QJsonObject jConfig; - jConfig[config_key::config] = config; - - jConfig[config_key::hostName] = connData.host; - jConfig[config_key::port] = connData.port.toInt(); - jConfig[config_key::client_priv_key] = connData.clientPrivKey; - jConfig[config_key::client_ip] = connData.clientIP; - jConfig[config_key::client_pub_key] = connData.clientPubKey; - jConfig[config_key::psk_key] = connData.pskKey; - jConfig[config_key::server_pub_key] = connData.serverPubKey; - jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu); - - jConfig[config_key::persistent_keep_alive] = "25"; - QJsonArray allowedIps { "0.0.0.0/0", "::/0" }; - jConfig[config_key::allowed_ips] = allowedIps; - - jConfig[config_key::clientId] = connData.clientPubKey; - - return QJsonDocument(jConfig).toJson(); + QString mtu = protocols::wireguard::defaultMtu; + if (wireguardClientConfig && !wireguardClientConfig->mtu.isEmpty()) { + mtu = wireguardClientConfig->mtu; + } else if (awgClientConfig && !awgClientConfig->mtu.isEmpty()) { + mtu = awgClientConfig->mtu; + } + + WireGuardProtocolConfig protocolConfig; + if (wireguardServerConfig) { + protocolConfig.serverConfig = *wireguardServerConfig; + } + + WireGuardClientConfig clientConfig; + clientConfig.nativeConfig = config; + clientConfig.hostName = connData.host; + clientConfig.port = connData.port.toInt(); + clientConfig.clientIp = connData.clientIP; + clientConfig.clientPrivateKey = connData.clientPrivKey; + clientConfig.clientPublicKey = connData.clientPubKey; + clientConfig.serverPublicKey = connData.serverPubKey; + clientConfig.presharedKey = connData.pskKey; + clientConfig.clientId = connData.clientPubKey; + clientConfig.allowedIps = QStringList { "0.0.0.0/0", "::/0" }; + clientConfig.persistentKeepAlive = "25"; + clientConfig.mtu = mtu; + clientConfig.isObfuscationEnabled = false; + + protocolConfig.setClientConfig(clientConfig); + + return protocolConfig; } -QString WireguardConfigurator::processConfigWithLocalSettings(const QPair &dns, - const bool isApiConfig, QString &protocolConfigString) +ProtocolConfig WireguardConfigurator::processConfigWithLocalSettings(const ConnectionSettings &settings, + ProtocolConfig protocolConfig) { - processConfigWithDnsSettings(dns, protocolConfigString); - - return protocolConfigString; + return ConfiguratorBase::processConfigWithLocalSettings(settings, protocolConfig); } -QString WireguardConfigurator::processConfigWithExportSettings(const QPair &dns, - const bool isApiConfig, QString &protocolConfigString) +ProtocolConfig WireguardConfigurator::processConfigWithExportSettings(const ExportSettings &settings, + ProtocolConfig protocolConfig) { - processConfigWithDnsSettings(dns, protocolConfigString); - - return protocolConfigString; + return ConfiguratorBase::processConfigWithExportSettings(settings, protocolConfig); } diff --git a/client/core/configurators/wireguardConfigurator.h b/client/core/configurators/wireguardConfigurator.h new file mode 100644 index 000000000..99e1990d3 --- /dev/null +++ b/client/core/configurators/wireguardConfigurator.h @@ -0,0 +1,61 @@ +#ifndef WIREGUARD_CONFIGURATOR_H +#define WIREGUARD_CONFIGURATOR_H + +#include +#include +#include + +#include "configuratorBase.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/utils/selfhosted/scriptsRegistry.h" + +class WireguardConfigurator : public ConfiguratorBase +{ + Q_OBJECT +public: + WireguardConfigurator(SshSession* sshSession, + bool isAwg, QObject *parent = nullptr); + + struct ConnectionData + { + QString clientPrivKey; // client private key + QString clientPubKey; // client public key + QString clientIP; // internal client IP address + QString serverPubKey; // tls-auth key + QString pskKey; // preshared key + QString host; // host ip + QString port; + }; + + amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, + const amnezia::ContainerConfig &containerConfig, + const amnezia::DnsSettings &dnsSettings, + amnezia::ErrorCode &errorCode) override; + + amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings, + amnezia::ProtocolConfig protocolConfig) override; + amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings, + amnezia::ProtocolConfig protocolConfig) override; + + static ConnectionData genClientKeys(); + +private: + QList getIpsFromConf(const QString &input); + ConnectionData prepareWireguardConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, + const amnezia::WireGuardServerConfig* serverConfig, + const amnezia::AwgServerConfig* awgServerConfig, + const amnezia::DnsSettings &dnsSettings, + amnezia::ErrorCode &errorCode); + + bool m_isAwg; + QString m_serverConfigPath; + QString m_serverPublicKeyPath; + QString m_serverPskKeyPath; + amnezia::ProtocolScriptType m_configTemplate; + QString m_protocolName; + QString m_defaultPort; +}; + +#endif // WIREGUARD_CONFIGURATOR_H diff --git a/client/configurators/xray_configurator.cpp b/client/core/configurators/xrayConfigurator.cpp similarity index 52% rename from client/configurators/xray_configurator.cpp rename to client/core/configurators/xrayConfigurator.cpp index 514aa8211..b525c8991 100644 --- a/client/configurators/xray_configurator.cpp +++ b/client/core/configurators/xrayConfigurator.cpp @@ -1,32 +1,43 @@ -#include "xray_configurator.h" +#include "xrayConfigurator.h" #include #include #include +#include #include #include "logger.h" -#include "containers/containers_defs.h" -#include "core/controllers/serverController.h" -#include "core/scripts_registry.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/selfhosted/sshSession.h" +#include "core/utils/selfhosted/scriptsRegistry.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/models/containerConfig.h" +#include "core/models/protocols/xrayProtocolConfig.h" namespace { Logger logger("XrayConfigurator"); } -XrayConfigurator::XrayConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent) - : ConfiguratorBase(settings, serverController, parent) +XrayConfigurator::XrayConfigurator(SshSession* sshSession, QObject *parent) + : ConfiguratorBase(sshSession, parent) { } QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode) + const ContainerConfig &containerConfig, + const DnsSettings &dnsSettings, + ErrorCode &errorCode) { // Generate new UUID for client QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces); // Get current server config - QString currentConfig = m_serverController->getTextFileFromContainer( + QString currentConfig = m_sshSession->getTextFileFromContainer( container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode); if (errorCode != ErrorCode::NoError) { @@ -45,13 +56,13 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia QJsonObject serverConfig = doc.object(); // Validate server config structure - if (!serverConfig.contains("inbounds")) { + if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) { logger.error() << "Server config missing 'inbounds' field"; errorCode = ErrorCode::InternalError; return ""; } - QJsonArray inbounds = serverConfig["inbounds"].toArray(); + QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray(); if (inbounds.isEmpty()) { logger.error() << "Server config has empty 'inbounds' array"; errorCode = ErrorCode::InternalError; @@ -59,38 +70,38 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia } QJsonObject inbound = inbounds[0].toObject(); - if (!inbound.contains("settings")) { + if (!inbound.contains(amnezia::protocols::xray::settings)) { logger.error() << "Inbound missing 'settings' field"; errorCode = ErrorCode::InternalError; return ""; } - QJsonObject settings = inbound["settings"].toObject(); - if (!settings.contains("clients")) { + 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 ""; } - QJsonArray clients = settings["clients"].toArray(); + QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray(); // Create configuration for new client QJsonObject clientConfig { - {"id", clientId}, - {"flow", "xtls-rprx-vision"} + {amnezia::protocols::xray::id, clientId}, + {amnezia::protocols::xray::flow, "xtls-rprx-vision"} }; clients.append(clientConfig); // Update config - settings["clients"] = clients; - inbound["settings"] = settings; + settings[amnezia::protocols::xray::clients] = clients; + inbound[amnezia::protocols::xray::settings] = settings; inbounds[0] = inbound; - serverConfig["inbounds"] = inbounds; + serverConfig[amnezia::protocols::xray::inbounds] = inbounds; // Save updated config to server QString updatedConfig = QJsonDocument(serverConfig).toJson(); - errorCode = m_serverController->uploadTextFileToContainer( + errorCode = m_sshSession->uploadTextFileToContainer( container, credentials, updatedConfig, @@ -104,9 +115,9 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia // Restart container QString restartScript = QString("sudo docker restart $CONTAINER_NAME"); - errorCode = m_serverController->runScript( + errorCode = m_sshSession->runScript( credentials, - m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container)) + m_sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns)) ); if (errorCode != ErrorCode::NoError) { @@ -117,57 +128,75 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia return clientId; } -QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode) +ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const ContainerConfig &containerConfig, + const DnsSettings &dnsSettings, + ErrorCode &errorCode) { - // Get client ID from prepareServerConfig - QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode); + const XrayServerConfig* serverConfig = nullptr; + if (auto* xrayConfig = containerConfig.protocolConfig.as()) { + serverConfig = &xrayConfig->serverConfig; + } + + QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, dnsSettings, errorCode); if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) { logger.error() << "Failed to prepare server config"; errorCode = ErrorCode::InternalError; - return ""; + return XrayProtocolConfig{}; } - QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), - m_serverController->genVarsForScript(credentials, container, containerConfig)); + amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns); + vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig)); + QString config = m_sshSession->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), vars); if (config.isEmpty()) { logger.error() << "Failed to get config template"; errorCode = ErrorCode::InternalError; - return ""; + return XrayProtocolConfig{}; } QString xrayPublicKey = - m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode); + m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode); if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) { logger.error() << "Failed to get public key"; errorCode = ErrorCode::InternalError; - return ""; + return XrayProtocolConfig{}; } xrayPublicKey.replace("\n", ""); QString xrayShortId = - m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode); + m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode); if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) { logger.error() << "Failed to get short ID"; errorCode = ErrorCode::InternalError; - return ""; + return XrayProtocolConfig{}; } xrayShortId.replace("\n", ""); - // Validate all required variables are present if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) { logger.error() << "Config template missing required variables:" << "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID") << "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY") << "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID"); errorCode = ErrorCode::InternalError; - return ""; + return XrayProtocolConfig{}; } config.replace("$XRAY_CLIENT_ID", xrayClientId); config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey); config.replace("$XRAY_SHORT_ID", xrayShortId); - return config; + XrayProtocolConfig protocolConfig; + if (serverConfig) { + protocolConfig.serverConfig = *serverConfig; + } + + XrayClientConfig clientConfig; + clientConfig.nativeConfig = config; + clientConfig.localPort = ""; + clientConfig.id = xrayClientId; + + protocolConfig.setClientConfig(clientConfig); + + return protocolConfig; } diff --git a/client/core/configurators/xrayConfigurator.h b/client/core/configurators/xrayConfigurator.h new file mode 100644 index 000000000..74a0ea006 --- /dev/null +++ b/client/core/configurators/xrayConfigurator.h @@ -0,0 +1,27 @@ +#ifndef XRAY_CONFIGURATOR_H +#define XRAY_CONFIGURATOR_H + +#include + +#include "configuratorBase.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" + +class XrayConfigurator : public ConfiguratorBase +{ + Q_OBJECT +public: + XrayConfigurator(SshSession* sshSession, QObject *parent = nullptr); + + amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig, + const amnezia::DnsSettings &dnsSettings, + amnezia::ErrorCode &errorCode) override; + +private: + QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig, + const amnezia::DnsSettings &dnsSettings, + amnezia::ErrorCode &errorCode); +}; + +#endif // XRAY_CONFIGURATOR_H diff --git a/client/core/controllers/allowedDnsController.cpp b/client/core/controllers/allowedDnsController.cpp new file mode 100644 index 000000000..8cf1ba410 --- /dev/null +++ b/client/core/controllers/allowedDnsController.cpp @@ -0,0 +1,54 @@ +#include "allowedDnsController.h" + +AllowedDnsController::AllowedDnsController(SecureAppSettingsRepository* appSettingsRepository) + : m_appSettingsRepository(appSettingsRepository) +{ + fillDnsServers(); +} + +bool AllowedDnsController::addDns(const QString &ip) +{ + if (m_dnsServers.contains(ip)) { + return false; + } + + m_dnsServers.append(ip); + m_appSettingsRepository->setAllowedDnsServers(m_dnsServers); + return true; +} + +void AllowedDnsController::addDnsList(const QStringList &dnsServers, bool replaceExisting) +{ + if (replaceExisting) { + m_dnsServers.clear(); + } + + for (const QString &ip : dnsServers) { + if (!m_dnsServers.contains(ip)) { + m_dnsServers.append(ip); + } + } + + m_appSettingsRepository->setAllowedDnsServers(m_dnsServers); +} + +void AllowedDnsController::removeDns(int index) +{ + if (index < 0 || index >= m_dnsServers.size()) { + return; + } + + m_dnsServers.removeAt(index); + m_appSettingsRepository->setAllowedDnsServers(m_dnsServers); +} + +QStringList AllowedDnsController::getCurrentDnsServers() const +{ + return m_dnsServers; +} + +void AllowedDnsController::fillDnsServers() +{ + m_dnsServers = m_appSettingsRepository->getAllowedDnsServers(); +} + diff --git a/client/core/controllers/allowedDnsController.h b/client/core/controllers/allowedDnsController.h new file mode 100644 index 000000000..4d1416bb1 --- /dev/null +++ b/client/core/controllers/allowedDnsController.h @@ -0,0 +1,26 @@ +#ifndef ALLOWEDDNSCONTROLLER_H +#define ALLOWEDDNSCONTROLLER_H + +#include + +#include "core/repositories/secureAppSettingsRepository.h" + +class AllowedDnsController +{ +public: + explicit AllowedDnsController(SecureAppSettingsRepository* appSettingsRepository); + + bool addDns(const QString &ip); + void addDnsList(const QStringList &dnsServers, bool replaceExisting); + void removeDns(int index); + QStringList getCurrentDnsServers() const; + +private: + void fillDnsServers(); + + SecureAppSettingsRepository* m_appSettingsRepository; + QStringList m_dnsServers; +}; + +#endif // ALLOWEDDNSCONTROLLER_H + diff --git a/client/core/controllers/api/newsController.cpp b/client/core/controllers/api/newsController.cpp new file mode 100644 index 000000000..abc045947 --- /dev/null +++ b/client/core/controllers/api/newsController.cpp @@ -0,0 +1,72 @@ +#include "newsController.h" + +#include "core/controllers/gatewayController.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" +#include "core/utils/constants/configKeys.h" +#include +#include +#include +#include + +using namespace amnezia; + +NewsController::NewsController(SecureAppSettingsRepository* appSettingsRepository, + ServersController* serversController) + : m_appSettingsRepository(appSettingsRepository), m_serversController(serversController) +{ +} + +QFuture> NewsController::fetchNews() +{ + if (!m_serversController) { + qWarning() << "ServersController is null, skip fetchNews"; + return QtFuture::makeReadyFuture(qMakePair(ErrorCode::InternalError, QJsonArray())); + } + + const auto stacks = m_serversController->gatewayStacks(); + if (stacks.isEmpty()) { + qDebug() << "No Gateway stacks, skip fetchNews"; + return QtFuture::makeReadyFuture(qMakePair(ErrorCode::NoError, QJsonArray())); + } + + auto gatewayController = QSharedPointer::create( + m_appSettingsRepository->getGatewayEndpoint(), + m_appSettingsRepository->isDevGatewayEnv(), + apiDefs::requestTimeoutMsecs, + m_appSettingsRepository->isStrictKillSwitchEnabled()); + + QJsonObject payload; + payload.insert("locale", m_appSettingsRepository->getAppLanguage().name().split("_").first()); + + const QJsonObject stacksJson = stacks.toJson(); + if (stacksJson.contains(apiDefs::key::userCountryCode)) { + payload.insert(apiDefs::key::userCountryCode, stacksJson.value(apiDefs::key::userCountryCode)); + } + if (stacksJson.contains(apiDefs::key::serviceType)) { + payload.insert(apiDefs::key::serviceType, stacksJson.value(apiDefs::key::serviceType)); + } + + auto future = gatewayController->postAsync(QString("%1v1/news"), payload); + return future.then([gatewayController](QPair result) -> QPair { + auto [errorCode, responseBody] = result; + if (errorCode != ErrorCode::NoError) { + return qMakePair(errorCode, QJsonArray()); + } + + QJsonDocument doc = QJsonDocument::fromJson(responseBody); + QJsonArray newsArray; + if (doc.isArray()) { + newsArray = doc.array(); + } else if (doc.isObject()) { + QJsonObject obj = doc.object(); + if (obj.value("news").isArray()) { + newsArray = obj.value("news").toArray(); + } + } + + return qMakePair(ErrorCode::NoError, newsArray); + }); +} + diff --git a/client/core/controllers/api/newsController.h b/client/core/controllers/api/newsController.h new file mode 100644 index 000000000..15ffd67b1 --- /dev/null +++ b/client/core/controllers/api/newsController.h @@ -0,0 +1,28 @@ +#ifndef NEWSCONTROLLER_H +#define NEWSCONTROLLER_H + +#include +#include +#include + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/repositories/secureAppSettingsRepository.h" +#include "core/controllers/serversController.h" + +class NewsController +{ +public: + explicit NewsController(SecureAppSettingsRepository* appSettingsRepository, + ServersController* serversController); + + QFuture> fetchNews(); + +private: + SecureAppSettingsRepository* m_appSettingsRepository; + ServersController* m_serversController; +}; + +#endif // NEWSCONTROLLER_H + diff --git a/client/core/controllers/api/servicesCatalogController.cpp b/client/core/controllers/api/servicesCatalogController.cpp new file mode 100644 index 000000000..afdcfac6d --- /dev/null +++ b/client/core/controllers/api/servicesCatalogController.cpp @@ -0,0 +1,248 @@ +#include "servicesCatalogController.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/controllers/gatewayController.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" +#include "version.h" + +#if defined(Q_OS_IOS) || defined(MACOS_NE) +#include "platforms/ios/ios_controller.h" +#endif + +namespace +{ + namespace configKey + { + constexpr char serviceDescription[] = "service_description"; + constexpr char subscriptionPlans[] = "subscription_plans"; + constexpr char storeProductId[] = "store_product_id"; + constexpr char priceLabel[] = "price_label"; + constexpr char subtitle[] = "subtitle"; + constexpr char isTrial[] = "is_trial"; + constexpr char minPriceLabel[] = "min_price_label"; + } + + namespace serviceType + { + constexpr char amneziaPremium[] = "amnezia-premium"; + } + +#if defined(Q_OS_IOS) || defined(MACOS_NE) + struct StoreKitPlanQuote { + QString displayPrice; + double priceAmount = 0.0; + double subscriptionBillingMonths = 0.0; + QString displayPricePerMonth; + }; + + constexpr double oneMonthThreshold = 1.0 + 1e-6; + constexpr double monthsFallbackThreshold = 1e-6; + constexpr double monthlyPriceEpsilon = 1e-9; + + QStringList collectPremiumStoreProductIds(const QJsonArray &services) + { + QStringList productIds; + QSet seenProductIds; + for (const QJsonValue &serviceValue : services) { + const QJsonObject serviceObject = serviceValue.toObject(); + if (serviceObject.value(apiDefs::key::serviceType).toString() != serviceType::amneziaPremium) { + continue; + } + const QJsonArray subscriptionPlans = + serviceObject.value(configKey::serviceDescription).toObject().value(configKey::subscriptionPlans).toArray(); + for (const QJsonValue &planValue : subscriptionPlans) { + if (!planValue.isObject()) { + continue; + } + const QString storeProductId = planValue.toObject().value(configKey::storeProductId).toString(); + if (storeProductId.isEmpty() || seenProductIds.contains(storeProductId)) { + continue; + } + seenProductIds.insert(storeProductId); + productIds.append(storeProductId); + } + } + return productIds; + } + + QHash buildStoreKitQuoteMap(const QList &fetchedProducts) + { + QHash quotesByProductId; + quotesByProductId.reserve(fetchedProducts.size()); + + for (const QVariantMap &productInfo : fetchedProducts) { + const QString productId = productInfo.value(QStringLiteral("productId")).toString(); + if (productId.isEmpty()) { + continue; + } + + QString displayPrice = productInfo.value(QStringLiteral("displayPrice")).toString(); + if (displayPrice.isEmpty()) { + const QString price = productInfo.value(QStringLiteral("price")).toString(); + const QString currencyCode = productInfo.value(QStringLiteral("currencyCode")).toString(); + displayPrice = currencyCode.isEmpty() ? price : (price + QLatin1Char(' ') + currencyCode); + } + + StoreKitPlanQuote quote; + quote.displayPrice = displayPrice; + quote.priceAmount = productInfo.value(QStringLiteral("priceAmount")).toDouble(); + quote.subscriptionBillingMonths = productInfo.value(QStringLiteral("subscriptionBillingMonths")).toDouble(); + quote.displayPricePerMonth = productInfo.value(QStringLiteral("displayPricePerMonth")).toString(); + quotesByProductId.insert(productId, quote); + } + + return quotesByProductId; + } + + void mergeStoreKitPricesIntoPremiumPlans(QJsonObject &data) + { + QJsonArray services = data.value(apiDefs::key::services).toArray(); + if (services.isEmpty()) { + return; + } + + const QStringList productIds = collectPremiumStoreProductIds(services); + if (productIds.isEmpty()) { + qInfo().noquote() << "[IAP] No store_product_id in premium plans; skip StoreKit merge into services payload"; + return; + } + + QList fetchedProducts; + QEventLoop loop; + IosController::Instance()->fetchProducts(productIds, + [&](const QList &products, const QStringList &invalidIds, + const QString &errorString) { + if (!errorString.isEmpty()) { + qWarning().noquote() << "[IAP] StoreKit merge fetch:" << errorString; + } + if (!invalidIds.isEmpty()) { + qWarning().noquote() << "[IAP] Unknown App Store product ids:" << invalidIds; + } + fetchedProducts = products; + loop.quit(); + }); + loop.exec(); + + const QHash quotesByProductId = buildStoreKitQuoteMap(fetchedProducts); + + for (int serviceIndex = 0; serviceIndex < services.size(); ++serviceIndex) { + QJsonObject serviceObject = services.at(serviceIndex).toObject(); + if (serviceObject.value(apiDefs::key::serviceType).toString() != serviceType::amneziaPremium) { + continue; + } + + QJsonObject descriptionObject = serviceObject.value(configKey::serviceDescription).toObject(); + const QJsonArray sourcePlans = descriptionObject.value(configKey::subscriptionPlans).toArray(); + + QJsonArray mergedPlans; + double minMonthlyAmount = std::numeric_limits::infinity(); + QString minMonthlyDisplay; + + for (const QJsonValue &planValue : sourcePlans) { + if (!planValue.isObject()) { + continue; + } + + QJsonObject planObject = planValue.toObject(); + const QString storeProductId = planObject.value(configKey::storeProductId).toString(); + if (storeProductId.isEmpty()) { + continue; + } + + const auto quoteIterator = quotesByProductId.constFind(storeProductId); + if (quoteIterator == quotesByProductId.cend()) { + continue; + } + + const bool isTrialPlan = planObject.value(configKey::isTrial).toBool(); + const StoreKitPlanQuote "e = *quoteIterator; + planObject.insert(configKey::priceLabel, quote.displayPrice); + + const double months = quote.subscriptionBillingMonths; + if (!isTrialPlan && months > oneMonthThreshold && !quote.displayPricePerMonth.isEmpty()) { + planObject.insert( + configKey::subtitle, + QCoreApplication::translate("ServicesCatalogController", "%1/mo", + "IAP: price per month in plan subtitle") + .arg(quote.displayPricePerMonth)); + } + + if (!isTrialPlan && quote.priceAmount > 0.0) { + const double monthsForMin = months > monthsFallbackThreshold ? months : 1.0; + const double monthly = quote.priceAmount / monthsForMin; + if (monthly < minMonthlyAmount - monthlyPriceEpsilon) { + minMonthlyAmount = monthly; + minMonthlyDisplay = !quote.displayPricePerMonth.isEmpty() ? quote.displayPricePerMonth : quote.displayPrice; + } + } + + mergedPlans.append(planObject); + } + + descriptionObject.insert(configKey::subscriptionPlans, mergedPlans); + if (minMonthlyAmount < std::numeric_limits::infinity() && !minMonthlyDisplay.isEmpty()) { + descriptionObject.insert(configKey::minPriceLabel, + QCoreApplication::translate("ServicesCatalogController", "from %1 per month", + "IAP: card footer minimum monthly price from StoreKit") + .arg(minMonthlyDisplay)); + } + serviceObject.insert(configKey::serviceDescription, descriptionObject); + services.replace(serviceIndex, serviceObject); + } + data.insert(apiDefs::key::services, services); + } +#endif +} + +ServicesCatalogController::ServicesCatalogController(SecureAppSettingsRepository* appSettingsRepository) + : m_appSettingsRepository(appSettingsRepository) +{ +} + +ErrorCode ServicesCatalogController::fillAvailableServices(QJsonObject &servicesData) +{ + QJsonObject apiPayload; + apiPayload[apiDefs::key::osVersion] = QSysInfo::productType(); + apiPayload[apiDefs::key::appVersion] = QString(APP_VERSION); + apiPayload[apiDefs::key::cliName] = QString(APPLICATION_NAME); + apiPayload[apiDefs::key::appLanguage] = m_appSettingsRepository->getAppLanguage().name().split("_").first(); + + QByteArray responseBody; + ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody); + if (errorCode == ErrorCode::NoError) { + if (!responseBody.contains(apiDefs::key::services.data())) { + errorCode = ErrorCode::ApiServicesMissingError; + } + } + + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + servicesData = QJsonDocument::fromJson(responseBody).object(); + +#if defined(Q_OS_IOS) || defined(MACOS_NE) + mergeStoreKitPricesIntoPremiumPlans(servicesData); +#endif + + return ErrorCode::NoError; +} + +ErrorCode ServicesCatalogController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody) +{ + GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_appSettingsRepository->isStrictKillSwitchEnabled()); + return gatewayController.post(endpoint, apiPayload, responseBody); +} + diff --git a/client/core/controllers/api/servicesCatalogController.h b/client/core/controllers/api/servicesCatalogController.h new file mode 100644 index 000000000..909cef072 --- /dev/null +++ b/client/core/controllers/api/servicesCatalogController.h @@ -0,0 +1,26 @@ +#ifndef SERVICESCATALOGCONTROLLER_H +#define SERVICESCATALOGCONTROLLER_H + +#include +#include + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/repositories/secureAppSettingsRepository.h" + +class ServicesCatalogController +{ +public: + explicit ServicesCatalogController(SecureAppSettingsRepository* appSettingsRepository); + + ErrorCode fillAvailableServices(QJsonObject &servicesData); + +private: + ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody); + + SecureAppSettingsRepository* m_appSettingsRepository; +}; + +#endif // SERVICESCATALOGCONTROLLER_H + diff --git a/client/core/controllers/api/subscriptionController.cpp b/client/core/controllers/api/subscriptionController.cpp new file mode 100644 index 000000000..e71abbcaf --- /dev/null +++ b/client/core/controllers/api/subscriptionController.cpp @@ -0,0 +1,1094 @@ +#include "subscriptionController.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/configurators/openVpnConfigurator.h" +#include "core/configurators/wireguardConfigurator.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" +#include "core/utils/api/apiUtils.h" +#include "core/controllers/gatewayController.h" +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" +#include "version.h" +#include "core/models/serverConfig.h" +#include "core/models/containerConfig.h" +#include "core/models/api/apiConfig.h" + +#if defined(Q_OS_IOS) || defined(MACOS_NE) + #include "platforms/ios/ios_controller.h" + #include +#endif + +using namespace amnezia; + +namespace +{ +QString getSubscriptionStatusForRenewal(const ApiConfig &apiConfig) +{ + if (apiConfig.subscriptionExpiredByServer) { + return QStringLiteral("expired"); + } + + if (!apiConfig.subscription.endDate.isEmpty()) { + if (apiUtils::isSubscriptionExpired(apiConfig.subscription.endDate)) { + return QStringLiteral("expired"); + } + if (apiUtils::isSubscriptionExpiringSoon(apiConfig.subscription.endDate)) { + return QStringLiteral("expire_soon"); + } + } + + return QStringLiteral("active"); +} +} + + +SubscriptionController::SubscriptionController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository) + : m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository) +{ +} + +QJsonObject SubscriptionController::GatewayRequestData::toJsonObject() const +{ + QJsonObject obj; + if (!osVersion.isEmpty()) { + obj[apiDefs::key::osVersion] = osVersion; + } + if (!appVersion.isEmpty()) { + obj[apiDefs::key::appVersion] = appVersion; + } + if (!appLanguage.isEmpty()) { + obj[apiDefs::key::appLanguage] = appLanguage; + } + if (!installationUuid.isEmpty()) { + obj[apiDefs::key::uuid] = installationUuid; + } + if (!userCountryCode.isEmpty()) { + obj[apiDefs::key::userCountryCode] = userCountryCode; + } + if (!serverCountryCode.isEmpty()) { + obj[apiDefs::key::serverCountryCode] = serverCountryCode; + } + if (!serviceType.isEmpty()) { + obj[apiDefs::key::serviceType] = serviceType; + } + if (!serviceProtocol.isEmpty()) { + obj[apiDefs::key::serviceProtocol] = serviceProtocol; + } + if (!authData.isEmpty()) { + obj[apiDefs::key::authData] = authData; + } + return obj; +} + +SubscriptionController::ProtocolData SubscriptionController::generateProtocolData(const QString &protocol) +{ + ProtocolData protocolData; + if (protocol == configKey::awg) { + auto connData = WireguardConfigurator::genClientKeys(); + protocolData.wireGuardClientPubKey = connData.clientPubKey; + protocolData.wireGuardClientPrivKey = connData.clientPrivKey; + } else if (protocol == configKey::vless) { + protocolData.xrayUuid = QUuid::createUuid().toString(QUuid::WithoutBraces); + } + + return protocolData; +} + +void SubscriptionController::appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload) +{ + if (protocol == configKey::awg) { + apiPayload[apiDefs::key::publicKey] = protocolData.wireGuardClientPubKey; + } else if (protocol == configKey::vless) { + apiPayload[apiDefs::key::publicKey] = protocolData.xrayUuid; + } +} + +ErrorCode SubscriptionController::extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol, + const ProtocolData &protocolData, QJsonObject &serverConfigJson) +{ + QString data = QJsonDocument::fromJson(apiResponseBody).object().value(configKey::config).toString(); + + data.replace("vpn://", ""); + QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + if (ba.isEmpty()) { + qDebug() << "empty vpn key"; + return ErrorCode::ApiConfigEmptyError; + } + + QByteArray ba_uncompressed = qUncompress(ba); + if (!ba_uncompressed.isEmpty()) { + ba = ba_uncompressed; + } + + QString configStr = ba; + if (protocol == configKey::awg) { + configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", protocolData.wireGuardClientPrivKey); + auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + auto containers = newServerConfig.value(configKey::containers).toArray(); + if (containers.isEmpty()) { + qDebug() << "missing containers field"; + return ErrorCode::ApiConfigEmptyError; + } + auto container = containers.at(0).toObject(); + QString containerName = ContainerUtils::containerTypeToString(DockerContainer::Awg); + auto serverProtocolConfig = container.value(containerName).toObject(); + auto clientProtocolConfig = + QJsonDocument::fromJson(serverProtocolConfig.value(configKey::lastConfig).toString().toUtf8()).object(); + + // TODO looks like this block can be removed after v1 configs EOL + + serverProtocolConfig[configKey::junkPacketCount] = clientProtocolConfig.value(configKey::junkPacketCount); + serverProtocolConfig[configKey::junkPacketMinSize] = clientProtocolConfig.value(configKey::junkPacketMinSize); + serverProtocolConfig[configKey::junkPacketMaxSize] = clientProtocolConfig.value(configKey::junkPacketMaxSize); + serverProtocolConfig[configKey::initPacketJunkSize] = clientProtocolConfig.value(configKey::initPacketJunkSize); + serverProtocolConfig[configKey::responsePacketJunkSize] = clientProtocolConfig.value(configKey::responsePacketJunkSize); + serverProtocolConfig[configKey::initPacketMagicHeader] = clientProtocolConfig.value(configKey::initPacketMagicHeader); + serverProtocolConfig[configKey::responsePacketMagicHeader] = clientProtocolConfig.value(configKey::responsePacketMagicHeader); + serverProtocolConfig[configKey::underloadPacketMagicHeader] = clientProtocolConfig.value(configKey::underloadPacketMagicHeader); + serverProtocolConfig[configKey::transportPacketMagicHeader] = clientProtocolConfig.value(configKey::transportPacketMagicHeader); + + serverProtocolConfig[configKey::cookieReplyPacketJunkSize] = clientProtocolConfig.value(configKey::cookieReplyPacketJunkSize); + serverProtocolConfig[configKey::transportPacketJunkSize] = clientProtocolConfig.value(configKey::transportPacketJunkSize); + serverProtocolConfig[configKey::specialJunk1] = clientProtocolConfig.value(configKey::specialJunk1); + serverProtocolConfig[configKey::specialJunk2] = clientProtocolConfig.value(configKey::specialJunk2); + serverProtocolConfig[configKey::specialJunk3] = clientProtocolConfig.value(configKey::specialJunk3); + serverProtocolConfig[configKey::specialJunk4] = clientProtocolConfig.value(configKey::specialJunk4); + serverProtocolConfig[configKey::specialJunk5] = clientProtocolConfig.value(configKey::specialJunk5); + + // + + container[containerName] = serverProtocolConfig; + containers.replace(0, container); + newServerConfig[configKey::containers] = containers; + configStr = QString(QJsonDocument(newServerConfig).toJson()); + } + + serverConfigJson = QJsonDocument::fromJson(configStr.toUtf8()).object(); + return ErrorCode::NoError; +} + +void SubscriptionController::updateApiConfigInJson(QJsonObject &serverConfigJson, const QString &serviceType, + const QString &serviceProtocol, const QString &userCountryCode, + const QByteArray &apiResponseBody) +{ + QJsonObject apiConfig = serverConfigJson.value(apiDefs::key::apiConfig).toObject(); + + apiConfig[apiDefs::key::serviceType] = serviceType; + apiConfig[apiDefs::key::serviceProtocol] = serviceProtocol; + apiConfig[apiDefs::key::userCountryCode] = userCountryCode; + + if (serverConfigJson.value(configKey::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { + QJsonObject responseObj = QJsonDocument::fromJson(apiResponseBody).object(); + if (responseObj.contains(apiDefs::key::supportedProtocols)) { + apiConfig.insert(apiDefs::key::supportedProtocols, responseObj.value(apiDefs::key::supportedProtocols).toArray()); + } + if (responseObj.contains(apiDefs::key::serviceInfo)) { + apiConfig.insert(apiDefs::key::serviceInfo, responseObj.value(apiDefs::key::serviceInfo).toObject()); + } + } + + serverConfigJson[apiDefs::key::apiConfig] = apiConfig; +} + +ErrorCode SubscriptionController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase) +{ + GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase), m_appSettingsRepository->isDevGatewayEnv(isTestPurchase), apiDefs::requestTimeoutMsecs, + m_appSettingsRepository->isStrictKillSwitchEnabled()); + return gatewayController.post(endpoint, apiPayload, responseBody); +} + +ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCountryCode, const QString &serviceType, + const QString &serviceProtocol, const ProtocolData &protocolData, + ServerConfig &serverConfig) +{ + 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); + + QByteArray responseBody; + ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + QJsonObject serverConfigJson; + errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody); + + ServerConfig serverConfigModel = ServerConfig::fromJson(serverConfigJson); + + if (!serverConfigModel.isApiV2()) { + return ErrorCode::InternalError; + } + + m_serversRepository->addServer(serverConfigModel); + serverConfig = serverConfigModel; + return ErrorCode::NoError; +} + +ErrorCode SubscriptionController::importTrialFromGateway(const QString &userCountryCode, const QString &serviceType, + const QString &serviceProtocol, const QString &email, + ServerConfig &serverConfig) +{ + const QString trimmedEmail = email.trimmed(); + if (trimmedEmail.isEmpty()) { + return ErrorCode::ApiConfigEmptyError; + } + + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_appSettingsRepository->getAppLanguage().name().split("_").first(), + m_appSettingsRepository->getInstallationUuid(true), + userCountryCode, + "", + serviceType, + serviceProtocol, + QJsonObject() }; + + ProtocolData protocolData = generateProtocolData(serviceProtocol); + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload); + apiPayload.insert(apiDefs::key::email, trimmedEmail); + + QByteArray responseBody; + ErrorCode errorCode = executeRequest(QString("%1v1/trial"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + QJsonObject responseObject = QJsonDocument::fromJson(responseBody).object(); + QString key = responseObject.value(apiDefs::key::config).toString(); + if (key.isEmpty()) { + return ErrorCode::ApiConfigEmptyError; + } + + key.replace(QStringLiteral("vpn://"), QString()); + QByteArray configBytes = QByteArray::fromBase64(key.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QByteArray uncompressed = qUncompress(configBytes); + if (!uncompressed.isEmpty()) { + configBytes = uncompressed; + } + + if (configBytes.isEmpty()) { + return ErrorCode::ApiConfigEmptyError; + } + + QJsonObject configObject = QJsonDocument::fromJson(configBytes).object(); + ServerConfig serverConfigModel = ServerConfig::fromJson(configObject); + m_serversRepository->addServer(serverConfigModel); + serverConfig = serverConfigModel; + return ErrorCode::NoError; +} + +ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userCountryCode, const QString &serviceType, + const QString &serviceProtocol, const ProtocolData &protocolData, + const QString &transactionId, bool isTestPurchase, + ServerConfig &serverConfig, + int *duplicateServerIndex) +{ + 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[apiDefs::key::transactionId] = transactionId; + + GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), + m_appSettingsRepository->isDevGatewayEnv(), + apiDefs::requestTimeoutMsecs, + m_appSettingsRepository->isStrictKillSwitchEnabled()); + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/subscriptions"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + // Parse the subscription response + QJsonObject responseObject = QJsonDocument::fromJson(responseBody).object(); + QString key = responseObject.value(QStringLiteral("key")).toString(); + if (key.isEmpty()) { + qWarning().noquote() << "[IAP] Subscription response does not contain a key field"; + return ErrorCode::ApiPurchaseError; + } + + // Check if server with this VPN key already exists + for (int i = 0; i < m_serversRepository->serversCount(); ++i) { + ServerConfig existingServerConfig = m_serversRepository->server(i); + QString existingVpnKey; + if (existingServerConfig.isApiV1()) { + const ApiV1ServerConfig* apiV1 = existingServerConfig.as(); + existingVpnKey = apiV1 ? apiV1->vpnKey() : QString(); + } else if (existingServerConfig.isApiV2()) { + const ApiV2ServerConfig* apiV2 = existingServerConfig.as(); + existingVpnKey = apiV2 ? apiV2->vpnKey() : QString(); + } + if (existingVpnKey == key) { + if (duplicateServerIndex) { + *duplicateServerIndex = i; + } + qInfo().noquote() << "[IAP] Subscription config with the same vpn_key already exists"; + return ErrorCode::ApiConfigAlreadyAdded; + } + } + + QString normalizedKey = key; + normalizedKey.replace(QStringLiteral("vpn://"), QString()); + + QByteArray configString = QByteArray::fromBase64(normalizedKey.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QByteArray configUncompressed = qUncompress(configString); + if (!configUncompressed.isEmpty()) { + configString = configUncompressed; + } + + if (configString.isEmpty()) { + qWarning().noquote() << "[IAP] Subscription response config payload is empty"; + return ErrorCode::ApiPurchaseError; + } + + QJsonObject configObject = QJsonDocument::fromJson(configString).object(); + + quint16 crc = qChecksum(QJsonDocument(configObject).toJson()); + + ServerConfig serverConfigModel = ServerConfig::fromJson(configObject); + + if (!serverConfigModel.isApiV2()) { + return ErrorCode::InternalError; + } + + ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + if (!apiV2) { + return ErrorCode::InternalError; + } + apiV2->apiConfig.vpnKey = normalizedKey; + apiV2->apiConfig.isTestPurchase = isTestPurchase; + apiV2->apiConfig.isInAppPurchase = true; + apiV2->apiConfig.subscriptionExpiredByServer = false; + apiV2->crc = crc; + + m_serversRepository->addServer(serverConfigModel); + serverConfig = serverConfigModel; + + return ErrorCode::NoError; +} + +ErrorCode SubscriptionController::updateServiceFromGateway(int serverIndex, const QString &newCountryCode, bool isConnectEvent) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + + if (!serverConfigModel.isApiV2()) { + return ErrorCode::InternalError; + } + + const ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + if (!apiV2) { + return ErrorCode::InternalError; + } + QString serviceProtocol = apiV2->serviceProtocol(); + ProtocolData protocolData = generateProtocolData(serviceProtocol); + + QJsonObject authDataJson = apiV2->authData.toJson(); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_appSettingsRepository->getAppLanguage().name().split("_").first(), + m_appSettingsRepository->getInstallationUuid(true), + apiV2->apiConfig.userCountryCode, + newCountryCode, + apiV2->serviceType(), + serviceProtocol, + authDataJson }; + + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload); + + if (isConnectEvent) { + apiPayload[apiDefs::key::isConnectEvent] = true; + } + + QByteArray responseBody; + ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + if (errorCode == ErrorCode::ApiSubscriptionExpiredError && !apiV2->apiConfig.isInAppPurchase) { + ServerConfig expiredServerConfig = serverConfigModel; + ApiV2ServerConfig *expiredApiV2 = expiredServerConfig.as(); + if (expiredApiV2) { + expiredApiV2->apiConfig.subscriptionExpiredByServer = true; + m_serversRepository->editServer(serverIndex, expiredServerConfig); + } + } + return errorCode; + } + + QJsonObject serverConfigJson; + errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + updateApiConfigInJson(serverConfigJson, apiV2->apiConfig.serviceType, serviceProtocol, apiV2->apiConfig.userCountryCode, responseBody); + + ServerConfig newServerConfigModel = ServerConfig::fromJson(serverConfigJson); + + if (!newServerConfigModel.isApiV2()) { + return ErrorCode::InternalError; + } + + ApiV2ServerConfig* newApiV2 = newServerConfigModel.as(); + if (!newApiV2) { + return ErrorCode::InternalError; + } + + newApiV2->apiConfig.vpnKey = apiV2->apiConfig.vpnKey; + newApiV2->apiConfig.isTestPurchase = apiV2->apiConfig.isTestPurchase; + newApiV2->apiConfig.isInAppPurchase = apiV2->apiConfig.isInAppPurchase; + newApiV2->apiConfig.subscriptionExpiredByServer = false; + + newApiV2->authData = apiV2->authData; + newApiV2->crc = apiV2->crc; + + if (apiV2->nameOverriddenByUser) { + newApiV2->name = apiV2->name; + newApiV2->nameOverriddenByUser = true; + } + + m_serversRepository->editServer(serverIndex, newServerConfigModel); + return ErrorCode::NoError; +} + +ErrorCode SubscriptionController::deactivateDevice(int serverIndex, bool isRemoveEvent) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + + if (!serverConfigModel.isApiV2()) { + return ErrorCode::NoError; + } + + const ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + if (!apiV2) { + return ErrorCode::NoError; + } + + if (!apiV2->isPremium() && !apiV2->isExternalPremium()) { + return ErrorCode::NoError; + } + + QJsonObject authDataJson = apiV2->authData.toJson(); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_appSettingsRepository->getAppLanguage().name().split("_").first(), + m_appSettingsRepository->getInstallationUuid(true), + apiV2->apiConfig.userCountryCode, + apiV2->apiConfig.serverCountryCode, + apiV2->serviceType(), + "", + authDataJson }; + + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + + QByteArray responseBody; + ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { + return errorCode; + } + + serverConfigModel.visit([](auto& arg) { + arg.containers.clear(); + }); + m_serversRepository->editServer(serverIndex, serverConfigModel); + return ErrorCode::NoError; +} + +ErrorCode SubscriptionController::deactivateExternalDevice(int serverIndex, const QString &uuid, const QString &serverCountryCode) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + + if (!serverConfigModel.isApiV2()) { + return ErrorCode::NoError; + } + + const ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + if (!apiV2) { + return ErrorCode::NoError; + } + + if (!apiV2->isPremium() && !apiV2->isExternalPremium()) { + return ErrorCode::NoError; + } + + QJsonObject authDataJson = apiV2->authData.toJson(); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_appSettingsRepository->getAppLanguage().name().split("_").first(), + uuid, + apiV2->apiConfig.userCountryCode, + serverCountryCode, + apiV2->serviceType(), + "", + authDataJson }; + + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + + QByteArray responseBody; + ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { + return errorCode; + } + + if (uuid == m_appSettingsRepository->getInstallationUuid(true)) { + serverConfigModel.visit([](auto& arg) { + arg.containers.clear(); + }); + m_serversRepository->editServer(serverIndex, serverConfigModel); + } + + return ErrorCode::NoError; +} + +ErrorCode SubscriptionController::exportNativeConfig(int serverIndex, const QString &serverCountryCode, QString &nativeConfig) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + + if (!serverConfigModel.isApiV2()) { + return ErrorCode::InternalError; + } + + const ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + if (!apiV2) { + return ErrorCode::InternalError; + } + QString protocol = configKey::awg; + ProtocolData protocolData = generateProtocolData(protocol); + + QJsonObject authDataJson = apiV2->authData.toJson(); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_appSettingsRepository->getAppLanguage().name().split("_").first(), + m_appSettingsRepository->getInstallationUuid(true), + apiV2->apiConfig.userCountryCode, + serverCountryCode, + apiV2->serviceType(), + protocol, + authDataJson }; + + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + appendProtocolDataToApiPayload(protocol, protocolData, apiPayload); + + QByteArray responseBody; + ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object(); + nativeConfig = jsonConfig.value(configKey::config).toString(); + nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", protocolData.wireGuardClientPrivKey); + return ErrorCode::NoError; +} + +ErrorCode SubscriptionController::revokeNativeConfig(int serverIndex, const QString &serverCountryCode) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + + if (!serverConfigModel.isApiV2()) { + return ErrorCode::InternalError; + } + + const ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + if (!apiV2) { + return ErrorCode::InternalError; + } + QString protocol = configKey::awg; + + QJsonObject authDataJson = apiV2->authData.toJson(); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_appSettingsRepository->getAppLanguage().name().split("_").first(), + m_appSettingsRepository->getInstallationUuid(true), + apiV2->apiConfig.userCountryCode, + serverCountryCode, + apiV2->serviceType(), + protocol, + authDataJson }; + + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + + QByteArray responseBody; + ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { + return errorCode; + } + + return ErrorCode::NoError; +} + +ErrorCode SubscriptionController::updateServiceFromTelegram(int serverIndex) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + + if (!serverConfigModel.isApiV1()) { + return ErrorCode::InternalError; + } + + const ApiV1ServerConfig* apiV1 = serverConfigModel.as(); + if (!apiV1) { + return ErrorCode::InternalError; + } + QString serviceProtocol = apiV1->protocol; + ProtocolData protocolData = generateProtocolData(serviceProtocol); + QString installationUuid = m_appSettingsRepository->getInstallationUuid(true); + + GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_appSettingsRepository->isStrictKillSwitchEnabled()); + + QJsonObject apiPayload; + appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload); + apiPayload[apiDefs::key::uuid] = installationUuid; + apiPayload[apiDefs::key::osVersion] = QSysInfo::productType(); + apiPayload[apiDefs::key::appVersion] = QString(APP_VERSION); + apiPayload[configKey::accessToken] = apiV1->apiKey; + apiPayload[apiDefs::key::apiEndpoint] = apiV1->apiEndpoint; + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/proxy_config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + QJsonObject serverConfigJson; + errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + ServerConfig newServerConfigModel = ServerConfig::fromJson(serverConfigJson); + + if (!newServerConfigModel.isApiV1()) { + return ErrorCode::InternalError; + } + + ApiV1ServerConfig* newApiV1 = newServerConfigModel.as(); + if (!newApiV1) { + return ErrorCode::InternalError; + } + newApiV1->apiKey = apiV1->apiKey; + newApiV1->apiEndpoint = apiV1->apiEndpoint; + newApiV1->crc = apiV1->crc; + + m_serversRepository->editServer(serverIndex, newServerConfigModel); + return ErrorCode::NoError; +} + +ErrorCode SubscriptionController::prepareVpnKeyExport(int serverIndex, QString &vpnKey) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + + if (serverConfigModel.isApiV1()) { + const ApiV1ServerConfig* apiV1 = serverConfigModel.as(); + vpnKey = apiV1 ? apiV1->vpnKey() : QString(); + } else if (serverConfigModel.isApiV2()) { + ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + vpnKey = apiV2 ? apiV2->vpnKey() : QString(); + if (vpnKey.isEmpty()) { + QJsonObject serverJson = serverConfigModel.toJson(); + vpnKey = apiUtils::getPremiumV2VpnKey(serverJson); + if (vpnKey.isEmpty()) { + return ErrorCode::ApiConfigEmptyError; + } + apiV2->apiConfig.vpnKey = vpnKey; + m_serversRepository->editServer(serverIndex, serverConfigModel); + } + } else { + return ErrorCode::ApiConfigEmptyError; + } + + return ErrorCode::NoError; +} + +ErrorCode SubscriptionController::validateAndUpdateConfig(int serverIndex, bool hasInstalledContainers) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + + apiDefs::ConfigSource configSource; + if (serverConfigModel.isApiV1()) { + configSource = apiDefs::ConfigSource::Telegram; + } else if (serverConfigModel.isApiV2()) { + configSource = apiDefs::ConfigSource::AmneziaGateway; + } else { + return ErrorCode::NoError; + } + + if (configSource == apiDefs::ConfigSource::Telegram && !hasInstalledContainers) { + removeApiConfig(serverIndex); + return updateServiceFromTelegram(serverIndex); + } else if (configSource == apiDefs::ConfigSource::AmneziaGateway && !hasInstalledContainers) { + return updateServiceFromGateway(serverIndex, "", false); + } else if (configSource && isApiKeyExpired(serverIndex)) { + qDebug() << "attempt to update api config by expires_at event"; + if (configSource == apiDefs::ConfigSource::AmneziaGateway) { + return updateServiceFromGateway(serverIndex, "", false); + } else { + removeApiConfig(serverIndex); + return updateServiceFromTelegram(serverIndex); + } + } + return ErrorCode::NoError; +} + +void SubscriptionController::removeApiConfig(int serverIndex) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + +#if defined(Q_OS_IOS) || defined(MACOS_NE) + QString description = serverConfigModel.description(); + QString hostName = serverConfigModel.hostName(); + QString vpncName = QString("%1 (%2) %3") + .arg(description) + .arg(hostName) + .arg(""); + + AmneziaVPN::removeVPNC(vpncName.toStdString()); +#endif + + serverConfigModel.visit([](auto& arg) { + arg.dns1.clear(); + arg.dns2.clear(); + arg.containers.clear(); + arg.hostName.clear(); + arg.defaultContainer = DockerContainer::None; + }); + + if (serverConfigModel.isApiV2()) { + ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + if (apiV2) { + apiV2->apiConfig.publicKey = ApiConfig::PublicKeyInfo{}; + } + } + + m_serversRepository->editServer(serverIndex, serverConfigModel); +} + +bool SubscriptionController::isApiKeyExpired(int serverIndex) const +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + + if (!serverConfigModel.isApiV2()) { + return false; + } + + const ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + if (!apiV2) { + return false; + } + const QString expiresAt = apiV2->apiConfig.publicKey.expiresAt; + + if (expiresAt.isEmpty()) { + return false; + } + + auto expiresAtDateTime = QDateTime::fromString(expiresAt, Qt::ISODate).toUTC(); + if (expiresAtDateTime < QDateTime::currentDateTimeUtc()) { + return true; + } + + return false; +} + +void SubscriptionController::setCurrentProtocol(int serverIndex, const QString &protocolName) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + if (serverConfigModel.isApiV2()) { + ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + if (apiV2) { + apiV2->apiConfig.serviceProtocol = protocolName; + } + m_serversRepository->editServer(serverIndex, serverConfigModel); + } +} + +bool SubscriptionController::isVlessProtocol(int serverIndex) const +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + if (serverConfigModel.isApiV2()) { + const ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + return apiV2 && apiV2->serviceProtocol() == "vless"; + } + return false; +} + +ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCountryCode, const QString &serviceType, + const QString &serviceProtocol, const QString &productId, + ServerConfig &serverConfig, + int *duplicateServerIndex) +{ +#if defined(Q_OS_IOS) || defined(MACOS_NE) + bool purchaseOk = false; + QString originalTransactionId; + QString storeTransactionId; + QString storeProductId; + QString purchaseError; + QEventLoop waitPurchase; + + IosController::Instance()->purchaseProduct(productId, + [&](bool success, const QString &txId, const QString &purchasedProductId, + const QString &originalTxId, const QString &errorString) { + purchaseOk = success; + originalTransactionId = originalTxId; + storeTransactionId = txId; + storeProductId = purchasedProductId; + purchaseError = errorString; + waitPurchase.quit(); + }); + waitPurchase.exec(); + + if (!purchaseOk || originalTransactionId.isEmpty()) { + qDebug() << "IAP purchase failed:" << purchaseError; + return ErrorCode::ApiPurchaseError; + } + qInfo().noquote() << "[IAP] Purchase success. transactionId =" << storeTransactionId + << "originalTransactionId =" << originalTransactionId << "productId =" << storeProductId; + + bool isTestPurchase = IosController::Instance()->isTestFlight(); + + ProtocolData protocolData = generateProtocolData(serviceProtocol); + return importServiceFromAppStore(userCountryCode, serviceType, serviceProtocol, protocolData, + originalTransactionId, isTestPurchase, serverConfig, duplicateServerIndex); +#else + Q_UNUSED(userCountryCode); + Q_UNUSED(serviceType); + Q_UNUSED(serviceProtocol); + Q_UNUSED(productId); + Q_UNUSED(serverConfig); + return ErrorCode::ApiPurchaseError; +#endif +} + +SubscriptionController::AppStoreRestoreResult SubscriptionController::processAppStoreRestore(const QString &userCountryCode, const QString &serviceType, + const QString &serviceProtocol) +{ + AppStoreRestoreResult result; + +#if defined(Q_OS_IOS) || defined(MACOS_NE) + bool restoreSuccess = false; + QList restoredTransactions; + QString restoreError; + QEventLoop waitRestore; + + IosController::Instance()->restorePurchases([&](bool success, const QList &transactions, const QString &errorString) { + restoreSuccess = success; + restoredTransactions = transactions; + restoreError = errorString; + waitRestore.quit(); + }); + waitRestore.exec(); + + if (!restoreSuccess) { + qWarning().noquote() << "[IAP] Restore failed:" << restoreError; + result.errorCode = ErrorCode::ApiPurchaseError; + return result; + } + + if (restoredTransactions.isEmpty()) { + qInfo().noquote() << "[IAP] Restore completed, but no transactions were returned"; + result.errorCode = ErrorCode::ApiNoPurchasedSubscriptionsError; + return result; + } + + bool isTestPurchase = IosController::Instance()->isTestFlight(); + QSet processedTransactions; + + for (const QVariantMap &transaction : restoredTransactions) { + const QString originalTransactionId = transaction.value(QStringLiteral("originalTransactionId")).toString(); + const QString transactionId = transaction.value(QStringLiteral("transactionId")).toString(); + const QString transactionProductId = transaction.value(QStringLiteral("productId")).toString(); + + if (originalTransactionId.isEmpty()) { + qWarning().noquote() << "[IAP] Skipping restored transaction without originalTransactionId" << transactionId; + continue; + } + + if (processedTransactions.contains(originalTransactionId)) { + result.duplicateCount++; + continue; + } + processedTransactions.insert(originalTransactionId); + + qInfo().noquote() << "[IAP] Restoring subscription. transactionId =" << transactionId + << "originalTransactionId =" << originalTransactionId << "productId =" << transactionProductId; + + ProtocolData protocolData = generateProtocolData(serviceProtocol); + ServerConfig serverConfig; + int currentDuplicateServerIndex = -1; + ErrorCode errorCode = importServiceFromAppStore(userCountryCode, serviceType, serviceProtocol, protocolData, + originalTransactionId, isTestPurchase, serverConfig, + ¤tDuplicateServerIndex); + + if (errorCode == ErrorCode::ApiConfigAlreadyAdded) { + result.duplicateConfigAlreadyPresent = true; + if (result.duplicateServerIndex < 0) { + result.duplicateServerIndex = currentDuplicateServerIndex; + } + qInfo().noquote() << "[IAP] Skipping restored transaction" << originalTransactionId + << "because subscription config with the same vpn_key already exists"; + } else if (errorCode != ErrorCode::NoError) { + qWarning().noquote() << "[IAP] Failed to process restored subscription response for transaction" << originalTransactionId; + result.errorCode = errorCode; + } else { + result.hasInstalledConfig = true; + } + } + + if (!result.hasInstalledConfig) { + result.errorCode = result.duplicateConfigAlreadyPresent ? ErrorCode::ApiConfigAlreadyAdded : ErrorCode::ApiPurchaseError; + } + + return result; +#else + Q_UNUSED(userCountryCode); + Q_UNUSED(serviceType); + Q_UNUSED(serviceProtocol); + result.errorCode = ErrorCode::ApiPurchaseError; + return result; +#endif +} + +ErrorCode SubscriptionController::getAccountInfo(int serverIndex, QJsonObject &accountInfo) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + + if (!serverConfigModel.isApiV2()) { + return ErrorCode::InternalError; + } + + const ApiV2ServerConfig* apiV2 = serverConfigModel.as(); + if (!apiV2) { + return ErrorCode::InternalError; + } + bool isTestPurchase = apiV2->apiConfig.isTestPurchase; + + QJsonObject authDataJson = apiV2->authData.toJson(); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_appSettingsRepository->getAppLanguage().name().split("_").first(), + m_appSettingsRepository->getInstallationUuid(true), + apiV2->apiConfig.userCountryCode, + "", + apiV2->serviceType(), + "", + authDataJson }; + + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + apiPayload[apiDefs::key::subscriptionStatus] = getSubscriptionStatusForRenewal(apiV2->apiConfig); + + QByteArray responseBody; + ErrorCode errorCode = executeRequest(QString("%1v1/account_info"), apiPayload, responseBody, isTestPurchase); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + accountInfo = QJsonDocument::fromJson(responseBody).object(); + return ErrorCode::NoError; +} + +QFuture> SubscriptionController::getRenewalLink(int serverIndex) +{ + auto promise = QSharedPointer>>::create(); + promise->start(); + + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + if (!serverConfigModel.isApiV2()) { + promise->addResult(qMakePair(ErrorCode::InternalError, QString())); + promise->finish(); + return promise->future(); + } + + const ApiV2ServerConfig *apiV2 = serverConfigModel.as(); + if (!apiV2) { + promise->addResult(qMakePair(ErrorCode::InternalError, QString())); + promise->finish(); + return promise->future(); + } + + bool isTestPurchase = apiV2->apiConfig.isTestPurchase; + QJsonObject authDataJson = apiV2->authData.toJson(); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_appSettingsRepository->getAppLanguage().name().split("_").first(), + m_appSettingsRepository->getInstallationUuid(true), + apiV2->apiConfig.userCountryCode, + "", + apiV2->serviceType(), + "", + authDataJson }; + + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + + auto gatewayController = QSharedPointer::create(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase), + m_appSettingsRepository->isDevGatewayEnv(isTestPurchase), + apiDefs::requestTimeoutMsecs, + m_appSettingsRepository->isStrictKillSwitchEnabled()); + auto postFuture = gatewayController->postAsync(QString("%1v1/renewal_link"), apiPayload); + auto *watcher = new QFutureWatcher>(); + QObject::connect(watcher, &QFutureWatcher>::finished, + [promise, watcher, gatewayController]() { + const auto [errorCode, responseBody] = watcher->result(); + watcher->deleteLater(); + if (errorCode != ErrorCode::NoError) { + promise->addResult(qMakePair(errorCode, QString())); + promise->finish(); + return; + } + + QJsonObject responseJson = QJsonDocument::fromJson(responseBody).object(); + const QString url = responseJson.value("renewal_url").toString(); + if (url.isEmpty()) { + promise->addResult(qMakePair(ErrorCode::InternalError, QString())); + } else { + promise->addResult(qMakePair(ErrorCode::NoError, url)); + } + promise->finish(); + }); + watcher->setFuture(postFuture); + return promise->future(); +} + diff --git a/client/core/controllers/api/subscriptionController.h b/client/core/controllers/api/subscriptionController.h new file mode 100644 index 000000000..05ec80a5b --- /dev/null +++ b/client/core/controllers/api/subscriptionController.h @@ -0,0 +1,122 @@ +#ifndef SUBSCRIPTIONCONTROLLER_H +#define SUBSCRIPTIONCONTROLLER_H + +#include +#include +#include +#include +#include + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/repositories/secureServersRepository.h" +#include "core/repositories/secureAppSettingsRepository.h" +#include "core/models/serverConfig.h" + +class ServersController; + +class SubscriptionController +{ +public: + struct ProtocolData + { + QString certRequest; + QString certPrivKey; + QString wireGuardClientPrivKey; + QString wireGuardClientPubKey; + QString xrayUuid; + }; + + struct GatewayRequestData + { + QString osVersion; + QString appVersion; + QString appLanguage; + QString installationUuid; + QString userCountryCode; + QString serverCountryCode; + QString serviceType; + QString serviceProtocol; + QJsonObject authData; + + QJsonObject toJsonObject() const; + }; + + explicit SubscriptionController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository); + + ProtocolData generateProtocolData(const QString &protocol); + void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload); + ErrorCode fillServerConfig(const QJsonObject &serverConfigJson, ServerConfig &serverConfig); + + ErrorCode importServiceFromGateway(const QString &userCountryCode, const QString &serviceType, + const QString &serviceProtocol, const ProtocolData &protocolData, + ServerConfig &serverConfig); + ErrorCode importTrialFromGateway(const QString &userCountryCode, const QString &serviceType, + const QString &serviceProtocol, const QString &email, + ServerConfig &serverConfig); + + ErrorCode importServiceFromAppStore(const QString &userCountryCode, const QString &serviceType, + const QString &serviceProtocol, const ProtocolData &protocolData, + const QString &transactionId, bool isTestPurchase, + ServerConfig &serverConfig, + int *duplicateServerIndex = nullptr); + + ErrorCode updateServiceFromGateway(int serverIndex, const QString &newCountryCode, bool isConnectEvent); + + ErrorCode deactivateDevice(int serverIndex, bool isRemoveEvent); + + ErrorCode deactivateExternalDevice(int serverIndex, const QString &uuid, const QString &serverCountryCode); + + ErrorCode exportNativeConfig(int serverIndex, const QString &serverCountryCode, QString &nativeConfig); + + ErrorCode revokeNativeConfig(int serverIndex, const QString &serverCountryCode); + + ErrorCode updateServiceFromTelegram(int serverIndex); + + ErrorCode prepareVpnKeyExport(int serverIndex, QString &vpnKey); + + ErrorCode validateAndUpdateConfig(int serverIndex, bool hasInstalledContainers); + + void removeApiConfig(int serverIndex); + + void setCurrentProtocol(int serverIndex, const QString &protocolName); + bool isVlessProtocol(int serverIndex) const; + + ErrorCode getAccountInfo(int serverIndex, QJsonObject &accountInfo); + QFuture> getRenewalLink(int serverIndex); + + struct AppStoreRestoreResult + { + bool hasInstalledConfig = false; + bool duplicateConfigAlreadyPresent = false; + int duplicateCount = 0; + int duplicateServerIndex = -1; + ErrorCode errorCode = ErrorCode::NoError; + }; + + ErrorCode processAppStorePurchase(const QString &userCountryCode, const QString &serviceType, + const QString &serviceProtocol, const QString &productId, + ServerConfig &serverConfig, + int *duplicateServerIndex = nullptr); + + AppStoreRestoreResult processAppStoreRestore(const QString &userCountryCode, const QString &serviceType, + const QString &serviceProtocol); + +private: + ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false); + bool isApiKeyExpired(int serverIndex) const; + + ErrorCode extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol, + const ProtocolData &protocolData, QJsonObject &serverConfigJson); + void updateApiConfigInJson(QJsonObject &serverConfigJson, const QString &serviceType, + const QString &serviceProtocol, const QString &userCountryCode, + const QByteArray &apiResponseBody); + + SecureServersRepository* m_serversRepository; + SecureAppSettingsRepository* m_appSettingsRepository; +}; + +#endif // SUBSCRIPTIONCONTROLLER_H + diff --git a/client/core/controllers/appSplitTunnelingController.cpp b/client/core/controllers/appSplitTunnelingController.cpp new file mode 100644 index 000000000..39b83cd32 --- /dev/null +++ b/client/core/controllers/appSplitTunnelingController.cpp @@ -0,0 +1,70 @@ +#include "appSplitTunnelingController.h" + +AppSplitTunnelingController::AppSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository) + : m_appSettingsRepository(appSettingsRepository) +{ + m_currentRouteMode = m_appSettingsRepository->appsRouteMode(); + if (m_currentRouteMode == AppsRouteMode::VpnAllApps) { // for old split tunneling configs + m_currentRouteMode = AppsRouteMode::VpnAllExceptApps; + m_apps = m_appSettingsRepository->vpnApps(m_currentRouteMode); + m_appSettingsRepository->setAppsRouteMode(AppsRouteMode::VpnAllExceptApps); + } else { + m_apps = m_appSettingsRepository->vpnApps(m_currentRouteMode); + } +} + +bool AppSplitTunnelingController::addApp(const amnezia::InstalledAppInfo &appInfo) +{ + if (m_apps.contains(appInfo)) { + return false; + } + + m_apps.append(appInfo); + m_appSettingsRepository->setVpnApps(m_currentRouteMode, m_apps); + + return true; +} + +void AppSplitTunnelingController::removeApp(int index) +{ + if (index < 0 || index >= m_apps.size()) { + return; + } + + m_apps.removeAt(index); + m_appSettingsRepository->setVpnApps(m_currentRouteMode, m_apps); +} + +void AppSplitTunnelingController::clearAppsList() +{ + m_apps.clear(); + m_appSettingsRepository->setVpnApps(m_currentRouteMode, m_apps); +} + +void AppSplitTunnelingController::setRouteMode(AppsRouteMode routeMode) +{ + m_currentRouteMode = routeMode; + m_apps = m_appSettingsRepository->vpnApps(m_currentRouteMode); + m_appSettingsRepository->setAppsRouteMode(routeMode); +} + +void AppSplitTunnelingController::toggleSplitTunneling(bool enabled) +{ + m_appSettingsRepository->setAppsSplitTunnelingEnabled(enabled); +} + +AppsRouteMode AppSplitTunnelingController::getRouteMode() const +{ + return m_currentRouteMode; +} + +bool AppSplitTunnelingController::isSplitTunnelingEnabled() const +{ + return m_appSettingsRepository->isAppsSplitTunnelingEnabled(); +} + +QVector AppSplitTunnelingController::getApps() const +{ + return m_apps; +} + diff --git a/client/core/controllers/appSplitTunnelingController.h b/client/core/controllers/appSplitTunnelingController.h new file mode 100644 index 000000000..1e8d9f0dc --- /dev/null +++ b/client/core/controllers/appSplitTunnelingController.h @@ -0,0 +1,32 @@ +#ifndef APPSPLITTUNNELINGCONTROLLER_H +#define APPSPLITTUNNELINGCONTROLLER_H + +#include + +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/repositories/secureAppSettingsRepository.h" + +class AppSplitTunnelingController +{ +public: + explicit AppSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository); + + bool addApp(const amnezia::InstalledAppInfo &appInfo); + void removeApp(int index); + void clearAppsList(); + void setRouteMode(AppsRouteMode routeMode); + void toggleSplitTunneling(bool enabled); + + AppsRouteMode getRouteMode() const; + bool isSplitTunnelingEnabled() const; + QVector getApps() const; + +private: + SecureAppSettingsRepository* m_appSettingsRepository; + AppsRouteMode m_currentRouteMode; + QVector m_apps; +}; + +#endif // APPSPLITTUNNELINGCONTROLLER_H + diff --git a/client/core/controllers/connectionController.cpp b/client/core/controllers/connectionController.cpp new file mode 100644 index 000000000..122727e76 --- /dev/null +++ b/client/core/controllers/connectionController.cpp @@ -0,0 +1,183 @@ +#include "connectionController.h" + +#include + +#include "core/configurators/configuratorBase.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/utilities.h" +#include "core/utils/networkUtilities.h" +#include "version.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/serverConfig.h" +#include "core/models/containerConfig.h" +#include "core/models/protocolConfig.h" + +using namespace amnezia; +using namespace ProtocolUtils; + +ConnectionController::ConnectionController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository, + VpnConnection* vpnConnection, + QObject* parent) + : QObject(parent), + m_serversRepository(serversRepository), + m_appSettingsRepository(appSettingsRepository), + m_vpnConnection(vpnConnection) +{ + connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged); + connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection); + connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); + connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection); + connect(this, &ConnectionController::killSwitchModeChangedRequested, m_vpnConnection, &VpnConnection::onKillSwitchModeChanged, Qt::QueuedConnection); +#ifdef Q_OS_ANDROID + connect(this, &ConnectionController::restoreConnectionRequested, m_vpnConnection, &VpnConnection::restoreConnection, Qt::QueuedConnection); +#endif +} + +bool ConnectionController::isConnected() const +{ + return m_vpnConnection && m_vpnConnection->connectionState() == Vpn::ConnectionState::Connected; +} + +void ConnectionController::setConnectionState(Vpn::ConnectionState state) +{ + if (m_vpnConnection) { + emit setConnectionStateRequested(state); + } +} + +ErrorCode ConnectionController::prepareConnection(int serverIndex, + QJsonObject& vpnConfiguration, + DockerContainer& container) +{ + if (!isServiceReady()) { + return ErrorCode::AmneziaServiceNotRunning; + } + + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + container = serverConfigModel.defaultContainer(); + + if (!isContainerSupported(container)) { + return ErrorCode::NotSupportedOnThisPlatform; + } + + ContainerConfig containerConfigModel = m_serversRepository->containerConfig(serverIndex, container); + + auto dns = serverConfigModel.getDnsPair(m_appSettingsRepository->useAmneziaDns(), + m_appSettingsRepository->primaryDns(), + m_appSettingsRepository->secondaryDns()); + + vpnConfiguration = createConnectionConfiguration(dns, serverConfigModel, containerConfigModel, container); + + return ErrorCode::NoError; +} + +ErrorCode ConnectionController::openConnection(int serverIndex) +{ + QJsonObject vpnConfiguration; + DockerContainer container; + + ErrorCode errorCode = prepareConnection(serverIndex, vpnConfiguration, container); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + emit openConnectionRequested(serverIndex, container, vpnConfiguration); + return ErrorCode::NoError; +} + +void ConnectionController::closeConnection() +{ + if (m_vpnConnection) { + emit closeConnectionRequested(); + } +} + +#ifdef Q_OS_ANDROID +void ConnectionController::restoreConnection() +{ + if (m_vpnConnection) { + emit restoreConnectionRequested(); + } +} +#endif + +void ConnectionController::onKillSwitchModeChanged(bool enabled) +{ + if (m_vpnConnection) { + emit killSwitchModeChangedRequested(enabled); + } +} + +ErrorCode ConnectionController::lastConnectionError() const +{ + return m_vpnConnection->lastError(); +} + +QJsonObject ConnectionController::createConnectionConfiguration(const QPair &dns, + const ServerConfig &serverConfig, + const ContainerConfig &containerConfig, + DockerContainer container) +{ + QJsonObject vpnConfiguration {}; + + if (ContainerUtils::containerService(container) == ServiceType::Other) { + return vpnConfiguration; + } + + Proto proto = ContainerUtils::defaultProtocol(container); + + ConnectionSettings connectionSettings = { + { dns.first, dns.second }, + serverConfig.isApiConfig(), + { + m_appSettingsRepository->isSitesSplitTunnelingEnabled(), + m_appSettingsRepository->routeMode() + } + }; + + auto configurator = ConfiguratorBase::create(proto, nullptr); + ProtocolConfig processedConfig = configurator->processConfigWithLocalSettings(connectionSettings, + containerConfig.protocolConfig); + + QJsonObject vpnConfigData = processedConfig.getClientConfigJson(); + if (ContainerUtils::isAwgContainer(container) || container == DockerContainer::WireGuard) { + if (vpnConfigData[configKey::mtu].toString().isEmpty()) { + vpnConfigData[configKey::mtu] = + ContainerUtils::isAwgContainer(container) ? protocols::awg::defaultMtu : + protocols::wireguard::defaultMtu; + } + } + + vpnConfiguration.insert(ProtocolUtils::key_proto_config_data(proto), vpnConfigData); + vpnConfiguration[configKey::vpnProto] = ProtocolUtils::protoToString(proto); + + vpnConfiguration[configKey::dns1] = dns.first; + vpnConfiguration[configKey::dns2] = dns.second; + + vpnConfiguration[configKey::hostName] = serverConfig.hostName(); + vpnConfiguration[configKey::description] = serverConfig.description(); + + vpnConfiguration[configKey::configVersion] = serverConfig.configVersion(); + + return vpnConfiguration; +} + +bool ConnectionController::isServiceReady() const +{ +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) + return Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true); +#else + return true; +#endif +} + +bool ConnectionController::isContainerSupported(DockerContainer container) const +{ + return ContainerUtils::isSupportedByCurrentPlatform(container); +} diff --git a/client/core/controllers/connectionController.h b/client/core/controllers/connectionController.h new file mode 100644 index 000000000..c4c9904bb --- /dev/null +++ b/client/core/controllers/connectionController.h @@ -0,0 +1,78 @@ +#ifndef CONNECTIONCONTROLLER_H +#define CONNECTIONCONTROLLER_H + +#include +#include +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/repositories/secureServersRepository.h" +#include "core/repositories/secureAppSettingsRepository.h" +#include "core/protocols/vpnProtocol.h" +#include "vpnConnection.h" + +using namespace amnezia; + +class ConnectionController : public QObject +{ + Q_OBJECT + +public: + explicit ConnectionController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository, + VpnConnection* vpnConnection, + QObject* parent = nullptr); + ~ConnectionController() = default; + + ErrorCode prepareConnection(int serverIndex, + QJsonObject& vpnConfiguration, + DockerContainer& container); + + ErrorCode openConnection(int serverIndex); + + void closeConnection(); + +#ifdef Q_OS_ANDROID + void restoreConnection(); +#endif + + void onKillSwitchModeChanged(bool enabled); + + ErrorCode lastConnectionError() const; + + bool isConnected() const; + void setConnectionState(Vpn::ConnectionState state); + + QJsonObject createConnectionConfiguration(const QPair &dns, + const ServerConfig &serverConfig, + const ContainerConfig &containerConfig, + DockerContainer container); + + bool isServiceReady() const; + + bool isContainerSupported(DockerContainer container) const; + +signals: + void connectionStateChanged(Vpn::ConnectionState state); + void openConnectionRequested(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration); + void closeConnectionRequested(); + void setConnectionStateRequested(Vpn::ConnectionState state); + void killSwitchModeChangedRequested(bool enabled); + +#ifdef Q_OS_ANDROID + void restoreConnectionRequested(); +#endif + +private: + SecureServersRepository* m_serversRepository; + SecureAppSettingsRepository* m_appSettingsRepository; + VpnConnection* m_vpnConnection; +}; + +#endif diff --git a/client/core/controllers/coreController.cpp b/client/core/controllers/coreController.cpp index 42a8e2037..4e95d5c70 100644 --- a/client/core/controllers/coreController.cpp +++ b/client/core/controllers/coreController.cpp @@ -2,9 +2,18 @@ #include #include +#include + +#include "core/utils/selfhosted/sshSession.h" +#include "core/controllers/selfhosted/installController.h" +#include "core/controllers/selfhosted/importController.h" +#include "core/controllers/coreSignalHandlers.h" +#include "core/models/serverConfig.h" +#include "logger.h" +#include "secureQSettings.h" #if defined(Q_OS_ANDROID) - #include "core/installedAppsImageProvider.h" + #include "core/utils/installedAppsImageProvider.h" #include "platforms/android/android_controller.h" #endif @@ -13,158 +22,196 @@ #include #endif -CoreController::CoreController(const QSharedPointer &vpnConnection, const std::shared_ptr &settings, +CoreController::CoreController(const QSharedPointer &vpnConnection, SecureQSettings* settings, QQmlApplicationEngine *engine, QObject *parent) : QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine) { + initRepositories(); + initCoreControllers(); initModels(); initControllers(); initSignalHandlers(); initAndroidController(); initAppleController(); + initLogging(); - initNotificationHandler(); + m_translator = new QTranslator(this); + if (m_appSettingsRepository) { + updateTranslator(m_appSettingsRepository->getAppLanguage()); + } +} - m_translator.reset(new QTranslator()); - updateTranslator(m_settings->getAppLanguage()); +void CoreController::setQmlContextProperty(const QString &name, QObject *value) +{ + if (m_engine) { + m_engine->rootContext()->setContextProperty(name, value); + } } void CoreController::initModels() { - m_containersModel.reset(new ContainersModel(this)); - m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + m_containersModel = new ContainersModel(this); + setQmlContextProperty("ContainersModel", m_containersModel); - m_defaultServerContainersModel.reset(new ContainersModel(this)); - m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get()); + m_defaultServerContainersModel = new ContainersModel(this); + setQmlContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel); - m_serversModel.reset(new ServersModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + m_serversModel = new ServersModel(this); + setQmlContextProperty("ServersModel", m_serversModel); - m_languageModel.reset(new LanguageModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); + m_languageModel = new LanguageModel(this); + setQmlContextProperty("LanguageModel", m_languageModel); - m_sitesModel.reset(new SitesModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); + m_ipSplitTunnelingModel = new IpSplitTunnelingModel(this); + setQmlContextProperty("IpSplitTunnelingModel", m_ipSplitTunnelingModel); - m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get()); + m_allowedDnsModel = new AllowedDnsModel(this); + setQmlContextProperty("AllowedDnsModel", m_allowedDnsModel); - m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get()); + m_appSplitTunnelingModel = new AppSplitTunnelingModel(this); + setQmlContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel); - m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); + m_protocolsModel = new ProtocolsModel(this); + setQmlContextProperty("ProtocolsModel", m_protocolsModel); - m_openVpnConfigModel.reset(new OpenVpnConfigModel(this)); - m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get()); + m_openVpnConfigModel = new OpenVpnConfigModel(this); + setQmlContextProperty("OpenVpnConfigModel", m_openVpnConfigModel); - m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this)); - m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get()); + m_wireGuardConfigModel = new WireGuardConfigModel(this); + setQmlContextProperty("WireGuardConfigModel", m_wireGuardConfigModel); - m_cloakConfigModel.reset(new CloakConfigModel(this)); - m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get()); + m_awgConfigModel = new AwgConfigModel(this); + setQmlContextProperty("AwgConfigModel", m_awgConfigModel); - m_wireGuardConfigModel.reset(new WireGuardConfigModel(this)); - m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get()); + m_xrayConfigModel = new XrayConfigModel(this); + setQmlContextProperty("XrayConfigModel", m_xrayConfigModel); - m_awgConfigModel.reset(new AwgConfigModel(this)); - m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get()); - - m_xrayConfigModel.reset(new XrayConfigModel(this)); - m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get()); + m_torConfigModel = new TorConfigModel(this); + setQmlContextProperty("TorConfigModel", m_torConfigModel); #ifdef Q_OS_WINDOWS - m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); - m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); + m_ikev2ConfigModel = new Ikev2ConfigModel(this); + setQmlContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel); #endif - m_sftpConfigModel.reset(new SftpConfigModel(this)); - m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get()); + m_sftpConfigModel = new SftpConfigModel(this); + setQmlContextProperty("SftpConfigModel", m_sftpConfigModel); - m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this)); - m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get()); + m_socks5ConfigModel = new Socks5ProxyConfigModel(this); + setQmlContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel); - m_clientManagementModel.reset(new ClientManagementModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get()); + m_clientManagementModel = new ClientManagementModel(this); + setQmlContextProperty("ClientManagementModel", m_clientManagementModel); - m_apiServicesModel.reset(new ApiServicesModel(this)); - m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get()); + m_apiServicesModel = new ApiServicesModel(this); + setQmlContextProperty("ApiServicesModel", m_apiServicesModel); - m_apiSubscriptionPlansModel.reset(new ApiSubscriptionPlansModel(this)); - m_engine->rootContext()->setContextProperty("ApiSubscriptionPlansModel", m_apiSubscriptionPlansModel.get()); + m_apiCountryModel = new ApiCountryModel(this); + setQmlContextProperty("ApiCountryModel", m_apiCountryModel); - m_apiBenefitsModel.reset(new ApiBenefitsModel(this)); - m_engine->rootContext()->setContextProperty("ApiBenefitsModel", m_apiBenefitsModel.get()); + m_apiSubscriptionPlansModel = new ApiSubscriptionPlansModel(this); + setQmlContextProperty("ApiSubscriptionPlansModel", m_apiSubscriptionPlansModel); - m_apiCountryModel.reset(new ApiCountryModel(this)); - m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get()); + m_apiBenefitsModel = new ApiBenefitsModel(this); + setQmlContextProperty("ApiBenefitsModel", m_apiBenefitsModel); - m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this)); - m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get()); + m_apiAccountInfoModel = new ApiAccountInfoModel(this); + setQmlContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel); - m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get()); + m_apiDevicesModel = new ApiDevicesModel(this); + setQmlContextProperty("ApiDevicesModel", m_apiDevicesModel); - m_newsModel.reset(new NewsModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get()); + m_newsModel = new NewsModel(m_appSettingsRepository, this); + setQmlContextProperty("NewsModel", m_newsModel); +} + +void CoreController::initRepositories() +{ + m_serversRepository = new SecureServersRepository(m_settings, this); + m_appSettingsRepository = new SecureAppSettingsRepository(m_settings, this); + + if (m_vpnConnection) { + m_vpnConnection->setRepositories(m_serversRepository, m_appSettingsRepository); + } +} + +void CoreController::initCoreControllers() +{ + m_serversController = new ServersController(m_serversRepository, m_appSettingsRepository, this); + m_appSplitTunnelingController = new AppSplitTunnelingController(m_appSettingsRepository); + m_usersController = new UsersController(m_serversRepository, this); + m_ipSplitTunnelingController = new IpSplitTunnelingController(m_appSettingsRepository, this); + m_allowedDnsController = new AllowedDnsController(m_appSettingsRepository); + m_servicesCatalogController = new ServicesCatalogController(m_appSettingsRepository); + m_subscriptionController = new SubscriptionController(m_serversRepository, m_appSettingsRepository); + m_newsController = new NewsController(m_appSettingsRepository, m_serversController); + + m_installController = new InstallController(m_serversRepository, m_appSettingsRepository, this); + m_exportController = new ExportController(m_serversRepository, m_appSettingsRepository, this); + m_importCoreController = new ImportController(m_serversRepository, m_appSettingsRepository, this); + m_connectionController = new ConnectionController(m_serversRepository, m_appSettingsRepository, m_vpnConnection.get(), this); + m_settingsController = new SettingsController(m_serversRepository, m_appSettingsRepository, this); } void CoreController::initControllers() { - m_connectionController.reset( - new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings)); - m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + m_connectionUiController = new ConnectionUiController(m_connectionController, m_serversController, this); + setQmlContextProperty("ConnectionController", m_connectionUiController); - m_pageController.reset(new PageController(m_serversModel, m_settings)); - m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + if (m_engine) { + m_focusController = new FocusController(m_engine, this); + setQmlContextProperty("FocusController", m_focusController); + } - m_focusController.reset(new FocusController(m_engine, this)); - m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get()); + m_installUiController = new InstallUiController(m_installController, m_serversController, m_settingsController, m_protocolsModel, m_usersController, + m_awgConfigModel, m_wireGuardConfigModel, m_openVpnConfigModel, m_xrayConfigModel, m_torConfigModel, +#ifdef Q_OS_WINDOWS + m_ikev2ConfigModel, +#endif + m_sftpConfigModel, m_socks5ConfigModel, this); + setQmlContextProperty("InstallController", m_installUiController); - m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings)); - m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + m_importController = new ImportUiController(m_importCoreController, this); + setQmlContextProperty("ImportController", m_importController); - connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(), - &ConnectionController::onCurrentContainerUpdated); // TODO remove this + m_exportUiController = new ExportUiController(m_exportController, this); + setQmlContextProperty("ExportController", m_exportUiController); - connect(m_installController.get(), &InstallController::profileCleared, - m_protocolsModel.get(), &ProtocolsModel::updateModel); + m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this); + setQmlContextProperty("LanguageUiController", m_languageUiController); - m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); - m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); + m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this); + setQmlContextProperty("SettingsController", m_settingsUiController); - m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings)); - m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); + m_pageController = new PageController(m_serversController, m_settingsController, this); + setQmlContextProperty("PageController", m_pageController); - m_settingsController.reset( - new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings)); - m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + m_serversUiController = new ServersUiController(m_serversController, m_settingsController, m_serversModel, m_containersModel, m_defaultServerContainersModel, this); + setQmlContextProperty("ServersUiController", m_serversUiController); - m_sitesController.reset(new SitesController(m_settings, m_sitesModel)); - m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); + m_ipSplitTunnelingUiController = new IpSplitTunnelingUiController(m_ipSplitTunnelingController, m_ipSplitTunnelingModel, this); + setQmlContextProperty("IpSplitTunnelingController", m_ipSplitTunnelingUiController); - m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel)); - m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get()); + m_allowedDnsUiController = new AllowedDnsUiController(m_allowedDnsController, m_allowedDnsModel, this); + setQmlContextProperty("AllowedDnsController", m_allowedDnsUiController); - m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel)); - m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get()); + m_appSplitTunnelingUiController = new AppSplitTunnelingUiController(m_appSplitTunnelingController, m_appSplitTunnelingModel, this); + setQmlContextProperty("AppSplitTunnelingController", m_appSplitTunnelingUiController); - m_systemController.reset(new SystemController(m_settings)); - m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); + m_systemController = new SystemController(this); + setQmlContextProperty("SystemController", m_systemController); - m_apiSettingsController.reset( - new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings)); - m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get()); + m_servicesCatalogUiController = new ServicesCatalogUiController(m_servicesCatalogController, m_apiServicesModel, this); + setQmlContextProperty("ServicesCatalogUiController", m_servicesCatalogUiController); - m_apiConfigsController.reset( - new ApiConfigsController(m_serversModel, m_apiServicesModel, m_apiSubscriptionPlansModel, m_apiBenefitsModel, m_settings)); - m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get()); - connect(m_apiConfigsController.get(), &ApiConfigsController::subscriptionRefreshNeeded, - this, [this]() { m_apiSettingsController->getAccountInfo(false); }); + 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); + setQmlContextProperty("SubscriptionUiController", m_subscriptionUiController); - m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this)); - m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get()); + m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this); + setQmlContextProperty("ApiNewsController", m_apiNewsUiController); } void CoreController::initAndroidController() @@ -173,33 +220,16 @@ void CoreController::initAndroidController() if (!AndroidController::initLogging()) { qFatal("Android logging initialization failed"); } - AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs()); - connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs); + AndroidController::instance()->setSaveLogs(m_appSettingsRepository->isSaveLogs()); + AndroidController::instance()->setScreenshotsEnabled(m_appSettingsRepository->isScreenshotsEnabled()); - AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled()); - connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled); - - connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer); - - connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); }); - - connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) { - m_connectionController->onConnectionStateChanged(state); - if (m_vpnConnection) - m_vpnConnection->restoreConnection(); - }); if (!AndroidController::instance()->initialize()) { qFatal("Android controller initialization failed"); } - connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) { - emit m_pageController->goToPageHome(); - m_importController->extractConfigFromData(data); - data.clear(); - emit m_pageController->goToPageViewConfig(); - }); - - m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider); + if (m_engine) { + m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider); + } #endif } @@ -207,63 +237,36 @@ void CoreController::initAppleController() { #ifdef Q_OS_IOS IosController::Instance()->initialize(); - connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) { - emit m_pageController->goToPageHome(); - m_importController->extractConfigFromData(data); - emit m_pageController->goToPageViewConfig(); - }); + QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_appSettingsRepository->isScreenshotsEnabled()); }); +#endif +} - connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) { - emit m_pageController->goToPageHome(); - m_pageController->goToPageSettingsBackup(); - emit m_settingsController->importBackupFromOutside(filePath); - }); - - QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); }); - - connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); }); +void CoreController::initLogging() +{ +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + bool enabled = m_appSettingsRepository->isSaveLogs(); + if (enabled) { + if (!Logger::init(false)) { + qWarning() << "Initialization of debug subsystem failed"; + } + } + Logger::setServiceLogsEnabled(enabled); #endif } void CoreController::initSignalHandlers() { - initErrorMessagesHandler(); - - initApiCountryModelUpdateHandler(); - initContainerModelUpdateHandler(); - initAdminConfigRevokedHandler(); - initPassphraseRequestHandler(); - initTranslationsUpdatedHandler(); - initAutoConnectHandler(); - initAmneziaDnsToggledHandler(); - initPrepareConfigHandler(); - initStrictKillSwitchHandler(); -} - -void CoreController::initNotificationHandler() -{ -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - m_notificationHandler.reset(NotificationHandler::create(nullptr)); - - connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), - &NotificationHandler::setConnectionState); - - connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow); - connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), - static_cast(&ConnectionController::openConnection)); - connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), - &ConnectionController::closeConnection); - connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated); - - auto* trayHandler = qobject_cast(m_notificationHandler.get()); - connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl); -#endif + m_signalHandlers = new CoreSignalHandlers(this, this); + m_signalHandlers->initAllHandlers(); + + // Trigger initial update after handlers are connected + m_serversUiController->updateModel(); } void CoreController::updateTranslator(const QLocale &locale) { if (!m_translator->isEmpty()) { - QCoreApplication::removeTranslator(m_translator.get()); + QCoreApplication::removeTranslator(m_translator); } QStringList availableTranslations; @@ -284,119 +287,31 @@ void CoreController::updateTranslator(const QLocale &locale) } if (m_translator->load(strFileName)) { - if (QCoreApplication::installTranslator(m_translator.get())) { - m_settings->setAppLanguage(locale); - } + QCoreApplication::installTranslator(m_translator); } else { - m_settings->setAppLanguage(QLocale::English); + if (m_translator->load(QString(":/translations/amneziavpn_en.qm"))) { + QCoreApplication::installTranslator(m_translator); + } } - m_engine->retranslate(); + if (m_engine) { + m_engine->retranslate(); + } emit translationsUpdated(); - emit websiteUrlChanged(m_languageModel->getCurrentSiteUrl()); -} - -void CoreController::initErrorMessagesHandler() -{ - connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](ErrorCode errorCode) { - emit m_pageController->showErrorMessage(errorCode); - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); - }); - - connect(m_apiConfigsController.get(), &ApiConfigsController::errorOccurred, m_pageController.get(), - qOverload(&PageController::showErrorMessage)); + if (m_languageUiController) { + emit websiteUrlChanged(m_languageUiController->getCurrentSiteUrl()); + } } void CoreController::setQmlRoot() { - m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); -} - -void CoreController::initApiCountryModelUpdateHandler() -{ - connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() { - m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(), - m_serversModel->getProcessedServerData("apiServerCountryCode").toString()); - }); -} - -void CoreController::initContainerModelUpdateHandler() -{ - connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel); - connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(), - &ContainersModel::updateModel); - connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() { - if (m_serversModel->hasServersFromGatewayApi()) { - m_apiNewsController->fetchNews(false); - } - }); - m_serversModel->resetModel(); -} - -void CoreController::initAdminConfigRevokedHandler() -{ - connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(), - &ServersModel::clearCachedProfile); -} - -void CoreController::initPassphraseRequestHandler() -{ - connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), - &PageController::showPassphraseRequestDrawer); - connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), - &InstallController::setEncryptedPassphrase); -} - -void CoreController::initTranslationsUpdatedHandler() -{ - connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &CoreController::updateTranslator); - connect(this, &CoreController::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); - connect(this, &CoreController::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated); -} - -void CoreController::initAutoConnectHandler() -{ - if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { - QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); + if (m_engine && m_systemController) { + m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); } } -void CoreController::initAmneziaDnsToggledHandler() -{ - connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns); -} - -void CoreController::initPrepareConfigHandler() -{ - connect(m_connectionController.get(), &ConnectionController::prepareConfig, this, [this]() { - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing); - - if (!m_apiConfigsController->isConfigValid()) { - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); - return; - } - - m_installController->validateConfig(); - }); - - connect(m_installController.get(), &InstallController::configValidated, this, [this](bool isValid) { - if (!isValid) { - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); - return; - } - - m_connectionController->openConnection(); - }); -} - -void CoreController::initStrictKillSwitchHandler() -{ - connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(), - &VpnConnection::onKillSwitchModeChanged); -} - -QSharedPointer CoreController::pageController() const +PageController* CoreController::pageController() const { return m_pageController; } @@ -405,9 +320,11 @@ void CoreController::openConnectionByIndex(int serverIndex) { if (m_serversModel) { m_serversModel->setProcessedServerIndex(serverIndex); - m_serversModel->setDefaultServerIndex(serverIndex); } - m_connectionController->toggleConnection(); + if (m_serversController) { + m_serversController->setDefaultServerIndex(serverIndex); + } + m_connectionUiController->toggleConnection(); } void CoreController::importConfigFromData(const QString &data) diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h index fd2e88cad..1fb178f0f 100644 --- a/client/core/controllers/coreController.h +++ b/client/core/controllers/coreController.h @@ -6,28 +6,47 @@ #include #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - #include "ui/systemtray_notificationhandler.h" + #include "ui/utils/systemTrayNotificationHandler.h" #endif -#include "ui/controllers/api/apiConfigsController.h" -#include "ui/controllers/api/apiSettingsController.h" -#include "ui/controllers/api/apiNewsController.h" -#include "ui/controllers/appSplitTunnelingController.h" -#include "ui/controllers/allowedDnsController.h" -#include "ui/controllers/connectionController.h" -#include "ui/controllers/exportController.h" -#include "ui/controllers/focusController.h" -#include "ui/controllers/importController.h" -#include "ui/controllers/installController.h" -#include "ui/controllers/pageController.h" -#include "ui/controllers/settingsController.h" -#include "ui/controllers/sitesController.h" +#include "ui/controllers/api/subscriptionUiController.h" +#include "ui/controllers/api/apiNewsUiController.h" +#include "ui/controllers/appSplitTunnelingUiController.h" +#include "ui/controllers/allowedDnsUiController.h" +#include "ui/controllers/connectionUiController.h" +#include "ui/controllers/selfhosted/exportUiController.h" +#include "core/controllers/selfhosted/exportController.h" +#include "ui/controllers/qml/focusController.h" +#include "ui/controllers/importUiController.h" +#include "core/controllers/selfhosted/importController.h" +#include "ui/controllers/selfhosted/installUiController.h" +#include "ui/controllers/qml/pageController.h" +#include "ui/controllers/settingsUiController.h" +#include "ui/controllers/serversUiController.h" +#include "ui/controllers/ipSplitTunnelingUiController.h" #include "ui/controllers/systemController.h" +#include "ui/controllers/languageUiController.h" +#include "ui/controllers/api/servicesCatalogUiController.h" -#include "ui/models/allowed_dns_model.h" -#include "ui/models/containers_model.h" +#include "core/controllers/serversController.h" +#include "core/controllers/selfhosted/usersController.h" +#include "core/controllers/appSplitTunnelingController.h" +#include "core/controllers/ipSplitTunnelingController.h" +#include "core/controllers/allowedDnsController.h" +#include "core/controllers/api/servicesCatalogController.h" +#include "core/controllers/api/subscriptionController.h" +#include "core/controllers/api/newsController.h" +#include "core/controllers/selfhosted/installController.h" +#include "core/controllers/settingsController.h" +#include "core/controllers/connectionController.h" + +#include "core/repositories/secureServersRepository.h" +#include "core/repositories/secureAppSettingsRepository.h" +#include "secureQSettings.h" + +#include "ui/models/allowedDnsModel.h" +#include "ui/models/containersModel.h" #include "ui/models/languageModel.h" -#include "ui/models/protocols/cloakConfigModel.h" #ifdef Q_OS_WINDOWS #include "ui/models/protocols/ikev2ConfigModel.h" #endif @@ -41,117 +60,154 @@ #include "ui/models/clientManagementModel.h" #include "ui/models/protocols/awgConfigModel.h" #include "ui/models/protocols/openvpnConfigModel.h" -#include "ui/models/protocols/shadowsocksConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h" #include "ui/models/protocols/xrayConfigModel.h" -#include "ui/models/protocols_model.h" -#include "ui/models/servers_model.h" +#include "ui/models/protocolsModel.h" +#include "ui/models/services/torConfigModel.h" +#include "ui/models/serversModel.h" #include "ui/models/services/sftpConfigModel.h" #include "ui/models/services/socks5ProxyConfigModel.h" -#include "ui/models/sites_model.h" +#include "ui/models/ipSplitTunnelingModel.h" #include "ui/models/newsModel.h" #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - #include "ui/notificationhandler.h" + #include "ui/utils/notificationHandler.h" #endif +class CoreSignalHandlers; +class TestMultipleImports; +class TestAdminSelfHostedExport; +class TestServerEdit; +class TestDefaultServerChange; +class TestServerEdgeCases; +class TestSignalOrder; +class TestServersModelSync; +class TestGatewayStacks; +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 TestGatewayStacks; + friend class TestComplexOperations; + friend class TestSettingsSignals; + friend class TestUiServersModelAndController; + friend class TestSelfHostedServerSetup; public: - explicit CoreController(const QSharedPointer &vpnConnection, const std::shared_ptr &settings, + explicit CoreController(const QSharedPointer &vpnConnection, SecureQSettings* settings, QQmlApplicationEngine *engine, QObject *parent = nullptr); - QSharedPointer pageController() const; + PageController* pageController() const; void setQmlRoot(); void openConnectionByIndex(int serverIndex); void importConfigFromData(const QString &data); + void updateTranslator(const QLocale &locale); signals: void translationsUpdated(); void websiteUrlChanged(const QString &newUrl); private: + void initRepositories(); + void initCoreControllers(); void initModels(); void initControllers(); void initAndroidController(); void initAppleController(); + void initLogging(); void initSignalHandlers(); - - void initNotificationHandler(); - - void updateTranslator(const QLocale &locale); - - void initErrorMessagesHandler(); - - void initApiCountryModelUpdateHandler(); - void initContainerModelUpdateHandler(); - void initAdminConfigRevokedHandler(); - void initPassphraseRequestHandler(); - void initTranslationsUpdatedHandler(); - void initAutoConnectHandler(); - void initAmneziaDnsToggledHandler(); - void initPrepareConfigHandler(); - void initStrictKillSwitchHandler(); + void setQmlContextProperty(const QString &name, QObject *value); QQmlApplicationEngine *m_engine {}; // TODO use parent child system here? - std::shared_ptr m_settings; + SecureQSettings* m_settings; QSharedPointer m_vpnConnection; - QSharedPointer m_translator; + QTranslator* m_translator; + + SecureServersRepository* m_serversRepository; + SecureAppSettingsRepository* m_appSettingsRepository; #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - QScopedPointer m_notificationHandler; + NotificationHandler* m_notificationHandler; #endif QMetaObject::Connection m_reloadConfigErrorOccurredConnection; - QScopedPointer m_connectionController; - QScopedPointer m_focusController; - QSharedPointer m_pageController; // TODO - QScopedPointer m_installController; - QScopedPointer m_importController; - QScopedPointer m_exportController; - QScopedPointer m_settingsController; - QScopedPointer m_sitesController; - QScopedPointer m_systemController; - QScopedPointer m_appSplitTunnelingController; - QScopedPointer m_allowedDnsController; + ConnectionUiController* m_connectionUiController; + FocusController* m_focusController; + PageController* m_pageController; + InstallUiController* m_installUiController; + ImportUiController* m_importController; + ImportController* m_importCoreController; + ExportUiController* m_exportUiController; + SettingsUiController* m_settingsUiController; + ServersUiController* m_serversUiController; + IpSplitTunnelingUiController* m_ipSplitTunnelingUiController; + SystemController* m_systemController; + AppSplitTunnelingUiController* m_appSplitTunnelingUiController; + AllowedDnsUiController* m_allowedDnsUiController; + LanguageUiController* m_languageUiController; - QScopedPointer m_apiSettingsController; - QScopedPointer m_apiConfigsController; - QScopedPointer m_apiNewsController; + SubscriptionUiController* m_subscriptionUiController; + ApiNewsUiController* m_apiNewsUiController; + + ServicesCatalogUiController* m_servicesCatalogUiController; - QSharedPointer m_containersModel; - QSharedPointer m_defaultServerContainersModel; - QSharedPointer m_serversModel; - QSharedPointer m_languageModel; - QSharedPointer m_protocolsModel; - QSharedPointer m_sitesModel; - QSharedPointer m_newsModel; - QSharedPointer m_allowedDnsModel; - QSharedPointer m_appSplitTunnelingModel; - QSharedPointer m_clientManagementModel; + ServersController* m_serversController; + UsersController* m_usersController; + AppSplitTunnelingController* m_appSplitTunnelingController; + IpSplitTunnelingController* m_ipSplitTunnelingController; + AllowedDnsController* m_allowedDnsController; + ServicesCatalogController* m_servicesCatalogController; + SubscriptionController* m_subscriptionController; + NewsController* m_newsController; + InstallController* m_installController; + ExportController* m_exportController; + ConnectionController* m_connectionController; + SettingsController* m_settingsController; - QSharedPointer m_apiServicesModel; - QSharedPointer m_apiSubscriptionPlansModel; - QSharedPointer m_apiBenefitsModel; - QSharedPointer m_apiCountryModel; - QSharedPointer m_apiAccountInfoModel; - QSharedPointer m_apiDevicesModel; + ContainersModel* m_containersModel; + ContainersModel* m_defaultServerContainersModel; + ServersModel* m_serversModel; + LanguageModel* m_languageModel; + ProtocolsModel* m_protocolsModel; + IpSplitTunnelingModel* m_ipSplitTunnelingModel; + NewsModel* m_newsModel; + AllowedDnsModel* m_allowedDnsModel; + AppSplitTunnelingModel* m_appSplitTunnelingModel; + ClientManagementModel* m_clientManagementModel; - QScopedPointer m_openVpnConfigModel; - QScopedPointer m_shadowSocksConfigModel; - QScopedPointer m_cloakConfigModel; - QScopedPointer m_xrayConfigModel; - QScopedPointer m_wireGuardConfigModel; - QScopedPointer m_awgConfigModel; + ApiServicesModel* m_apiServicesModel; + ApiSubscriptionPlansModel* m_apiSubscriptionPlansModel; + ApiBenefitsModel* m_apiBenefitsModel; + ApiCountryModel* m_apiCountryModel; + ApiAccountInfoModel* m_apiAccountInfoModel; + ApiDevicesModel* m_apiDevicesModel; + + OpenVpnConfigModel* m_openVpnConfigModel; + XrayConfigModel* m_xrayConfigModel; + TorConfigModel* m_torConfigModel; + WireGuardConfigModel* m_wireGuardConfigModel; + AwgConfigModel* m_awgConfigModel; #ifdef Q_OS_WINDOWS - QScopedPointer m_ikev2ConfigModel; + Ikev2ConfigModel* m_ikev2ConfigModel; #endif - QScopedPointer m_sftpConfigModel; - QScopedPointer m_socks5ConfigModel; + SftpConfigModel* m_sftpConfigModel; + Socks5ProxyConfigModel* m_socks5ConfigModel; + + CoreSignalHandlers* m_signalHandlers; }; #endif // CORECONTROLLER_H diff --git a/client/core/controllers/coreSignalHandlers.cpp b/client/core/controllers/coreSignalHandlers.cpp new file mode 100644 index 000000000..f68eb8f91 --- /dev/null +++ b/client/core/controllers/coreSignalHandlers.cpp @@ -0,0 +1,412 @@ +#include "coreSignalHandlers.h" + +#include + +#include "core/utils/selfhosted/sshSession.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/controllers/coreController.h" +#include "core/repositories/secureServersRepository.h" +#include "core/repositories/secureAppSettingsRepository.h" +#include "vpnConnection.h" +#include "ui/controllers/qml/pageController.h" +#include "ui/controllers/connectionUiController.h" +#include "ui/controllers/settingsUiController.h" +#include "ui/controllers/serversUiController.h" +#include "ui/controllers/ipSplitTunnelingUiController.h" +#include "ui/controllers/allowedDnsUiController.h" +#include "ui/controllers/appSplitTunnelingUiController.h" +#include "ui/controllers/languageUiController.h" +#include "ui/controllers/selfhosted/installUiController.h" +#include "ui/controllers/importUiController.h" +#include "ui/controllers/api/subscriptionUiController.h" +#include "ui/models/serversModel.h" +#include "core/controllers/serversController.h" +#include "core/controllers/ipSplitTunnelingController.h" +#include "core/controllers/appSplitTunnelingController.h" +#include "core/controllers/selfhosted/usersController.h" +#include "core/controllers/settingsController.h" +#include "core/controllers/selfhosted/installController.h" +#include "core/controllers/selfhosted/exportController.h" +#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" + +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + #include "ui/utils/notificationHandler.h" + #include "ui/utils/systemTrayNotificationHandler.h" +#endif + +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" +#endif + +#ifdef Q_OS_IOS + #include "platforms/ios/ios_controller.h" + #include +#endif + +CoreSignalHandlers::CoreSignalHandlers(CoreController* coreController, QObject* parent) + : QObject(parent), + m_coreController(coreController) +{ +} + +void CoreSignalHandlers::initAllHandlers() +{ + initErrorMessagesHandler(); + initSettingsSplitTunnelingHandler(); + initInstallControllerHandler(); + initExportControllerHandler(); + initImportControllerHandler(); + initApiCountryModelUpdateHandler(); + initSubscriptionRefreshHandler(); + initContainerModelUpdateHandler(); + initAdminConfigRevokedHandler(); + initPassphraseRequestHandler(); + initTranslationsUpdatedHandler(); + initLanguageHandler(); + initAutoConnectHandler(); + initAmneziaDnsToggledHandler(); + initServersModelUpdateHandler(); + initClientManagementModelUpdateHandler(); + initSitesModelUpdateHandler(); + initAllowedDnsModelUpdateHandler(); + initAppSplitTunnelingModelUpdateHandler(); + initPrepareConfigHandler(); + initStrictKillSwitchHandler(); + initAndroidSettingsHandler(); + initAndroidConnectionHandler(); + initIosImportHandler(); + initIosSettingsHandler(); + initNotificationHandler(); +} + +void CoreSignalHandlers::initErrorMessagesHandler() +{ + connect(m_coreController->m_connectionUiController, &ConnectionUiController::connectionErrorOccurred, this, [this](ErrorCode errorCode) { + emit m_coreController->m_pageController->showErrorMessage(errorCode); + m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected); + }); + + connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::errorOccurred, m_coreController->m_pageController, + qOverload(&PageController::showErrorMessage)); + + connect(m_coreController->m_settingsUiController, &SettingsUiController::errorOccurred, m_coreController->m_pageController, + qOverload(&PageController::showErrorMessage)); +} + +void CoreSignalHandlers::initSettingsSplitTunnelingHandler() +{ + connect(m_coreController->m_settingsController, &SettingsController::siteSplitTunnelingRouteModeChanged, this, [this](RouteMode mode) { + m_coreController->m_ipSplitTunnelingController->setRouteMode(mode); + }); + connect(m_coreController->m_settingsController, &SettingsController::siteSplitTunnelingToggled, this, [this](bool enabled) { + m_coreController->m_ipSplitTunnelingController->toggleSplitTunneling(enabled); + }); + connect(m_coreController->m_settingsController, &SettingsController::appSplitTunnelingRouteModeChanged, this, [this](AppsRouteMode mode) { + m_coreController->m_appSplitTunnelingController->setRouteMode(mode); + }); + connect(m_coreController->m_settingsController, &SettingsController::appSplitTunnelingToggled, this, [this](bool enabled) { + m_coreController->m_appSplitTunnelingController->toggleSplitTunneling(enabled); + }); + connect(m_coreController->m_settingsController, &SettingsController::appSplitTunnelingClearAppsList, this, [this]() { + m_coreController->m_appSplitTunnelingController->clearAppsList(); + }); +} + +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_installUiController, &InstallUiController::currentContainerUpdated, m_coreController->m_connectionUiController, + &ConnectionUiController::onCurrentContainerUpdated); + connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIndexChanged, + m_coreController->m_installUiController, [this](int index) { + if (index >= 0) { + m_coreController->m_installUiController->clearProcessedServerCredentials(); + } + }); +} + +void CoreSignalHandlers::initExportControllerHandler() +{ + connect(m_coreController->m_exportController, &ExportController::appendClientRequested, this, + [this](int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container) { + m_coreController->m_usersController->appendClient(serverIndex, clientId, clientName, container); + }); + connect(m_coreController->m_exportController, &ExportController::updateClientsRequested, this, + [this](int serverIndex, DockerContainer container) { + m_coreController->m_usersController->updateClients(serverIndex, container); + }); + connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this, + [this](int serverIndex, int row, DockerContainer container) { + m_coreController->m_usersController->revokeClient(serverIndex, row, container); + }); + connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this, + [this](int serverIndex, int row, const QString &clientName, DockerContainer container) { + m_coreController->m_usersController->renameClient(serverIndex, row, clientName, container); + }); +} + +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; + m_coreController->m_serversController->setDefaultServerIndex(newServerIndex); + if (m_coreController->m_serversUiController) { + m_coreController->m_serversUiController->setProcessedServerIndex(newServerIndex); + } + } + }); +} + +void CoreSignalHandlers::initApiCountryModelUpdateHandler() +{ + connect(m_coreController->m_serversUiController, &ServersUiController::updateApiCountryModel, this, [this]() { + int processedIndex = m_coreController->m_serversUiController->getProcessedServerIndex(); + if (processedIndex < 0 || processedIndex >= m_coreController->m_serversRepository->serversCount()) { + return; + } + + ServerConfig server = m_coreController->m_serversRepository->server(processedIndex); + QJsonArray availableCountries; + QString serverCountryCode; + + if (server.isApiV2()) { + const ApiV2ServerConfig* apiV2 = server.as(); + if (apiV2) { + availableCountries = apiV2->apiConfig.availableCountries; + serverCountryCode = apiV2->apiConfig.serverCountryCode; + } + } + + m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode); + }); +} + +void CoreSignalHandlers::initSubscriptionRefreshHandler() +{ + connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::subscriptionRefreshNeeded, this, [this]() { + const int defaultServerIndex = m_coreController->m_serversController->getDefaultServerIndex(); + if (defaultServerIndex >= 0) { + m_coreController->m_subscriptionUiController->getAccountInfo(defaultServerIndex, false); + } + }); +} + +void CoreSignalHandlers::initContainerModelUpdateHandler() +{ + connect(m_coreController->m_serversController, &ServersController::gatewayStacksExpanded, this, [this]() { + if (m_coreController->m_serversUiController->hasServersFromGatewayApi()) { + m_coreController->m_apiNewsUiController->fetchNews(false); + } + }); +} + +void CoreSignalHandlers::initAdminConfigRevokedHandler() +{ + connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this, + [this](int serverIndex, const ContainerConfig &containerConfig, DockerContainer container) { + m_coreController->m_usersController->revokeClient(serverIndex, containerConfig, container); + }); + + connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this, + [this](int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container) { + m_coreController->m_usersController->appendClient(serverIndex, clientId, clientName, container); + }); + + connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_serversController, + &ServersController::clearCachedProfile); +} + +void CoreSignalHandlers::initPassphraseRequestHandler() +{ + connect(m_coreController->m_installUiController, &InstallUiController::passphraseRequestStarted, m_coreController->m_pageController, + &PageController::showPassphraseRequestDrawer); + connect(m_coreController->m_pageController, &PageController::passphraseRequestDrawerClosed, m_coreController->m_installUiController, + &InstallUiController::setEncryptedPassphrase); +} + +void CoreSignalHandlers::initTranslationsUpdatedHandler() +{ + connect(m_coreController->m_languageUiController, &LanguageUiController::updateTranslations, m_coreController, &CoreController::updateTranslator); + connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_languageUiController, &LanguageUiController::translationsUpdated); + connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_connectionUiController, &ConnectionUiController::onTranslationsUpdated); +} + +void CoreSignalHandlers::initLanguageHandler() +{ + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appLanguageChanged, m_coreController->m_languageUiController, &LanguageUiController::onAppLanguageChanged); + connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() { + m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum()); + }); +} + +void CoreSignalHandlers::initAutoConnectHandler() +{ + if (m_coreController->m_settingsUiController->isAutoConnectEnabled() && m_coreController->m_serversController->getDefaultServerIndex() >= 0) { + QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); }); + } +} + +void CoreSignalHandlers::initAmneziaDnsToggledHandler() +{ + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::useAmneziaDnsChanged, m_coreController->m_serversUiController, &ServersUiController::updateModel); +} + +void CoreSignalHandlers::initServersModelUpdateHandler() +{ + connect(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded, + m_coreController->m_serversUiController, &ServersUiController::updateModel); + connect(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited, + m_coreController->m_serversUiController, &ServersUiController::updateModel); + connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved, + m_coreController->m_serversUiController, &ServersUiController::updateModel); + connect(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged, + m_coreController->m_serversUiController, &ServersUiController::onDefaultServerChanged); + + connect(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded, + m_coreController->m_serversController, &ServersController::recomputeGatewayStacks); + connect(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited, + m_coreController->m_serversController, &ServersController::recomputeGatewayStacks); + connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved, + m_coreController->m_serversController, &ServersController::recomputeGatewayStacks); + + connect(m_coreController->m_settingsUiController, &SettingsUiController::restoreBackupFinished, + m_coreController->m_serversUiController, &ServersUiController::updateModel); +} + +void CoreSignalHandlers::initClientManagementModelUpdateHandler() +{ + connect(m_coreController->m_usersController, &UsersController::clientsUpdated, + m_coreController->m_clientManagementModel, &ClientManagementModel::updateModel); + connect(m_coreController->m_usersController, &UsersController::clientRenamed, + m_coreController->m_clientManagementModel, &ClientManagementModel::updateClientName); +} + +void CoreSignalHandlers::initSitesModelUpdateHandler() +{ + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::sitesChanged, m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::updateModel); + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::sitesSplitTunnelingEnabledChanged, m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::updateModel); + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::routeModeChanged, m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::updateModel); +} + +void CoreSignalHandlers::initAllowedDnsModelUpdateHandler() +{ + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::allowedDnsServersChanged, m_coreController->m_allowedDnsUiController, &AllowedDnsUiController::updateModel); +} + +void CoreSignalHandlers::initAppSplitTunnelingModelUpdateHandler() +{ + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsChanged, m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::updateModel); + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsSplitTunnelingEnabledChanged, m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::updateModel); + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsRouteModeChanged, m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::updateModel); +} + +void CoreSignalHandlers::initPrepareConfigHandler() +{ + connect(m_coreController->m_connectionUiController, &ConnectionUiController::prepareConfig, this, [this]() { + m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Preparing); + + m_coreController->m_subscriptionUiController->validateConfig(); + }); + + connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::configValidated, this, [this](bool isValid) { + if (!isValid) { + m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected); + return; + } + + m_coreController->m_installUiController->validateConfig(); + }); + + connect(m_coreController->m_installUiController, &InstallUiController::configValidated, this, [this](bool isValid) { + if (!isValid) { + m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected); + return; + } + + m_coreController->m_connectionUiController->openConnection(); + }); +} + +void CoreSignalHandlers::initStrictKillSwitchHandler() +{ + connect(m_coreController->m_settingsUiController, &SettingsUiController::strictKillSwitchEnabledChanged, m_coreController->m_connectionController, + &ConnectionController::onKillSwitchModeChanged); +} + +void CoreSignalHandlers::initAndroidSettingsHandler() +{ +#ifdef Q_OS_ANDROID + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs); + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled); + connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer); + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); }); +#endif +} + +void CoreSignalHandlers::initAndroidConnectionHandler() +{ +#ifdef Q_OS_ANDROID + connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) { + m_coreController->m_connectionUiController->onConnectionStateChanged(state); + m_coreController->m_connectionController->restoreConnection(); + }); + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) { + emit m_coreController->m_pageController->goToPageHome(); + m_coreController->m_importController->extractConfigFromData(data); + data.clear(); + emit m_coreController->m_pageController->goToPageViewConfig(); + }); +#endif +} + +void CoreSignalHandlers::initIosImportHandler() +{ +#ifdef Q_OS_IOS + connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) { + emit m_coreController->m_pageController->goToPageHome(); + m_coreController->m_importController->extractConfigFromData(data); + emit m_coreController->m_pageController->goToPageViewConfig(); + }); + connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) { + emit m_coreController->m_pageController->goToPageHome(); + m_coreController->m_pageController->goToPageSettingsBackup(); + emit m_coreController->m_settingsUiController->importBackupFromOutside(filePath); + }); +#endif +} + +void CoreSignalHandlers::initIosSettingsHandler() +{ +#ifdef Q_OS_IOS + connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); }); +#endif +} + +void CoreSignalHandlers::initNotificationHandler() +{ +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + m_coreController->m_notificationHandler = NotificationHandler::create(m_coreController); + + connect(m_coreController->m_connectionController, &ConnectionController::connectionStateChanged, m_coreController->m_notificationHandler, + &NotificationHandler::setConnectionState); + + connect(m_coreController->m_notificationHandler, &NotificationHandler::raiseRequested, m_coreController->m_pageController, &PageController::raiseMainWindow); + connect(m_coreController->m_notificationHandler, &NotificationHandler::connectRequested, m_coreController->m_connectionUiController, + static_cast(&ConnectionUiController::openConnection)); + connect(m_coreController->m_notificationHandler, &NotificationHandler::disconnectRequested, m_coreController->m_connectionUiController, + &ConnectionUiController::closeConnection); + connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_notificationHandler, &NotificationHandler::onTranslationsUpdated); + + auto* trayHandler = qobject_cast(m_coreController->m_notificationHandler); + connect(m_coreController, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl); +#endif +} + diff --git a/client/core/controllers/coreSignalHandlers.h b/client/core/controllers/coreSignalHandlers.h new file mode 100644 index 000000000..33e567ce8 --- /dev/null +++ b/client/core/controllers/coreSignalHandlers.h @@ -0,0 +1,48 @@ +#ifndef CORESIGNALHANDLERS_H +#define CORESIGNALHANDLERS_H + +#include +#include "core/controllers/coreController.h" + +class CoreSignalHandlers : public QObject +{ + Q_OBJECT + +public: + explicit CoreSignalHandlers(CoreController* coreController, QObject* parent = nullptr); + + void initAllHandlers(); + +private: + void initErrorMessagesHandler(); + void initSettingsSplitTunnelingHandler(); + void initInstallControllerHandler(); + void initExportControllerHandler(); + void initImportControllerHandler(); + void initApiCountryModelUpdateHandler(); + void initSubscriptionRefreshHandler(); + void initContainerModelUpdateHandler(); + void initAdminConfigRevokedHandler(); + void initPassphraseRequestHandler(); + void initTranslationsUpdatedHandler(); + void initLanguageHandler(); + void initAutoConnectHandler(); + void initAmneziaDnsToggledHandler(); + void initServersModelUpdateHandler(); + void initClientManagementModelUpdateHandler(); + void initSitesModelUpdateHandler(); + void initAllowedDnsModelUpdateHandler(); + void initAppSplitTunnelingModelUpdateHandler(); + void initPrepareConfigHandler(); + void initStrictKillSwitchHandler(); + void initAndroidSettingsHandler(); + void initAndroidConnectionHandler(); + void initIosImportHandler(); + void initIosSettingsHandler(); + void initNotificationHandler(); + + CoreController* m_coreController; +}; + +#endif // CORESIGNALHANDLERS_H + diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index 4631eac80..79d959fa1 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -15,27 +15,18 @@ #include "QBlockCipher.h" #include "QRsa.h" -#include "amnezia_application.h" -#include "core/api/apiUtils.h" -#include "core/networkUtilities.h" -#include "utilities.h" +#include "amneziaApplication.h" +#include "core/utils/api/apiUtils.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/networkUtilities.h" +#include "core/utils/utilities.h" #ifdef AMNEZIA_DESKTOP - #include "core/ipcclient.h" + #include "core/utils/ipcClient.h" #endif namespace { - namespace configKey - { - constexpr char aesKey[] = "aes_key"; - constexpr char aesIv[] = "aes_iv"; - constexpr char aesSalt[] = "aes_salt"; - - constexpr char apiPayload[] = "api_payload"; - constexpr char keyPayload[] = "key_payload"; - } - constexpr QLatin1String errorResponsePattern1("No active configuration found for"); constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for"); constexpr QLatin1String errorResponsePattern3("Account not found."); @@ -99,9 +90,9 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const encRequestData.salt = blockCipher.generatePrivateSalt(8); QJsonObject keyPayload; - keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64()); - keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64()); - keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64()); + keyPayload[apiDefs::key::aesKey] = QString(encRequestData.key.toBase64()); + keyPayload[apiDefs::key::aesIv] = QString(encRequestData.iv.toBase64()); + keyPayload[apiDefs::key::aesSalt] = QString(encRequestData.salt.toBase64()); QByteArray encryptedKeyPayload; QByteArray encryptedApiPayload; @@ -133,8 +124,8 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const } QJsonObject requestBody; - requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64()); - requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64()); + requestBody[apiDefs::key::keyPayload] = QString(encryptedKeyPayload.toBase64()); + requestBody[apiDefs::key::apiPayload] = QString(encryptedApiPayload.toBase64()); encRequestData.requestBody = QJsonDocument(requestBody).toJson(); return encRequestData; @@ -294,6 +285,9 @@ QFuture> GatewayController::postAsync(const QString primaryBaseUrls = QString(PROD_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts); fallbackBaseUrls = QString(FALLBACK_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts); } + std::random_device randomDevice; + std::mt19937 generator(randomDevice()); + std::shuffle(baseUrls.begin(), baseUrls.end(), generator); auto appendStorageUrls = [&serviceType, &userCountryCode](const QStringList &baseUrls, QStringList &target) { if (!serviceType.isEmpty()) { diff --git a/client/core/controllers/gatewayController.h b/client/core/controllers/gatewayController.h index 96e842535..ef2994709 100644 --- a/client/core/controllers/gatewayController.h +++ b/client/core/controllers/gatewayController.h @@ -8,7 +8,9 @@ #include #include -#include "core/defs.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" #ifdef Q_OS_IOS #include "platforms/ios/ios_controller.h" diff --git a/client/core/controllers/ipSplitTunnelingController.cpp b/client/core/controllers/ipSplitTunnelingController.cpp new file mode 100644 index 000000000..e945e7bd1 --- /dev/null +++ b/client/core/controllers/ipSplitTunnelingController.cpp @@ -0,0 +1,245 @@ +#include "ipSplitTunnelingController.h" +#include "core/utils/networkUtilities.h" +#include + +IpSplitTunnelingController::IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent) + : QObject(parent), + m_appSettingsRepository(appSettingsRepository) +{ + m_currentRouteMode = m_appSettingsRepository->routeMode(); + if (m_currentRouteMode == RouteMode::VpnAllSites) { // for old split tunneling configs + m_appSettingsRepository->setRouteMode(RouteMode::VpnOnlyForwardSites); + m_currentRouteMode = RouteMode::VpnOnlyForwardSites; + } + fillSites(); +} + +bool IpSplitTunnelingController::addSiteInternal(const QString &hostname, const QString &ip) +{ + QVariantMap existing = m_appSettingsRepository->vpnSites(m_currentRouteMode); + if (existing.contains(hostname) && ip.isEmpty()) { + return false; + } + + for (int i = 0; i < m_sites.size(); i++) { + if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) { + m_sites[i].second = ip; + m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip); + return true; + } else if (m_sites[i].first == hostname && (m_sites[i].second == ip)) { + return false; + } + } + m_sites.append(qMakePair(hostname, ip)); + m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip); + return true; +} + +void IpSplitTunnelingController::addSites(const QMap &sites, bool replaceExisting) +{ + if (replaceExisting) { + m_sites.clear(); + } + for (auto it = sites.constBegin(); it != sites.constEnd(); ++it) { + const QString &hostname = it.key(); + const QString &ip = it.value(); + bool found = false; + for (int i = 0; i < m_sites.size(); i++) { + if (m_sites[i].first == hostname) { + if (!ip.isEmpty()) { + m_sites[i].second = ip; + } + found = true; + break; + } + } + if (!found) { + m_sites.append(qMakePair(hostname, ip)); + } + } + if (replaceExisting) { + m_appSettingsRepository->removeAllVpnSites(m_currentRouteMode); + } + m_appSettingsRepository->addVpnSites(m_currentRouteMode, sites); +} + +bool IpSplitTunnelingController::addSite(const QString &hostname) +{ + QString normalizedHostname = normalizeHostname(hostname); + + if (!validateHostname(normalizedHostname)) { + return false; + } + + if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(normalizedHostname)) { + processSite(normalizedHostname, ""); + return true; + } + + if (addSiteInternal(normalizedHostname, "")) { + QHostInfo::lookupHost(normalizedHostname, this, SLOT(onHostResolved(QHostInfo))); + return true; + } + + return false; +} + +bool IpSplitTunnelingController::removeSite(const QString &hostname) +{ + for (int i = 0; i < m_sites.size(); i++) { + if (m_sites[i].first == hostname) { + m_sites.removeAt(i); + m_appSettingsRepository->removeVpnSite(m_currentRouteMode, hostname); + return true; + } + } + return false; +} + +void IpSplitTunnelingController::removeSites() +{ + m_sites.clear(); + m_appSettingsRepository->removeAllVpnSites(m_currentRouteMode); +} + +void IpSplitTunnelingController::setRouteMode(RouteMode routeMode) +{ + m_currentRouteMode = routeMode; + fillSites(); + m_appSettingsRepository->setRouteMode(routeMode); +} + +void IpSplitTunnelingController::toggleSplitTunneling(bool enabled) +{ + m_appSettingsRepository->setSitesSplitTunnelingEnabled(enabled); +} + +RouteMode IpSplitTunnelingController::getRouteMode() const +{ + return m_currentRouteMode; +} + +bool IpSplitTunnelingController::isSplitTunnelingEnabled() const +{ + return m_appSettingsRepository->isSitesSplitTunnelingEnabled(); +} + +QVector> IpSplitTunnelingController::getCurrentSites() const +{ + return m_sites; +} + +void IpSplitTunnelingController::fillSites() +{ + QVariantMap sitesMap = m_appSettingsRepository->vpnSites(m_currentRouteMode); + m_sites.clear(); + for (auto it = sitesMap.begin(); it != sitesMap.end(); ++it) { + m_sites.append(qMakePair(it.key(), it.value().toString())); + } +} + +QString IpSplitTunnelingController::normalizeHostname(const QString &hostname) const +{ + QString normalized = hostname; + normalized.replace("https://", ""); + normalized.replace("http://", ""); + normalized.replace("ftp://", ""); + normalized = normalized.split("/", Qt::SkipEmptyParts).first(); + return normalized; +} + +bool IpSplitTunnelingController::validateHostname(const QString &hostname) const +{ + if (hostname.isEmpty()) { + return false; + } + if (!hostname.contains(".") && !NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + return false; + } + return true; +} + + +void IpSplitTunnelingController::onHostResolved(const QHostInfo &hostInfo) +{ + const QList &addresses = hostInfo.addresses(); + QString hostname = hostInfo.hostName(); + + for (const QHostAddress &addr : addresses) { + if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { + processSiteAfterResolve(hostname, addr.toString()); + break; + } + } +} + +void IpSplitTunnelingController::processSiteAfterResolve(const QString &hostname, const QString &ip) +{ + for (int i = 0; i < m_sites.size(); i++) { + if (m_sites[i].first == hostname && m_sites[i].second.isEmpty()) { + m_sites[i].second = ip; + m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip); + break; + } + } +} + +void IpSplitTunnelingController::processSite(const QString &hostname, const QString &ip) +{ + addSiteInternal(hostname, ip); +} + +bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage) +{ + QJsonParseError parseError; + QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData, &parseError); + + if (parseError.error != QJsonParseError::NoError) { + errorMessage = tr("Failed to parse JSON data: %1").arg(parseError.errorString()); + return false; + } + + if (!jsonDocument.isArray()) { + errorMessage = tr("The JSON data is not an array"); + return false; + } + + QJsonArray jsonArray = jsonDocument.array(); + QMap sites; + + for (auto jsonValue : jsonArray) { + QJsonObject jsonObject = jsonValue.toObject(); + QString hostname = jsonObject.value("hostname").toString(""); + QString ip = jsonObject.value("ip").toString(""); + + QString normalizedHostname = normalizeHostname(hostname); + + if (!validateHostname(normalizedHostname)) { + qDebug() << normalizedHostname << " not look like ip adress or domain name"; + continue; + } + + sites.insert(normalizedHostname, ip); + } + + addSites(sites, replaceExisting); + + return true; +} + +QByteArray IpSplitTunnelingController::exportSitesToJson() const +{ + QVector> sites = getCurrentSites(); + QJsonArray jsonArray; + + for (const auto &site : sites) { + QJsonObject jsonObject; + jsonObject["hostname"] = site.first; + jsonObject["ip"] = site.second; + jsonArray.append(jsonObject); + } + + QJsonDocument jsonDocument(jsonArray); + return jsonDocument.toJson(); +} + diff --git a/client/core/controllers/ipSplitTunnelingController.h b/client/core/controllers/ipSplitTunnelingController.h new file mode 100644 index 000000000..5a216ff7d --- /dev/null +++ b/client/core/controllers/ipSplitTunnelingController.h @@ -0,0 +1,58 @@ +#ifndef IPSPLITTUNNELINGCONTROLLER_H +#define IPSPLITTUNNELINGCONTROLLER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/repositories/secureAppSettingsRepository.h" + +using namespace amnezia; + +class IpSplitTunnelingController : public QObject +{ + Q_OBJECT + +public: + explicit IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent = nullptr); + + bool addSite(const QString &hostname); + void addSites(const QMap &sites, bool replaceExisting); + bool removeSite(const QString &hostname); + void removeSites(); + void setRouteMode(RouteMode routeMode); + void toggleSplitTunneling(bool enabled); + + RouteMode getRouteMode() const; + bool isSplitTunnelingEnabled() const; + QVector> getCurrentSites() const; + + bool importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage); + QByteArray exportSitesToJson() const; + +private slots: + void onHostResolved(const QHostInfo &hostInfo); + +private: + void fillSites(); + bool addSiteInternal(const QString &hostname, const QString &ip); + QString normalizeHostname(const QString &hostname) const; + bool validateHostname(const QString &hostname) const; + void processSiteAfterResolve(const QString &hostname, const QString &ip); + void processSite(const QString &hostname, const QString &ip); + + SecureAppSettingsRepository* m_appSettingsRepository; + RouteMode m_currentRouteMode; + QVector> m_sites; +}; + +#endif // IPSPLITTUNNELINGCONTROLLER_H + diff --git a/client/core/controllers/selfhosted/exportController.cpp b/client/core/controllers/selfhosted/exportController.cpp new file mode 100644 index 000000000..917304c49 --- /dev/null +++ b/client/core/controllers/selfhosted/exportController.cpp @@ -0,0 +1,337 @@ +#include "exportController.h" + +#include +#include + +#include "core/configurators/configuratorBase.h" +#include "core/utils/selfhosted/sshSession.h" +#include "core/utils/networkUtilities.h" +#include "core/utils/qrCodeUtils.h" +#include "core/utils/serialization/serialization.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/models/serverConfig.h" +#include "core/models/containerConfig.h" +#include "core/models/protocolConfig.h" + +using namespace amnezia; + +ExportController::ExportController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository, + QObject *parent) + : QObject(parent), + m_serversRepository(serversRepository), + m_appSettingsRepository(appSettingsRepository) +{ +} + +ExportController::ExportResult ExportController::generateFullAccessConfig(int serverIndex) +{ + ExportResult result; + + ServerConfig serverConfig = m_serversRepository->server(serverIndex); + serverConfig.visit([](auto& arg) { + for (auto it = arg.containers.begin(); it != arg.containers.end(); ++it) { + it.value().protocolConfig.clearClientConfig(); + } + }); + + QJsonObject serverJson = serverConfig.toJson(); + QByteArray compressedConfig = QJsonDocument(serverJson).toJson(); + compressedConfig = qCompress(compressedConfig, 8); + result.config = generateVpnUrl(compressedConfig); + result.qrCodes = generateQrCodesFromConfig(compressedConfig); + + return result; +} + +ExportController::ExportResult ExportController::generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName) +{ + ExportResult result; + + DockerContainer container = static_cast(containerIndex); + ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex); + ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container); + + if (ContainerUtils::containerService(container) != ServiceType::Other) { + SshSession sshSession; + Proto protocol = ContainerUtils::defaultProtocol(container); + + DnsSettings dnsSettings = { + m_appSettingsRepository->primaryDns(), + m_appSettingsRepository->secondaryDns() + }; + + auto configurator = ConfiguratorBase::create(protocol, &sshSession); + ProtocolConfig newProtocolConfig = configurator->createConfig(credentials, container, containerConfig, dnsSettings, result.errorCode); + if (result.errorCode != ErrorCode::NoError) { + return result; + } + + containerConfig.protocolConfig = newProtocolConfig; + + QString clientId = newProtocolConfig.clientId(); + if (!clientId.isEmpty()) { + emit appendClientRequested(serverIndex, clientId, clientName, container); + } + } + + ServerConfig serverConfig = m_serversRepository->server(serverIndex); + serverConfig.visit([container, containerConfig](auto& arg) { + arg.containers.clear(); + arg.containers[container] = containerConfig; + arg.defaultContainer = container; + }); + + if (serverConfig.isSelfHosted()) { + SelfHostedServerConfig* selfHosted = serverConfig.as(); + if (selfHosted) { + selfHosted->userName.reset(); + selfHosted->password.reset(); + selfHosted->port.reset(); + } + } + + auto dns = serverConfig.getDnsPair(m_appSettingsRepository->useAmneziaDns(), + m_appSettingsRepository->primaryDns(), + m_appSettingsRepository->secondaryDns()); + serverConfig.visit([&dns](auto& arg) { + arg.dns1 = dns.first; + arg.dns2 = dns.second; + }); + + QJsonObject serverJson = serverConfig.toJson(); + QByteArray compressedConfig = QJsonDocument(serverJson).toJson(); + compressedConfig = qCompress(compressedConfig, 8); + result.config = generateVpnUrl(compressedConfig); + result.qrCodes = generateQrCodesFromConfig(compressedConfig); + + return result; +} + +ExportController::NativeConfigResult ExportController::generateNativeConfig(int serverIndex, DockerContainer container, + const ContainerConfig &containerConfig, + const QString &clientName) +{ + NativeConfigResult result; + + if (ContainerUtils::containerService(container) == ServiceType::Other) { + return result; + } + + Proto protocol = ContainerUtils::defaultProtocol(container); + + ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex); + ServerConfig serverConfig = m_serversRepository->server(serverIndex); + auto dns = serverConfig.getDnsPair(m_appSettingsRepository->useAmneziaDns(), + m_appSettingsRepository->primaryDns(), + m_appSettingsRepository->secondaryDns()); + + ContainerConfig modifiedContainerConfig = containerConfig; + modifiedContainerConfig.container = container; + + DnsSettings dnsSettings = { + m_appSettingsRepository->primaryDns(), + m_appSettingsRepository->secondaryDns() + }; + + SshSession sshSession; + auto configurator = ConfiguratorBase::create(protocol, &sshSession); + + ProtocolConfig newProtocolConfig = configurator->createConfig(credentials, container, modifiedContainerConfig, dnsSettings, result.errorCode); + if (result.errorCode != ErrorCode::NoError) { + return result; + } + + ExportSettings exportSettings = { { dns.first, dns.second } }; + ProtocolConfig processedConfig = configurator->processConfigWithExportSettings(exportSettings, newProtocolConfig); + + if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg) { + result.jsonNativeConfig[configKey::config] = processedConfig.nativeConfig(); + } else { + result.jsonNativeConfig = QJsonDocument::fromJson(processedConfig.nativeConfig().toUtf8()).object(); + } + + if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg || protocol == Proto::Xray) { + QString clientId = newProtocolConfig.clientId(); + if (!clientId.isEmpty()) { + emit appendClientRequested(serverIndex, clientId, clientName, container); + } + } + return result; +} + +ExportController::ExportResult ExportController::generateOpenVpnConfig(int serverIndex, const QString &clientName) +{ + ExportResult result; + + DockerContainer container = DockerContainer::OpenVpn; + ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container); + + auto nativeResult = generateNativeConfig(serverIndex, container, containerConfig, clientName); + if (nativeResult.errorCode != ErrorCode::NoError) { + result.errorCode = nativeResult.errorCode; + return result; + } + + QStringList lines = nativeResult.jsonNativeConfig.value(configKey::config).toString().replace("\r", "").split("\n"); + for (const QString &line : std::as_const(lines)) { + result.config.append(line + "\n"); + } + + result.qrCodes = generateQrCodesFromConfig(result.config.toUtf8()); + return result; +} + +ExportController::ExportResult ExportController::generateWireGuardConfig(int serverIndex, const QString &clientName) +{ + ExportResult result; + + ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, DockerContainer::WireGuard); + + auto nativeResult = generateNativeConfig(serverIndex, DockerContainer::WireGuard, containerConfig, clientName); + if (nativeResult.errorCode != ErrorCode::NoError) { + result.errorCode = nativeResult.errorCode; + return result; + } + + QStringList lines = nativeResult.jsonNativeConfig.value(configKey::config).toString().replace("\r", "").split("\n"); + for (const QString &line : std::as_const(lines)) { + result.config.append(line + "\n"); + } + + result.qrCodes << generateSingleQrCode(result.config.toUtf8()); + return result; +} + +ExportController::ExportResult ExportController::generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName) +{ + ExportResult result; + + DockerContainer container = static_cast(containerIndex); + if (container != DockerContainer::Awg && container != DockerContainer::Awg2) { + result.errorCode = ErrorCode::InternalError; + return result; + } + ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container); + + auto nativeResult = generateNativeConfig(serverIndex, container, containerConfig, clientName); + if (nativeResult.errorCode != ErrorCode::NoError) { + result.errorCode = nativeResult.errorCode; + return result; + } + + QStringList lines = nativeResult.jsonNativeConfig.value(configKey::config).toString().replace("\r", "").split("\n"); + for (const QString &line : std::as_const(lines)) { + result.config.append(line + "\n"); + } + + result.qrCodes << generateSingleQrCode(result.config.toUtf8()); + return result; +} + + +ExportController::ExportResult ExportController::generateXrayConfig(int serverIndex, const QString &clientName) +{ + ExportResult result; + + ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, DockerContainer::Xray); + + auto nativeResult = generateNativeConfig(serverIndex, DockerContainer::Xray, containerConfig, clientName); + if (nativeResult.errorCode != ErrorCode::NoError) { + result.errorCode = nativeResult.errorCode; + return result; + } + + QStringList lines = QString(QJsonDocument(nativeResult.jsonNativeConfig).toJson()).replace("\r", "").split("\n"); + for (const QString &line : std::as_const(lines)) { + result.config.append(line + "\n"); + } + + // Parse the Xray data to extract VLESS parameters and generate string + QJsonObject xrayConfig = nativeResult.jsonNativeConfig; + QJsonArray outbounds = xrayConfig.value(amnezia::protocols::xray::outbounds).toArray(); + + if (outbounds.isEmpty()) { + result.errorCode = ErrorCode::InternalError; + return result; + } + + QJsonObject outbound = outbounds[0].toObject(); + QJsonObject settings = outbound.value(amnezia::protocols::xray::settings).toObject(); + QJsonObject streamSettings = outbound.value(amnezia::protocols::xray::streamSettings).toObject(); + + QJsonArray vnext = settings.value(amnezia::protocols::xray::vnext).toArray(); + if (vnext.isEmpty()) { + result.errorCode = ErrorCode::InternalError; + return result; + } + + QJsonObject server = vnext[0].toObject(); + QJsonArray users = server.value(amnezia::protocols::xray::users).toArray(); + if (users.isEmpty()) { + result.errorCode = ErrorCode::InternalError; + return result; + } + + QJsonObject user = users[0].toObject(); + + amnezia::serialization::VlessServerObject vlessServer; + vlessServer.address = server.value(amnezia::protocols::xray::address).toString(); + vlessServer.port = server.value(amnezia::protocols::xray::port).toInt(); + vlessServer.id = user.value(amnezia::protocols::xray::id).toString(); + vlessServer.flow = user.value(amnezia::protocols::xray::flow).toString("xtls-rprx-vision"); + vlessServer.encryption = user.value(amnezia::protocols::xray::encryption).toString("none"); + + vlessServer.network = streamSettings.value(amnezia::protocols::xray::network).toString("tcp"); + vlessServer.security = streamSettings.value(amnezia::protocols::xray::security).toString("reality"); + + if (vlessServer.security == "reality") { + QJsonObject realitySettings = streamSettings.value(amnezia::protocols::xray::realitySettings).toObject(); + vlessServer.serverName = realitySettings.value(amnezia::protocols::xray::serverName).toString(); + vlessServer.publicKey = realitySettings.value(amnezia::protocols::xray::publicKey).toString(); + vlessServer.shortId = realitySettings.value(amnezia::protocols::xray::shortId).toString(); + vlessServer.fingerprint = realitySettings.value(amnezia::protocols::xray::fingerprint).toString("chrome"); + vlessServer.spiderX = realitySettings.value(amnezia::protocols::xray::spiderX).toString(""); + } + + result.nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "AmneziaVPN"); + + return result; +} + +void ExportController::updateClientManagementModel(int serverIndex, int containerIndex) +{ + DockerContainer container = static_cast(containerIndex); + emit updateClientsRequested(serverIndex, container); +} + +void ExportController::revokeConfig(int row, int serverIndex, int containerIndex) +{ + DockerContainer container = static_cast(containerIndex); + emit revokeClientRequested(serverIndex, row, container); +} + +void ExportController::renameClient(int row, const QString &clientName, int serverIndex, int containerIndex) +{ + DockerContainer container = static_cast(containerIndex); + emit renameClientRequested(serverIndex, row, clientName, container); +} + +QString ExportController::generateVpnUrl(const QByteArray &compressedConfig) +{ + return QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); +} + +QList ExportController::generateQrCodesFromConfig(const QByteArray &data) +{ + return qrCodeUtils::generateQrCodeImageSeries(data); +} + +QString ExportController::generateSingleQrCode(const QByteArray &data) +{ + auto qr = qrCodeUtils::generateQrCode(data); + return qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); +} diff --git a/client/core/controllers/selfhosted/exportController.h b/client/core/controllers/selfhosted/exportController.h new file mode 100644 index 000000000..2f2648c1c --- /dev/null +++ b/client/core/controllers/selfhosted/exportController.h @@ -0,0 +1,77 @@ +#ifndef EXPORTCONTROLLER_H +#define EXPORTCONTROLLER_H + +#include +#include +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/repositories/secureServersRepository.h" +#include "core/repositories/secureAppSettingsRepository.h" + +class SshSession; +class VpnConfigurationsController; + +using namespace amnezia; + +class ExportController : public QObject +{ + Q_OBJECT + +public: + struct ExportResult + { + ErrorCode errorCode = ErrorCode::NoError; + QString config; + QString nativeConfigString; + QList qrCodes; + }; + + explicit ExportController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository, + QObject *parent = nullptr); + + ExportResult generateFullAccessConfig(int serverIndex); + ExportResult generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName); + ExportResult generateOpenVpnConfig(int serverIndex, const QString &clientName); + ExportResult generateWireGuardConfig(int serverIndex, const QString &clientName); + ExportResult generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName); + ExportResult generateXrayConfig(int serverIndex, const QString &clientName); + +signals: + void appendClientRequested(int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container); + void updateClientsRequested(int serverIndex, DockerContainer container); + void revokeClientRequested(int serverIndex, int row, DockerContainer container); + void renameClientRequested(int serverIndex, int row, const QString &clientName, DockerContainer container); + +public slots: + void updateClientManagementModel(int serverIndex, int containerIndex); + void revokeConfig(int row, int serverIndex, int containerIndex); + void renameClient(int row, const QString &clientName, int serverIndex, int containerIndex); + +private: + struct NativeConfigResult + { + ErrorCode errorCode = ErrorCode::NoError; + QJsonObject jsonNativeConfig; + }; + + NativeConfigResult generateNativeConfig(int serverIndex, DockerContainer container, + const ContainerConfig &containerConfig, + const QString &clientName); + + QString generateVpnUrl(const QByteArray &compressedConfig); + QList generateQrCodesFromConfig(const QByteArray &data); + QString generateSingleQrCode(const QByteArray &data); + + SecureServersRepository* m_serversRepository; + SecureAppSettingsRepository* m_appSettingsRepository; +}; + +#endif // EXPORTCONTROLLER_H diff --git a/client/core/controllers/selfhosted/importController.cpp b/client/core/controllers/selfhosted/importController.cpp new file mode 100644 index 000000000..c1c7503eb --- /dev/null +++ b/client/core/controllers/selfhosted/importController.cpp @@ -0,0 +1,762 @@ +#include "importController.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" +#include "core/utils/api/apiUtils.h" +#include "core/utils/serialization/serialization.h" +#include "core/utils/utilities.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/qrCodeUtils.h" +#include "core/models/serverConfig.h" + +using namespace amnezia; +using namespace ProtocolUtils; + +namespace +{ + ConfigTypes checkConfigFormat(const QString &config) + { + const QString openVpnConfigPatternCli = "client"; + const QString openVpnConfigPatternDriver1 = "dev tun"; + const QString openVpnConfigPatternDriver2 = "dev tap"; + + const QString wireguardConfigPatternSectionInterface = "[Interface]"; + const QString wireguardConfigPatternSectionPeer = "[Peer]"; + + const QString xrayConfigPatternInbound = "inbounds"; + const QString xrayConfigPatternOutbound = "outbounds"; + + const QString amneziaConfigPattern = "containers"; + const QString amneziaConfigPatternHostName = "hostName"; + const QString amneziaConfigPatternUserName = "userName"; + const QString amneziaConfigPatternPassword = "password"; + const QString amneziaFreeConfigPattern = "api_key"; + const QString amneziaPremiumConfigPattern = "auth_data"; + const QString backupPattern = "Servers/serversList"; + + if (config.contains(backupPattern)) { + return ConfigTypes::Backup; + } else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) + || config.contains(amneziaPremiumConfigPattern) + || (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName) + && config.contains(amneziaConfigPatternPassword))) { + return ConfigTypes::Amnezia; + } else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) { + return ConfigTypes::WireGuard; + } else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) { + return ConfigTypes::Xray; + } else if (config.contains(openVpnConfigPatternCli) + && (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { + return ConfigTypes::OpenVpn; + } + return ConfigTypes::Invalid; + } +} // namespace + +ImportController::ImportController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository, + QObject *parent) + : QObject(parent), + m_serversRepository(serversRepository), + m_appSettingsRepository(appSettingsRepository) +{ +} + +ImportController::ImportResult ImportController::extractConfigFromData(const QString &data, const QString &configFileName) +{ + ImportResult result; + result.configFileName = configFileName; + result.maliciousWarningText.clear(); + + QString config = data; + QString prefix; + QString errormsg; + ConfigTypes configType = ConfigTypes::Invalid; + + if (config.startsWith("vless://")) { + configType = ConfigTypes::Xray; + result.config = extractXrayConfig( + Utils::JsonToString(serialization::vless::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact), + configType, prefix); + if (!result.config.empty()) { + result.configType = configType; + return result; + } + } + + if (config.startsWith("vmess://") && config.contains("@")) { + configType = ConfigTypes::Xray; + result.config = extractXrayConfig( + Utils::JsonToString(serialization::vmess_new::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact), + configType, prefix); + if (!result.config.empty()) { + result.configType = configType; + return result; + } + } + + if (config.startsWith("vmess://")) { + configType = ConfigTypes::Xray; + result.config = extractXrayConfig( + Utils::JsonToString(serialization::vmess::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact), + configType, prefix); + if (!result.config.empty()) { + result.configType = configType; + return result; + } + } + + if (config.startsWith("trojan://")) { + configType = ConfigTypes::Xray; + result.config = extractXrayConfig( + Utils::JsonToString(serialization::trojan::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact), + configType, prefix); + if (!result.config.empty()) { + result.configType = configType; + return result; + } + } + + if (config.startsWith("ss://") && !config.contains("plugin=")) { + configType = ConfigTypes::ShadowSocks; + result.config = extractXrayConfig( + Utils::JsonToString(serialization::ss::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact), + configType, prefix); + if (!result.config.empty()) { + result.configType = configType; + return result; + } + } + + if (config.startsWith("ssd://")) { + QStringList tmp; + QList> servers = serialization::ssd::Deserialize(config, &prefix, &tmp); + configType = ConfigTypes::ShadowSocks; + // Took only first config from list + if (!servers.isEmpty()) { + result.config = extractXrayConfig(servers.first().first, configType); + } + if (!result.config.empty()) { + result.configType = configType; + return result; + } + } + + configType = checkConfigFormat(config); + if (configType == ConfigTypes::Invalid) { + config.replace("vpn://", ""); + QByteArray ba = QByteArray::fromBase64(config.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QByteArray baUncompressed = qUncompress(ba); + if (!baUncompressed.isEmpty()) { + ba = baUncompressed; + } + + config = ba; + configType = checkConfigFormat(config); + } + + result.configType = configType; + + switch (configType) { + case ConfigTypes::OpenVpn: { + result.config = extractOpenVpnConfig(config); + if (!result.config.empty()) { + checkForMaliciousStrings(result.config, result.maliciousWarningText); + return result; + } + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + case ConfigTypes::Awg: + case ConfigTypes::WireGuard: { + result.config = extractWireGuardConfig(config, result.configType); + result.isNativeWireGuardConfig = (result.configType == ConfigTypes::WireGuard); + if (!result.config.empty()) { + return result; + } + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + case ConfigTypes::Xray: { + result.config = extractXrayConfig(config, configType); + if (!result.config.empty()) { + return result; + } + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + case ConfigTypes::Amnezia: { + result.config = QJsonDocument::fromJson(config.toUtf8()).object(); + + if (apiUtils::isServerFromApi(result.config)) { + auto apiConfig = result.config.value(apiDefs::key::apiConfig).toObject(); + apiConfig[apiDefs::key::vpnKey] = data; + result.config[apiDefs::key::apiConfig] = apiConfig; + } + + processAmneziaConfig(result.config); + if (!result.config.empty()) { + checkForMaliciousStrings(result.config, result.maliciousWarningText); + return result; + } + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + case ConfigTypes::Backup: { + result.errorCode = ErrorCode::ImportBackupFileUseRestoreInstead; + return result; + } + case ConfigTypes::Invalid: { + result.errorCode = ErrorCode::ImportInvalidConfigError; + result.configFileName.clear(); + return result; + } + } + + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; +} + +ImportController::ImportResult ImportController::extractConfigFromQr(const QByteArray &data) +{ + ImportResult result; + + QString dataStr = QString::fromUtf8(data); + ConfigTypes configType = checkConfigFormat(dataStr); + if (configType != ConfigTypes::Invalid) { + return extractConfigFromData(dataStr, ""); + } + + QJsonObject dataObj = QJsonDocument::fromJson(data).object(); + if (!dataObj.isEmpty()) { + result.config = dataObj; + result.configType = ConfigTypes::Amnezia; + return result; + } + + QByteArray ba_uncompressed = qUncompress(data); + if (!ba_uncompressed.isEmpty()) { + result.config = QJsonDocument::fromJson(ba_uncompressed).object(); + if (result.config.isEmpty()) { + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + result.configType = ConfigTypes::Amnezia; + return result; + } + + QByteArray ba = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QByteArray baUncompressed = qUncompress(ba); + + if (!baUncompressed.isEmpty()) { + ba = baUncompressed; + } + + if (!ba.isEmpty()) { + result.config = QJsonDocument::fromJson(ba).object(); + if (result.config.isEmpty()) { + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; + } + result.configType = ConfigTypes::Amnezia; + return result; + } + + result.errorCode = ErrorCode::ImportInvalidConfigError; + return result; +} + +void ImportController::startDecodingQr() +{ + m_qrCodeChunks.clear(); + m_totalQrCodeChunksCount = 0; + m_receivedQrCodeChunksCount = 0; + m_isQrCodeProcessed = true; +} + +ImportController::QrParseResult ImportController::parseQrCodeChunk(const QString &code) +{ + QrParseResult parseResult; + parseResult.chunksReceived = m_receivedQrCodeChunksCount; + parseResult.chunksTotal = m_totalQrCodeChunksCount; + + if (!m_isQrCodeProcessed) { + return parseResult; + } + + QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QDataStream s(&ba, QIODevice::ReadOnly); + qint16 magic; + s >> magic; + + if (magic == qrCodeUtils::qrMagicCode) { + quint8 chunksCount; + s >> chunksCount; + if (m_totalQrCodeChunksCount != chunksCount) { + m_qrCodeChunks.clear(); + } + + m_totalQrCodeChunksCount = chunksCount; + + quint8 chunkId; + s >> chunkId; + s >> m_qrCodeChunks[chunkId]; + m_receivedQrCodeChunksCount = m_qrCodeChunks.size(); + parseResult.chunksReceived = m_receivedQrCodeChunksCount; + parseResult.chunksTotal = m_totalQrCodeChunksCount; + + if (m_qrCodeChunks.size() == m_totalQrCodeChunksCount) { + QByteArray data; + for (int i = 0; i < m_totalQrCodeChunksCount; ++i) { + data.append(m_qrCodeChunks.value(i)); + } + + ImportResult result = extractConfigFromQr(data); + if (result.errorCode == ErrorCode::NoError) { + parseResult.success = true; + parseResult.importResult = result; + m_isQrCodeProcessed = false; + } else { + m_qrCodeChunks.clear(); + m_totalQrCodeChunksCount = 0; + m_receivedQrCodeChunksCount = 0; + } + } + } else { + ImportResult result = extractConfigFromQr(code.toUtf8()); + if (result.errorCode != ErrorCode::NoError) { + result = extractConfigFromQr(ba); + } + if (result.errorCode == ErrorCode::NoError) { + parseResult.success = true; + parseResult.importResult = result; + m_isQrCodeProcessed = false; + } + } + + return parseResult; +} + +bool ImportController::isQrDecodingActive() const +{ + return m_isQrCodeProcessed; +} + +int ImportController::qrChunksReceived() const +{ + return m_receivedQrCodeChunksCount; +} + +int ImportController::qrChunksTotal() const +{ + return m_totalQrCodeChunksCount; +} + +void ImportController::importConfig(const QJsonObject &config) +{ + ServerCredentials credentials; + credentials.hostName = config.value(configKey::hostName).toString(); + credentials.port = config.value(configKey::port).toInt(); + credentials.userName = config.value(configKey::userName).toString(); + credentials.secretData = config.value(configKey::password).toString(); + + if (credentials.isValid() || config.contains(configKey::containers)) { + ServerConfig serverConfig = ServerConfig::fromJson(config); + m_serversRepository->addServer(serverConfig); + emit importFinished(); + } else if (config.contains(configKey::configVersion)) { + quint16 crc = qChecksum(QJsonDocument(config).toJson()); + if (m_serversRepository->hasServerWithCrc(crc)) { + emit importErrorOccurred(ErrorCode::ApiConfigAlreadyAdded, true); + } else { + QJsonObject configWithCrc = config; + configWithCrc.insert(configKey::crc, crc); + ServerConfig serverConfig = ServerConfig::fromJson(configWithCrc); + m_serversRepository->addServer(serverConfig); + emit importFinished(); + } + } else { + qDebug() << "Failed to import profile"; + qDebug().noquote() << QJsonDocument(config).toJson(); + emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false); + } +} + +QJsonObject ImportController::processNativeWireGuardConfig(const QJsonObject &config) +{ + QJsonObject result = config; + auto containers = result.value(configKey::containers).toArray(); + if (!containers.isEmpty()) { + auto container = containers.at(0).toObject(); + auto serverProtocolConfig = container.value(ContainerUtils::containerTypeToProtocolString(DockerContainer::WireGuard)).toObject(); + auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(configKey::lastConfig).toString().toUtf8()).object(); + + QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7)); + QString junkPacketMinSize = QString::number(10); + QString junkPacketMaxSize = QString::number(50); + clientProtocolConfig[configKey::junkPacketCount] = junkPacketCount; + clientProtocolConfig[configKey::junkPacketMinSize] = junkPacketMinSize; + clientProtocolConfig[configKey::junkPacketMaxSize] = junkPacketMaxSize; + clientProtocolConfig[configKey::initPacketJunkSize] = "0"; + clientProtocolConfig[configKey::responsePacketJunkSize] = "0"; + clientProtocolConfig[configKey::initPacketMagicHeader] = "1"; + clientProtocolConfig[configKey::responsePacketMagicHeader] = "2"; + clientProtocolConfig[configKey::underloadPacketMagicHeader] = "3"; + clientProtocolConfig[configKey::transportPacketMagicHeader] = "4"; + + clientProtocolConfig[configKey::cookieReplyPacketJunkSize] = "0"; + clientProtocolConfig[configKey::transportPacketJunkSize] = "0"; + + clientProtocolConfig[configKey::specialJunk1] = protocols::awg::defaultSpecialJunk1; + + clientProtocolConfig[configKey::isObfuscationEnabled] = true; + + serverProtocolConfig[configKey::lastConfig] = QString(QJsonDocument(clientProtocolConfig).toJson()); + container[configKey::wireguard] = serverProtocolConfig; + containers.replace(0, container); + result[configKey::containers] = containers; + } + return result; +} + +ConfigTypes ImportController::checkConfigFormat(const QString &config) const +{ + return ::checkConfigFormat(config); +} + +QJsonObject ImportController::extractOpenVpnConfig(const QString &data) const +{ + QJsonObject openVpnConfig; + openVpnConfig[configKey::config] = data; + + QJsonObject lastConfig; + lastConfig[configKey::lastConfig] = QString(QJsonDocument(openVpnConfig).toJson()); + lastConfig[configKey::isThirdPartyConfig] = true; + + QJsonObject containers; + containers.insert(configKey::container, QJsonValue(configKey::amneziaOpenvpn)); + containers.insert(configKey::openvpn, QJsonValue(lastConfig)); + + QJsonArray arr; + arr.push_back(containers); + + QString hostName; + const static QRegularExpression hostNameRegExp("remote\\s+([^\\s]+)"); + QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data); + if (hostNameMatch.hasMatch()) { + hostName = hostNameMatch.captured(1); + } + + QJsonObject config; + config[configKey::containers] = arr; + config[configKey::defaultContainer] = configKey::amneziaOpenvpn; + config[configKey::description] = m_appSettingsRepository->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); + if (dnsMatch.hasNext()) { + config[configKey::dns1] = dnsMatch.next().captured(1); + } + if (dnsMatch.hasNext()) { + config[configKey::dns2] = dnsMatch.next().captured(1); + } + + config[configKey::hostName] = hostName; + + return config; +} + +QJsonObject ImportController::extractWireGuardConfig(const QString &data, ConfigTypes &configType) const +{ + QMap configMap; + auto configByLines = data.split("\n"); + for (const QString &line : configByLines) { + QString trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + configMap[parts.at(0).trimmed()] = parts.at(1).trimmed(); + } + } + } + + QJsonObject lastConfig; + lastConfig[configKey::config] = data; + + auto url { QUrl::fromUserInput(configMap.value(protocols::wireguard::Endpoint)) }; + QString hostName; + QString port; + if (!url.host().isEmpty()) { + hostName = url.host(); + } else { + qDebug() << "Key parameter" << protocols::wireguard::Endpoint << "is missing or has an invalid format"; + return QJsonObject(); + } + + if (url.port() != -1) { + port = QString::number(url.port()); + } else { + port = protocols::wireguard::defaultPort; + } + + lastConfig[configKey::hostName] = hostName; + lastConfig[configKey::port] = port.toInt(); + + if (!configMap.value(protocols::wireguard::PrivateKey).isEmpty() + && !configMap.value(protocols::wireguard::Address).isEmpty() + && !configMap.value(protocols::wireguard::PublicKey).isEmpty()) { + lastConfig[configKey::clientPrivKey] = configMap.value(protocols::wireguard::PrivateKey); + lastConfig[configKey::clientIp] = configMap.value(protocols::wireguard::Address); + + if (!configMap.value(protocols::wireguard::PresharedKey).isEmpty()) { + lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PresharedKey); + } else if (!configMap.value(protocols::wireguard::PreSharedKey).isEmpty()) { + lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PreSharedKey); + } + + lastConfig[configKey::serverPubKey] = configMap.value(protocols::wireguard::PublicKey); + } else { + qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)"; + return QJsonObject(); + } + + if (!configMap.value(protocols::wireguard::MTU).isEmpty()) { + lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU); + } + + if (!configMap.value(protocols::wireguard::PersistentKeepalive).isEmpty()) { + lastConfig[configKey::persistentKeepAlive] = configMap.value(protocols::wireguard::PersistentKeepalive); + } + + QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList( + configMap.value(protocols::wireguard::AllowedIPs).split(", ")); + + lastConfig[configKey::allowedIps] = allowedIpsJsonArray; + + QString protocolName = configKey::wireguard; + QString protocolVersion; + ConfigTypes detectedType = ConfigTypes::WireGuard; + + const QStringList requiredJunkFields = { configKey::junkPacketCount, configKey::junkPacketMinSize, + configKey::junkPacketMaxSize, configKey::initPacketJunkSize, + configKey::responsePacketJunkSize, configKey::initPacketMagicHeader, + configKey::responsePacketMagicHeader, configKey::underloadPacketMagicHeader, + configKey::transportPacketMagicHeader }; + + const QStringList optionalJunkFields = { configKey::cookieReplyPacketJunkSize, + configKey::transportPacketJunkSize, + configKey::specialJunk1, configKey::specialJunk2, configKey::specialJunk3, + configKey::specialJunk4, configKey::specialJunk5 + }; + + bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(), + [&configMap](const QString &field) { return !configMap.value(field).isEmpty(); }); + if (hasAllRequiredFields) { + for (const QString &field : requiredJunkFields) { + lastConfig[field] = configMap.value(field); + } + + for (const QString &field : optionalJunkFields) { + if (!configMap.value(field).isEmpty()) { + lastConfig[field] = configMap.value(field); + } + } + + bool hasCookieReplyPacketJunkSize = !configMap.value(configKey::cookieReplyPacketJunkSize).isEmpty(); + bool hasTransportPacketJunkSize = !configMap.value(configKey::transportPacketJunkSize).isEmpty(); + bool hasSpecialJunk = !configMap.value(configKey::specialJunk1).isEmpty() || + !configMap.value(configKey::specialJunk2).isEmpty() || + !configMap.value(configKey::specialJunk3).isEmpty() || + !configMap.value(configKey::specialJunk4).isEmpty() || + !configMap.value(configKey::specialJunk5).isEmpty(); + + if (hasCookieReplyPacketJunkSize && hasTransportPacketJunkSize) { + protocolVersion = "2"; + } else if (hasSpecialJunk && !hasCookieReplyPacketJunkSize && !hasTransportPacketJunkSize) { + protocolVersion = "1.5"; + } + protocolName = configKey::awg; + detectedType = ConfigTypes::Awg; + } + + if (!configMap.value(protocols::wireguard::MTU).isEmpty()) { + lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU); + } else { + lastConfig[configKey::mtu] = (protocolName == configKey::awg) + ? protocols::awg::defaultMtu + : protocols::wireguard::defaultMtu; + } + + QJsonObject wireguardConfig; + wireguardConfig[configKey::lastConfig] = QString(QJsonDocument(lastConfig).toJson()); + wireguardConfig[configKey::isThirdPartyConfig] = true; + wireguardConfig[configKey::port] = port; + wireguardConfig[configKey::transportProto] = protocols::openvpn::defaultTransportProto; + if (protocolName == configKey::awg && !protocolVersion.isEmpty()) { + wireguardConfig[configKey::protocolVersion] = protocolVersion; + } + + QJsonObject containers; + QString containerName = (protocolName == configKey::awg) ? configKey::amneziaAwg : configKey::amneziaWireguard; + containers.insert(configKey::container, QJsonValue(containerName)); + containers.insert(protocolName, QJsonValue(wireguardConfig)); + + QJsonArray arr; + arr.push_back(containers); + + QJsonObject config; + config[configKey::containers] = arr; + config[configKey::defaultContainer] = containerName; + config[configKey::description] = m_appSettingsRepository->nextAvailableServerName(); + + const static QRegularExpression dnsRegExp( + "DNS = " + "(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + QRegularExpressionMatch dnsMatch = dnsRegExp.match(data); + if (dnsMatch.hasMatch()) { + config[configKey::dns1] = dnsMatch.captured(1); + config[configKey::dns2] = dnsMatch.captured(2); + } + + config[configKey::hostName] = hostName; + + configType = detectedType; + return config; +} + +QJsonObject ImportController::extractXrayConfig(const QString &data, ConfigTypes configType, const QString &description) const +{ + QJsonParseError parserErr; + QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr); + + QJsonObject xrayVpnConfig; + xrayVpnConfig[configKey::config] = jsonConf.toJson().constData(); + QJsonObject lastConfig; + lastConfig[configKey::lastConfig] = jsonConf.toJson().constData(); + lastConfig[configKey::isThirdPartyConfig] = true; + + QJsonObject containers; + if (configType == ConfigTypes::ShadowSocks) { + containers.insert(configKey::ssxray, QJsonValue(lastConfig)); + containers.insert(configKey::container, QJsonValue(configKey::amneziaSsxray)); + } else { + containers.insert(configKey::container, QJsonValue(configKey::amneziaXray)); + containers.insert(configKey::xray, QJsonValue(lastConfig)); + } + + QJsonArray arr; + arr.push_back(containers); + + QString hostName; + + const static QRegularExpression hostNameRegExp("\"address\":\\s*\"([^\"]+)"); + QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data); + if (hostNameMatch.hasMatch()) { + hostName = hostNameMatch.captured(1); + } + + QJsonObject config; + config[configKey::containers] = arr; + config[configKey::defaultContainer] = (configType == ConfigTypes::ShadowSocks) + ? configKey::amneziaSsxray + : configKey::amneziaXray; + if (description.isEmpty()) { + config[configKey::description] = m_appSettingsRepository->nextAvailableServerName(); + } else { + config[configKey::description] = description; + } + config[configKey::hostName] = hostName; + + return config; +} + +void ImportController::checkForMaliciousStrings(const QJsonObject &serverConfig, QString &warningText) const +{ + const QJsonArray &containers = serverConfig.value(configKey::containers).toArray(); + for (const QJsonValue &container : containers) { + auto containerConfig = container.toObject(); + auto containerName = containerConfig[configKey::container].toString(); + if (containerName == ContainerUtils::containerToString(DockerContainer::OpenVpn)) { + + QString protocolConfig = + containerConfig[ProtocolUtils::protoToString(Proto::OpenVpn)].toObject()[configKey::lastConfig].toString(); + QString protocolConfigJson = QJsonDocument::fromJson(protocolConfig.toUtf8()).object()[configKey::config].toString(); + + // https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/script-options.rst + QStringList dangerousTags { + "up", "tls-verify", "ipchange", "client-connect", "route-up", "route-pre-down", "client-disconnect", "down", "learn-address", "auth-user-pass-verify" + }; + + QStringList maliciousStrings; + QStringList lines = protocolConfigJson.split('\n', Qt::SkipEmptyParts); + + for (const QString &rawLine : lines) { + QString line = rawLine.trimmed(); + + QString command = line.section(' ', 0, 0, QString::SectionSkipEmpty); + if (dangerousTags.contains(command, Qt::CaseInsensitive)) { + maliciousStrings << rawLine; + } + } + + warningText = "This configuration contains an OpenVPN setup. OpenVPN configurations can include malicious " + "scripts, so only add it if you fully trust the provider of this config. "; + + if (!maliciousStrings.isEmpty()) { + warningText += "
In the imported configuration, potentially dangerous lines were found:"; + for (const auto &string : maliciousStrings) { + warningText += QString("
%1").arg(string); + } + } + } + } +} + +void ImportController::processAmneziaConfig(QJsonObject &config) const +{ + auto containers = config.value(configKey::containers).toArray(); + for (auto i = 0; i < containers.size(); i++) { + auto container = containers.at(i).toObject(); + auto dockerContainer = ContainerUtils::containerFromString(container.value(configKey::container).toString()); + if (ContainerUtils::isAwgContainer(dockerContainer) || dockerContainer == DockerContainer::WireGuard) { + auto containerConfig = container.value(ContainerUtils::containerTypeToProtocolString(dockerContainer)).toObject(); + auto protocolConfig = containerConfig.value(configKey::lastConfig).toString(); + if (protocolConfig.isEmpty()) { + return; + } + + QJsonObject jsonConfig = QJsonDocument::fromJson(protocolConfig.toUtf8()).object(); + jsonConfig[configKey::mtu] = + ContainerUtils::isAwgContainer(dockerContainer) ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu; + + containerConfig[configKey::lastConfig] = QString(QJsonDocument(jsonConfig).toJson()); + + container[ContainerUtils::containerTypeToProtocolString(dockerContainer)] = containerConfig; + containers.replace(i, container); + config.insert(configKey::containers, containers); + } + } +} + diff --git a/client/core/controllers/selfhosted/importController.h b/client/core/controllers/selfhosted/importController.h new file mode 100644 index 000000000..3168f6f4a --- /dev/null +++ b/client/core/controllers/selfhosted/importController.h @@ -0,0 +1,91 @@ +#ifndef IMPORTCONTROLLER_H +#define IMPORTCONTROLLER_H + +#include +#include +#include +#include + +#include "core/repositories/secureServersRepository.h" +#include "core/repositories/secureAppSettingsRepository.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" + +namespace +{ + enum class ConfigTypes { + Amnezia, + OpenVpn, + WireGuard, + Awg, + Xray, + ShadowSocks, + Backup, + Invalid + }; +} + +using namespace amnezia; + +class ImportController : public QObject +{ + Q_OBJECT + +public: + struct ImportResult + { + ErrorCode errorCode = ErrorCode::NoError; + QJsonObject config; + QString configFileName; + QString maliciousWarningText; + ConfigTypes configType = ConfigTypes::Invalid; + bool isNativeWireGuardConfig = false; + }; + + explicit ImportController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository, + QObject *parent = nullptr); + + struct QrParseResult { + bool success = false; + ImportResult importResult; + int chunksReceived = 0; + int chunksTotal = 0; + }; + + ImportResult extractConfigFromData(const QString &data, const QString &configFileName = ""); + ImportResult extractConfigFromQr(const QByteArray &data); + + void startDecodingQr(); + QrParseResult parseQrCodeChunk(const QString &code); + bool isQrDecodingActive() const; + int qrChunksReceived() const; + int qrChunksTotal() const; + + void importConfig(const QJsonObject &config); + QJsonObject processNativeWireGuardConfig(const QJsonObject &config); + +signals: + void importFinished(); + void importErrorOccurred(ErrorCode errorCode, bool goToPageHome); + void restoreAppConfig(const QByteArray &data); + +private: + ConfigTypes checkConfigFormat(const QString &config) const; + QJsonObject extractOpenVpnConfig(const QString &data) const; + QJsonObject extractWireGuardConfig(const QString &data, ConfigTypes &configType) const; + QJsonObject extractXrayConfig(const QString &data, ConfigTypes configType, const QString &description = "") const; + void checkForMaliciousStrings(const QJsonObject &serverConfig, QString &warningText) const; + void processAmneziaConfig(QJsonObject &config) const; + + SecureServersRepository* m_serversRepository; + SecureAppSettingsRepository* m_appSettingsRepository; + + QMap m_qrCodeChunks; + bool m_isQrCodeProcessed = false; + int m_totalQrCodeChunksCount = 0; + int m_receivedQrCodeChunksCount = 0; +}; + +#endif // IMPORTCONTROLLER_H diff --git a/client/core/controllers/selfhosted/installController.cpp b/client/core/controllers/selfhosted/installController.cpp new file mode 100644 index 000000000..c862f4723 --- /dev/null +++ b/client/core/controllers/selfhosted/installController.cpp @@ -0,0 +1,1179 @@ +#include "installController.h" + +#include "core/models/protocolConfig.h" + +#include +#include +#include +#include +#include +#include + +#include "core/configurators/configuratorBase.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/selfhosted/sshSession.h" +#include "core/installers/awgInstaller.h" +#include "core/installers/installerBase.h" +#include "core/installers/openvpnInstaller.h" +#include "core/installers/sftpInstaller.h" +#include "core/installers/socks5Installer.h" +#include "core/installers/torInstaller.h" +#include "core/installers/wireguardInstaller.h" +#include "core/installers/xrayInstaller.h" +#include "core/utils/networkUtilities.h" +#include "core/utils/api/apiUtils.h" +#include "core/repositories/secureServersRepository.h" +#include "core/repositories/secureAppSettingsRepository.h" +#include "core/utils/selfhosted/scriptsRegistry.h" +#include "core/utils/selfhosted/sshClient.h" +#include "logger.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/models/serverConfig.h" +#include "core/models/containerConfig.h" +#include "core/models/protocols/awgProtocolConfig.h" +#include "ui/models/protocols/wireguardConfigModel.h" +#include "core/utils/utilities.h" +#include +#include +#include +#include +#include +#include +#ifdef Q_OS_WINDOWS + #include +#endif + +using namespace amnezia; +using namespace ProtocolUtils; + +namespace +{ + Logger logger("InstallController"); +} + +InstallController::InstallController(SecureServersRepository *serversRepository, + SecureAppSettingsRepository* appSettingsRepository, + QObject *parent) + : QObject(parent), + m_serversRepository(serversRepository), + m_appSettingsRepository(appSettingsRepository), + m_cancelInstallation(false) +{ +} + +InstallController::~InstallController() +{ + stopAllSftpMounts(); +} + +ErrorCode InstallController::setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, + bool isUpdate) +{ + qDebug().noquote() << "InstallController::setupContainer" << ContainerUtils::containerToString(container); + SshSession sshSession(this); + ErrorCode e = ErrorCode::NoError; + + e = isUserInSudo(credentials, sshSession); + if (e) + return e; + + e = isServerDpkgBusy(credentials, sshSession); + if (e) + return e; + + e = installDockerWorker(credentials, container, sshSession); + if (e) + return e; + qDebug().noquote() << "InstallController::setupContainer installDockerWorker finished"; + + if (!isUpdate) { + e = isServerPortBusy(credentials, container, config, sshSession); + if (e) + return e; + } + + e = prepareHostWorker(credentials, container, sshSession); + if (e) + return e; + qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished"; + + sshSession.runScript(credentials, + sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), + amnezia::genBaseVars(credentials, container, QString(), QString()))); + qDebug().noquote() << "InstallController::setupContainer removeContainer finished"; + + qDebug().noquote() << "buildContainerWorker start"; + e = buildContainerWorker(credentials, container, config, sshSession); + if (e) + return e; + qDebug().noquote() << "InstallController::setupContainer buildContainerWorker finished"; + + e = runContainerWorker(credentials, container, config, sshSession); + if (e) + return e; + qDebug().noquote() << "InstallController::setupContainer runContainerWorker finished"; + + e = configureContainerWorker(credentials, container, config, sshSession); + if (e) + return e; + qDebug().noquote() << "InstallController::setupContainer configureContainerWorker finished"; + + setupServerFirewall(credentials, sshSession); + qDebug().noquote() << "InstallController::setupContainer setupServerFirewall finished"; + + return startupContainerWorker(credentials, container, config, sshSession); +} + +ErrorCode InstallController::updateContainer(int serverIndex, DockerContainer container, const ContainerConfig &oldConfig, + ContainerConfig &newConfig) +{ + if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) { + m_serversRepository->setContainerConfig(serverIndex, container, newConfig); + return ErrorCode::NoError; + } + + ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex); + SshSession sshSession(this); + + bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); + qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired; + + ErrorCode errorCode = ErrorCode::NoError; + if (reinstallRequired) { + errorCode = setupContainer(credentials, container, newConfig, true); + } else { + errorCode = configureContainerWorker(credentials, container, newConfig, sshSession); + if (errorCode == ErrorCode::NoError) { + errorCode = startupContainerWorker(credentials, container, newConfig, sshSession); + } + } + + if (errorCode == ErrorCode::NoError) { + clearCachedProfile(serverIndex, container); + m_serversRepository->setContainerConfig(serverIndex, container, newConfig); + } + + return errorCode; +} + +void InstallController::clearCachedProfile(int serverIndex, DockerContainer container) +{ + if (ContainerUtils::containerService(container) == ServiceType::Other) { + return; + } + + ContainerConfig containerConfigModel = m_serversRepository->containerConfig(serverIndex, container); + + m_serversRepository->clearLastConnectionConfig(serverIndex, container); + + emit clientRevocationRequested(serverIndex, containerConfigModel, container); +} + +ErrorCode InstallController::validateAndPrepareConfig(int serverIndex) +{ + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + + if (serverConfigModel.isApiConfig()) { + return ErrorCode::NoError; + } + + DockerContainer container = serverConfigModel.defaultContainer(); + + if (container == DockerContainer::None) { + return ErrorCode::NoInstalledContainersError; + } + + ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container); + ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex); + SshSession sshSession; + + auto isProtocolConfigExists = [](const ContainerConfig &cfg) { + return cfg.protocolConfig.hasClientConfig(); + }; + + if (!isProtocolConfigExists(containerConfig)) { + QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName()); + ErrorCode errorCode = processContainerForAdmin(container, containerConfig, credentials, sshSession, serverIndex, clientName); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + m_serversRepository->setContainerConfig(serverIndex, container, containerConfig); + } + + return ErrorCode::NoError; +} + +void InstallController::validateConfig(int serverIndex) +{ + QFuture future = QtConcurrent::run([this, serverIndex]() { + return validateAndPrepareConfig(serverIndex); + }); + + auto *watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, this, [this, watcher]() { + ErrorCode errorCode = watcher->result(); + watcher->deleteLater(); + + if (errorCode == ErrorCode::NoError) { + emit configValidated(true); + return; + } + + emit validationErrorOccurred(errorCode); + emit configValidated(false); + }); + watcher->setFuture(future); +} + +ErrorCode InstallController::prepareContainerConfig(DockerContainer container, const ServerCredentials &credentials, ContainerConfig &containerConfig, SshSession &sshSession) +{ + if (!ContainerUtils::isSupportedByCurrentPlatform(container)) { + return ErrorCode::NoError; + } + + if (ContainerUtils::containerService(container) != ServiceType::Other) { + Proto protocol = ContainerUtils::defaultProtocol(container); + + DnsSettings dnsSettings = { + m_appSettingsRepository->primaryDns(), + m_appSettingsRepository->secondaryDns() + }; + + auto configurator = ConfiguratorBase::create(protocol, &sshSession); + ErrorCode errorCode = ErrorCode::NoError; + ProtocolConfig newProtocolConfig = configurator->createConfig(credentials, container, containerConfig, dnsSettings, errorCode); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + containerConfig.protocolConfig = newProtocolConfig; + } + + return ErrorCode::NoError; +} + +void InstallController::adminAppendRequested(int serverIndex, DockerContainer container, + const ContainerConfig &containerConfig, const QString &clientName) +{ + if (ContainerUtils::containerService(container) == ServiceType::Other + || !containerConfig.protocolConfig.hasClientConfig()) { + return; + } + QString clientId = containerConfig.protocolConfig.clientId(); + if (!clientId.isEmpty()) { + emit clientAppendRequested(serverIndex, clientId, clientName, container); + } +} + +ErrorCode InstallController::processContainerForAdmin(DockerContainer container, ContainerConfig &containerConfig, + const ServerCredentials &credentials, SshSession &sshSession, + int serverIndex, const QString &clientName) +{ + if (ContainerUtils::isSupportedByCurrentPlatform(container)) { + ErrorCode errorCode = prepareContainerConfig(container, credentials, containerConfig, sshSession); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + } + adminAppendRequested(serverIndex, container, containerConfig, clientName); + return ErrorCode::NoError; +} + +ErrorCode InstallController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession) +{ + amnezia::ScriptVars baseVars = amnezia::genBaseVars(credentials, container, QString(), QString()); + + QString dockerfilePath = "/opt/amnezia/" + ContainerUtils::containerToString(container) + "/Dockerfile"; + QString removeScript = QString("sudo rm %1").arg(dockerfilePath); + + ErrorCode errorCode = sshSession.runScript(credentials, sshSession.replaceVars(removeScript, baseVars)); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + errorCode = sshSession.uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(), dockerfilePath); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + amnezia::ScriptVars protocolVars = amnezia::genProtocolVarsForContainer(container, config); + baseVars.append(protocolVars); + ErrorCode error = sshSession.runScript( + credentials, sshSession.replaceVars(amnezia::scriptData(SharedScriptType::build_container), baseVars), cbReadStdOut, + cbReadStdErr); + + if (stdOut.contains("doesn't work on cgroups v2")) + return ErrorCode::ServerDockerOnCgroupsV2; + if (stdOut.contains("cgroup mountpoint does not exist")) + return ErrorCode::ServerCgroupMountpoint; + if (stdOut.contains("have reached") && stdOut.contains("pull rate limit")) + return ErrorCode::DockerPullRateLimit; + + return error; +} + +ErrorCode InstallController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession) +{ + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + amnezia::ScriptVars baseVars = amnezia::genBaseVars(credentials, container, QString(), QString()); + amnezia::ScriptVars protocolVars = amnezia::genProtocolVarsForContainer(container, config); + baseVars.append(protocolVars); + ErrorCode e = sshSession.runScript( + credentials, sshSession.replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), baseVars), + cbReadStdOut); + + if (stdOut.contains("address already in use")) + return ErrorCode::ServerPortAlreadyAllocatedError; + if (stdOut.contains("is already in use by container")) + return ErrorCode::ServerPortAlreadyAllocatedError; + if (stdOut.contains("invalid publish")) + return ErrorCode::ServerDockerFailedError; + + return e; +} + +ErrorCode InstallController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession) +{ + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + amnezia::ScriptVars baseVars = amnezia::genBaseVars(credentials, container, QString(), QString()); + amnezia::ScriptVars protocolVars = amnezia::genProtocolVarsForContainer(container, config); + baseVars.append(protocolVars); + ErrorCode e = sshSession.runContainerScript( + credentials, container, + sshSession.replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), baseVars), + cbReadStdOut, cbReadStdErr); + + updateContainerConfigAfterInstallation(container, config, stdOut); + + return e; +} + +ErrorCode InstallController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession) +{ + QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container); + + if (script.isEmpty()) { + return ErrorCode::NoError; + } + + amnezia::ScriptVars baseVars = amnezia::genBaseVars(credentials, container, QString(), QString()); + amnezia::ScriptVars protocolVars = amnezia::genProtocolVarsForContainer(container, config); + baseVars.append(protocolVars); + ErrorCode e = sshSession.uploadTextFileToContainer(container, credentials, sshSession.replaceVars(script, baseVars), + "/opt/amnezia/start.sh"); + if (e) + return e; + + return sshSession.runScript( + credentials, + sshSession.replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && " + "/opt/amnezia/start.sh\"", + baseVars)); +} + +ErrorCode InstallController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession) +{ + if (container == DockerContainer::Dns) { + return ErrorCode::NoError; + } + + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + const Proto protocol = ContainerUtils::defaultProtocol(container); + QStringList fixedPorts = ContainerUtils::fixedPortsForContainer(container); + + QString port = config.protocolConfig.port(); + if (port.isEmpty()) { + port = QString::number(ProtocolUtils::defaultPort(protocol)); + } + QString transportProto = config.protocolConfig.transportProto(); + if (transportProto.isEmpty()) { + transportProto = ProtocolUtils::transportProtoToString(ProtocolUtils::defaultTransportProto(protocol), protocol); + } + + // TODO reimplement with netstat + QString script = QString("which lsof > /dev/null 2>&1 || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port); + for (auto &port : fixedPorts) { + script = script.append("|:%1").arg(port); + } + + if (transportProto == "tcpandudp") { + QString tcpProtoScript = script; + QString udpProtoScript = script; + tcpProtoScript.append("' | grep -i tcp"); + udpProtoScript.append("' | grep -i udp"); + tcpProtoScript.append(" | grep LISTEN"); + + ErrorCode errorCode = sshSession.runScript( + credentials, + sshSession.replaceVars(tcpProtoScript, amnezia::genBaseVars(credentials, container, QString(), QString())), + cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + errorCode = sshSession.runScript( + credentials, + sshSession.replaceVars(udpProtoScript, amnezia::genBaseVars(credentials, container, QString(), QString())), + cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + if (!stdOut.isEmpty()) { + return ErrorCode::ServerPortAlreadyAllocatedError; + } + return ErrorCode::NoError; + } + + script = script.append("' | grep -i %1").arg(transportProto); + + if (transportProto == "tcp") { + script = script.append(" | grep LISTEN"); + } + + ErrorCode errorCode = sshSession.runScript( + credentials, sshSession.replaceVars(script, amnezia::genBaseVars(credentials, container, QString(), QString())), + cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + if (!stdOut.isEmpty()) { + return ErrorCode::ServerPortAlreadyAllocatedError; + } + return ErrorCode::NoError; +} + +bool InstallController::isReinstallContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig) +{ + if (container == DockerContainer::OpenVpn) { + const auto* oldOvpnConfig = oldConfig.getOpenVpnProtocolConfig(); + const auto* newOvpnConfig = newConfig.getOpenVpnProtocolConfig(); + + if (oldOvpnConfig && newOvpnConfig) { + if (!oldOvpnConfig->serverConfig.hasEqualServerSettings(newOvpnConfig->serverConfig)) { + return true; + } + } + } + + if (ContainerUtils::isAwgContainer(container)) { + const auto* oldAwgConfig = oldConfig.getAwgProtocolConfig(); + const auto* newAwgConfig = newConfig.getAwgProtocolConfig(); + + if (oldAwgConfig && newAwgConfig) { + if (!oldAwgConfig->serverConfig.hasEqualServerSettings(newAwgConfig->serverConfig)) { + return true; + } + } + } + + if (container == DockerContainer::WireGuard) { + const auto* oldWgConfig = oldConfig.getWireGuardProtocolConfig(); + const auto* newWgConfig = newConfig.getWireGuardProtocolConfig(); + + if (oldWgConfig && newWgConfig) { + if (!oldWgConfig->serverConfig.hasEqualServerSettings(newWgConfig->serverConfig)) { + return true; + } + } + } + + if (container == DockerContainer::Xray || container == DockerContainer::SSXray) { + const auto* oldXrayConfig = oldConfig.getXrayProtocolConfig(); + const auto* newXrayConfig = newConfig.getXrayProtocolConfig(); + + if (oldXrayConfig && newXrayConfig) { + if (oldXrayConfig->serverConfig.port != newXrayConfig->serverConfig.port) + return true; + } + } + + if (container == DockerContainer::Socks5Proxy) { + return true; + } + + return false; +} + +void InstallController::cancelInstallation() +{ + m_cancelInstallation = true; +} + +ErrorCode InstallController::installDockerWorker(const ServerCredentials &credentials, DockerContainer container, SshSession &sshSession) +{ + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &client) { + stdOut += data + "\n"; + + if (data.contains("Automatically restart Docker daemon?")) { + return client.writeResponse("yes"); + } + return ErrorCode::NoError; + }; + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + ErrorCode error = sshSession.runScript( + credentials, + sshSession.replaceVars(amnezia::scriptData(SharedScriptType::install_docker), + amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())), + cbReadStdOut, cbReadStdErr); + + qDebug().noquote() << "InstallController::installDockerWorker" << stdOut; + + if (container == DockerContainer::Awg2) { + QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)"); + QRegularExpressionMatch match = regex.match(stdOut); + if (match.hasMatch()) { + int majorVersion = match.captured(1).toInt(); + int minorVersion = match.captured(2).toInt(); + + if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 14)) { + return ErrorCode::ServerLinuxKernelTooOld; + } + } + } + + if (stdOut.contains("lock")) + return ErrorCode::ServerPacketManagerError; + if (stdOut.contains("command not found")) + return ErrorCode::ServerDockerFailedError; + + return error; +} + +ErrorCode InstallController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, SshSession &sshSession) +{ + // create folder on host + return sshSession.runScript(credentials, + sshSession.replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), + amnezia::genBaseVars(credentials, container, QString(), QString()))); +} + +ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials, SshSession &sshSession) +{ + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo); + ErrorCode error = sshSession.runScript( + credentials, + sshSession.replaceVars(scriptData, amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())), + cbReadStdOut, cbReadStdErr); + + if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found")) + return ErrorCode::ServerSudoPackageIsNotPreinstalled; + if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel")) + 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")) + return ErrorCode::ServerUserNotAllowedInSudoers; + if (stdOut.contains("password is required")) + return ErrorCode::ServerUserPasswordRequired; + + return error; +} + +ErrorCode InstallController::isServerDpkgBusy(const ServerCredentials &credentials, SshSession &sshSession) +{ + m_cancelInstallation = false; + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + QFutureWatcher watcher; + + QFuture future = QtConcurrent::run([this, &stdOut, &cbReadStdOut, &cbReadStdErr, &credentials, &sshSession]() { + // max 100 attempts + for (int i = 0; i < 30; ++i) { + if (m_cancelInstallation) { + return ErrorCode::ServerCancelInstallation; + } + stdOut.clear(); + sshSession.runScript( + credentials, + sshSession.replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), + amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())), + cbReadStdOut, cbReadStdErr); + + if (stdOut.contains("Packet manager not found")) + return ErrorCode::ServerPacketManagerError; + if (stdOut.contains("fuser not installed") || stdOut.contains("cat not installed")) + return ErrorCode::NoError; + + if (stdOut.isEmpty()) { + return ErrorCode::NoError; + } else { +#ifdef MZ_DEBUG + qDebug().noquote() << stdOut; +#endif + emit serverIsBusy(true); + QThread::msleep(10000); + } + } + return ErrorCode::ServerPacketManagerError; + }); + + QEventLoop wait; + QObject::connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); + watcher.setFuture(future); + wait.exec(); + + emit serverIsBusy(false); + + return future.result(); +} + +ErrorCode InstallController::setupServerFirewall(const ServerCredentials &credentials, SshSession &sshSession) +{ + return sshSession.runScript( + credentials, + sshSession.replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), + amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString()))); +} + +ErrorCode InstallController::rebootServer(int serverIndex) +{ + auto credentials = m_serversRepository->serverCredentials(serverIndex); + SshSession sshSession(this); + + QString script = QString("sudo reboot"); + + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data; + return ErrorCode::NoError; + }; + + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + return sshSession.runScript(credentials, script, cbReadStdOut, cbReadStdErr); +} + +ErrorCode InstallController::removeAllContainers(int serverIndex) +{ + auto credentials = m_serversRepository->serverCredentials(serverIndex); + SshSession sshSession(this); + ErrorCode errorCode = sshSession.runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers)); + + if (errorCode == ErrorCode::NoError) { + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + serverConfigModel.visit([](auto& arg) { + arg.containers.clear(); + arg.defaultContainer = DockerContainer::None; + }); + m_serversRepository->editServer(serverIndex, serverConfigModel); + } + + return errorCode; +} + +ErrorCode InstallController::removeContainer(int serverIndex, DockerContainer container) +{ + auto credentials = m_serversRepository->serverCredentials(serverIndex); + SshSession sshSession(this); + ErrorCode errorCode = sshSession.runScript( + credentials, + sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), + amnezia::genBaseVars(credentials, container, QString(), QString()))); + + if (errorCode == ErrorCode::NoError) { + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + QMap containers = serverConfigModel.containers(); + containers.remove(container); + + DockerContainer defaultContainer = serverConfigModel.defaultContainer(); + if (defaultContainer == container) { + if (containers.isEmpty()) { + defaultContainer = DockerContainer::None; + } else { + defaultContainer = containers.begin().key(); + } + } + + serverConfigModel.visit([&containers, defaultContainer](auto& arg) { + arg.containers = containers; + arg.defaultContainer = defaultContainer; + }); + m_serversRepository->editServer(serverIndex, serverConfigModel); + } + + return errorCode; +} + +QScopedPointer InstallController::createInstaller(DockerContainer container) +{ + switch (container) { + case DockerContainer::Awg: return QScopedPointer(new AwgInstaller(this)); + case DockerContainer::Awg2: return QScopedPointer(new AwgInstaller(this)); + case DockerContainer::WireGuard: return QScopedPointer(new WireguardInstaller(this)); + case DockerContainer::OpenVpn: return QScopedPointer(new OpenVpnInstaller(this)); + case DockerContainer::Xray: + case DockerContainer::SSXray: return QScopedPointer(new XrayInstaller(this)); + case DockerContainer::TorWebSite: return QScopedPointer(new TorInstaller(this)); + case DockerContainer::Sftp: return QScopedPointer(new SftpInstaller(this)); + case DockerContainer::Socks5Proxy: return QScopedPointer(new Socks5Installer(this)); + default: return QScopedPointer(new InstallerBase(this)); + } +} + +ContainerConfig InstallController::generateConfig(DockerContainer container, int port, TransportProto transportProto) +{ + auto installer = createInstaller(container); + return installer->generateConfig(container, port, transportProto); +} + +ErrorCode InstallController::installContainer(const ServerCredentials &credentials, DockerContainer container, int port, + TransportProto transportProto, ContainerConfig &config) +{ + config = generateConfig(container, port, transportProto); + return setupContainer(credentials, container, config, false); +} + + +bool InstallController::isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig) +{ + if (ContainerUtils::isAwgContainer(container)) { + const auto* oldAwgConfig = oldConfig.getAwgProtocolConfig(); + const auto* newAwgConfig = newConfig.getAwgProtocolConfig(); + + if (oldAwgConfig && newAwgConfig) { + if (oldAwgConfig->serverConfig.hasEqualServerSettings(newAwgConfig->serverConfig)) { + return false; + } + } + } else if (container == DockerContainer::WireGuard) { + const auto* oldWgConfig = oldConfig.getWireGuardProtocolConfig(); + const auto* newWgConfig = newConfig.getWireGuardProtocolConfig(); + + if (oldWgConfig && newWgConfig) { + if (oldWgConfig->serverConfig.hasEqualServerSettings(newWgConfig->serverConfig)) { + return false; + } + } + } + + return true; +} + +ErrorCode InstallController::scanServerForInstalledContainers(int serverIndex) +{ + ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex); + SshSession sshSession(this); + + QMap installedContainers; + ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + ServerConfig serverConfigModel = m_serversRepository->server(serverIndex); + QMap containers = serverConfigModel.containers(); + bool hasNewContainers = false; + + QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName()); + for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { + if (!containers.contains(iterator.key())) { + ContainerConfig containerConfig = iterator.value(); + errorCode = processContainerForAdmin(iterator.key(), containerConfig, credentials, sshSession, + serverIndex, clientName); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + containers.insert(iterator.key(), containerConfig); + hasNewContainers = true; + + DockerContainer defaultContainer = serverConfigModel.defaultContainer(); + if (defaultContainer == DockerContainer::None + && ContainerUtils::containerService(iterator.key()) != ServiceType::Other + && ContainerUtils::isSupportedByCurrentPlatform(iterator.key())) { + serverConfigModel.visit([iterator](auto& arg) { + arg.defaultContainer = iterator.key(); + }); + } + } + } + + if (hasNewContainers) { + serverConfigModel.visit([&containers](auto& arg) { + arg.containers = containers; + }); + m_serversRepository->editServer(serverIndex, serverConfigModel); + } + + return ErrorCode::NoError; +} + +ErrorCode InstallController::installServer(const ServerCredentials &credentials, DockerContainer container, int port, + TransportProto transportProto, bool &wasContainerInstalled) +{ + SshSession sshSession(this); + QMap installedContainers; + ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession); + if (errorCode) { + return errorCode; + } + + wasContainerInstalled = false; + if (!installedContainers.contains(container)) { + ContainerConfig config; + errorCode = installContainer(credentials, container, port, transportProto, config); + if (errorCode) { + return errorCode; + } + + installedContainers.insert(container, config); + wasContainerInstalled = true; + } + + QMap preparedContainers; + for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { + DockerContainer container = iterator.key(); + ContainerConfig containerConfig = iterator.value(); + + if (ContainerUtils::isSupportedByCurrentPlatform(container)) { + errorCode = prepareContainerConfig(container, credentials, containerConfig, sshSession); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + } + preparedContainers.insert(container, containerConfig); + } + + SelfHostedServerConfig serverConfig; + serverConfig.hostName = credentials.hostName; + serverConfig.userName = credentials.userName; + serverConfig.password = credentials.secretData; + serverConfig.port = credentials.port; + serverConfig.description = m_appSettingsRepository->nextAvailableServerName(); + + for (auto iterator = preparedContainers.begin(); iterator != preparedContainers.end(); iterator++) { + serverConfig.containers.insert(iterator.key(), iterator.value()); + } + + serverConfig.defaultContainer = container; + + m_serversRepository->addServer(ServerConfig(serverConfig)); + + int serverIndex = m_serversRepository->serversCount() - 1; + QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName()); + for (auto iterator = preparedContainers.begin(); iterator != preparedContainers.end(); iterator++) { + adminAppendRequested(serverIndex, iterator.key(), iterator.value(), clientName); + } + + return ErrorCode::NoError; +} + +ErrorCode InstallController::installContainer(int serverIndex, DockerContainer container, int port, + TransportProto transportProto, bool &wasContainerInstalled) +{ + ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex); + SshSession sshSession(this); + + QMap installedContainers; + ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession); + if (errorCode) { + return errorCode; + } + + wasContainerInstalled = false; + if (!installedContainers.contains(container)) { + ContainerConfig config; + errorCode = installContainer(credentials, container, port, transportProto, config); + if (errorCode) { + return errorCode; + } + + installedContainers.insert(container, config); + wasContainerInstalled = true; + } + + QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName()); + for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { + ContainerConfig existingConfigModel = m_serversRepository->containerConfig(serverIndex, iterator.key()); + if (existingConfigModel.container == DockerContainer::None) { + ContainerConfig containerConfig = iterator.value(); + errorCode = processContainerForAdmin(iterator.key(), containerConfig, credentials, sshSession, + serverIndex, clientName); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + m_serversRepository->setContainerConfig(serverIndex, iterator.key(), containerConfig); + } + } + + return ErrorCode::NoError; +} + +ErrorCode InstallController::checkSshConnection(const ServerCredentials &credentials, QString &output, + std::function passphraseCallback) +{ + SshSession sshSession(this); + ErrorCode errorCode = ErrorCode::NoError; + + ServerCredentials processedCredentials = credentials; + + if (processedCredentials.secretData.contains("BEGIN") && processedCredentials.secretData.contains("PRIVATE KEY")) { + if (!passphraseCallback) { + return ErrorCode::SshPrivateKeyError; + } + + QString decryptedPrivateKey; + errorCode = sshSession.getDecryptedPrivateKey(processedCredentials, decryptedPrivateKey, passphraseCallback); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + processedCredentials.secretData = decryptedPrivateKey; + } + + output = sshSession.checkSshConnection(processedCredentials, errorCode); + return errorCode; +} + +bool InstallController::isServerAlreadyExists(const ServerCredentials &credentials, int &existingServerIndex) +{ + int serversCount = m_serversRepository->serversCount(); + for (int i = 0; i < serversCount; i++) { + const ServerCredentials existingCredentials = m_serversRepository->serverCredentials(i); + if (credentials.hostName == existingCredentials.hostName && credentials.port == existingCredentials.port) { + existingServerIndex = i; + return true; + } + } + existingServerIndex = -1; + return false; +} + +ErrorCode InstallController::mountSftpDrive(const ServerCredentials &credentials, const QString &port, const QString &password, + const QString &username) +{ + QString mountPath; + QString cmd; + QString hostname = credentials.hostName; + +#ifdef Q_OS_WINDOWS + mountPath = Utils::getNextDriverLetter() + ":"; + cmd = "C:\\Program Files\\SSHFS-Win\\bin\\sshfs.exe"; +#elif defined AMNEZIA_DESKTOP + mountPath = QString("%1/sftp:%2:%3").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), hostname, port); + QDir dir(mountPath); + if (!dir.exists()) { + dir.mkpath(mountPath); + } + + cmd = "/usr/local/bin/sshfs"; + + QSharedPointer process(new QProcess(this)); + process->setProcessChannelMode(QProcess::MergedChannels); + + connect(process.get(), &QProcess::readyRead, this, [process, mountPath]() { + QString s = process->readAll(); + if (s.contains("The service sshfs has been started")) { + QDesktopServices::openUrl(QUrl("file:///" + mountPath)); + } + qDebug() << s; + }); + + process->setProgram(cmd); + + QString args = QString("%1@%2:/ %3 " + "-o port=%4 " + "-f " + "-o reconnect " + "-o rellinks " + "-o fstypename=SSHFS " + "-o ssh_command=/usr/bin/ssh.exe " + "-o UserKnownHostsFile=/dev/null " + "-o StrictHostKeyChecking=no " + "-o password_stdin") + .arg(username, hostname, mountPath, port); + + process->setArguments(args.split(" ", Qt::SkipEmptyParts)); + process->start(); + process->waitForStarted(50); + if (process->state() != QProcess::Running) { + qDebug() << "mountSftpDrive process not started"; + qDebug() << args; + return ErrorCode::ServerContainerMissingError; + } else { + process->write((password + "\n").toUtf8()); + } + + m_sftpMountProcesses.append(process); +#else + Q_UNUSED(mountPath); + Q_UNUSED(cmd); + Q_UNUSED(password); + return ErrorCode::NoError; +#endif + + return ErrorCode::NoError; +} + +void InstallController::stopAllSftpMounts() +{ +#ifdef Q_OS_WINDOWS + for (QSharedPointer process : m_sftpMountProcesses) { + Utils::signalCtrl(process->processId(), CTRL_C_EVENT); + process->kill(); + process->waitForFinished(); + } + m_sftpMountProcesses.clear(); +#endif +} + +void InstallController::updateContainerConfigAfterInstallation(DockerContainer container, ContainerConfig &containerConfig, const QString &stdOut) +{ + Proto mainProto = ContainerUtils::defaultProtocol(container); + + if (container == DockerContainer::TorWebSite) { + if (auto* torProtocolConfig = containerConfig.getTorProtocolConfig()) { + qDebug() << "amnezia-tor onions" << stdOut; + + QString onion = stdOut; + onion.replace("\n", ""); + torProtocolConfig->serverConfig.site = onion; + } + } +} + +ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentials &credentials, + QMap &installedContainers, SshSession &sshSession) +{ + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'"); + ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z0-9]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*"); + const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*"); + + QStringList containerInfos = stdOut.split("\n"); + for (const QString &containerInfo : containerInfos) { + if (containerInfo.isEmpty()) { + continue; + } + + QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo); + if (containerAndPortMatch.hasMatch()) { + QString name = containerAndPortMatch.captured(1); + QString portStr = containerAndPortMatch.captured(2); + QString transportProtoStr = containerAndPortMatch.captured(3); + DockerContainer container = ContainerUtils::containerFromString(name); + + if (container == DockerContainer::None) { + continue; + } + + int port = portStr.toInt(); + TransportProto transportProto = ProtocolUtils::transportProtoFromString(transportProtoStr); + + auto installer = createInstaller(container); + ContainerConfig config = installer->createBaseConfig(container, port, transportProto); + ErrorCode extractError = installer->extractConfigFromContainer(container, credentials, &sshSession, config); + + if (extractError != ErrorCode::NoError && extractError != ErrorCode::ServerContainerMissingError) { + return extractError; + } + + installedContainers.insert(container, config); + } + + QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo); + if (torOrDnsRegMatch.hasMatch()) { + QString name = torOrDnsRegMatch.captured(1); + QString portStr = torOrDnsRegMatch.captured(2); + QString transportProtoStr = torOrDnsRegMatch.captured(3); + DockerContainer container = ContainerUtils::containerFromString(name); + + if (container == DockerContainer::None) { + continue; + } + + int port = portStr.toInt(); + TransportProto transportProto = ProtocolUtils::transportProtoFromString(transportProtoStr); + + auto installer = createInstaller(container); + ContainerConfig config = installer->createBaseConfig(container, port, transportProto); + ErrorCode extractError = installer->extractConfigFromContainer(container, credentials, &sshSession, config); + + if (extractError != ErrorCode::NoError && extractError != ErrorCode::ServerContainerMissingError) { + return extractError; + } + + installedContainers.insert(container, config); + } + } + + return ErrorCode::NoError; +} diff --git a/client/core/controllers/selfhosted/installController.h b/client/core/controllers/selfhosted/installController.h new file mode 100644 index 000000000..bf5701e27 --- /dev/null +++ b/client/core/controllers/selfhosted/installController.h @@ -0,0 +1,117 @@ +#ifndef INSTALLCONTROLLER_H +#define INSTALLCONTROLLER_H + +#include +#include +#include +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/models/containerConfig.h" +#include "core/repositories/secureServersRepository.h" +#include "core/repositories/secureAppSettingsRepository.h" + +class SshSession; +class InstallerBase; + +using namespace amnezia; + +class InstallController : public QObject +{ + Q_OBJECT + +public: + explicit InstallController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository, + QObject *parent = nullptr); + ~InstallController(); + + ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false); + ErrorCode updateContainer(int serverIndex, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig); + + ErrorCode rebootServer(int serverIndex); + ErrorCode removeAllContainers(int serverIndex); + ErrorCode removeContainer(int serverIndex, DockerContainer container); + + ContainerConfig generateConfig(DockerContainer container, int port, TransportProto transportProto); + ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers, SshSession &sshSession); + + ErrorCode scanServerForInstalledContainers(int serverIndex); + + ErrorCode installContainer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, ContainerConfig &config); + + ErrorCode installServer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, + bool &wasContainerInstalled); + ErrorCode installContainer(int serverIndex, DockerContainer container, int port, TransportProto transportProto, + bool &wasContainerInstalled); + + bool isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig); + + ErrorCode checkSshConnection(const ServerCredentials &credentials, QString &output, std::function passphraseCallback = nullptr); + + bool isServerAlreadyExists(const ServerCredentials &credentials, int &existingServerIndex); + + ErrorCode mountSftpDrive(const ServerCredentials &credentials, const QString &port, const QString &password, const QString &username); + void stopAllSftpMounts(); + + void cancelInstallation(); + + void clearCachedProfile(int serverIndex, DockerContainer container); + + ErrorCode validateAndPrepareConfig(int serverIndex); + + void validateConfig(int serverIndex); + +signals: + void configValidated(bool isValid); + void validationErrorOccurred(ErrorCode errorCode); + + void serverIsBusy(const bool isBusy); + void cancelInstallationRequested(); + void clientRevocationRequested(int serverIndex, const ContainerConfig &containerConfig, DockerContainer container); + void clientAppendRequested(int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container); + +private: + ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container, SshSession &sshSession); + ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, SshSession &sshSession); + ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession); + ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession); + ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession); + ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession); + + ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession); + ErrorCode isUserInSudo(const ServerCredentials &credentials, SshSession &sshSession); + ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, SshSession &sshSession); + ErrorCode setupServerFirewall(const ServerCredentials &credentials, SshSession &sshSession); + bool isReinstallContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig); + + ErrorCode prepareContainerConfig(DockerContainer container, const ServerCredentials &credentials, ContainerConfig &containerConfig, SshSession &sshSession); + + ErrorCode processContainerForAdmin(DockerContainer container, ContainerConfig &containerConfig, + const ServerCredentials &credentials, SshSession &sshSession, + int serverIndex, const QString &clientName); + + void adminAppendRequested(int serverIndex, DockerContainer container, + const ContainerConfig &containerConfig, const QString &clientName); + + static void updateContainerConfigAfterInstallation(DockerContainer container, ContainerConfig &containerConfig, const QString &stdOut); + + QScopedPointer createInstaller(DockerContainer container); + + SecureServersRepository* m_serversRepository; + SecureAppSettingsRepository* m_appSettingsRepository; + bool m_cancelInstallation = false; + +#ifndef Q_OS_IOS + QList> m_sftpMountProcesses; +#endif +}; + +#endif // INSTALLCONTROLLER_H + diff --git a/client/core/controllers/selfhosted/usersController.cpp b/client/core/controllers/selfhosted/usersController.cpp new file mode 100644 index 000000000..3999f66d7 --- /dev/null +++ b/client/core/controllers/selfhosted/usersController.cpp @@ -0,0 +1,807 @@ +#include "usersController.h" + +#include +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/selfhosted/sshSession.h" +#include "core/utils/selfhosted/scriptsRegistry.h" +#include "logger.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/models/serverConfig.h" +#include "core/models/containerConfig.h" + +using namespace amnezia; + +namespace +{ + Logger logger("UsersController"); +} + +UsersController::UsersController(SecureServersRepository* serversRepository, QObject *parent) + : QObject(parent), + m_serversRepository(serversRepository) +{ +} + +bool UsersController::isClientExists(const QString &clientId, const QJsonArray &clientsTable) +{ + for (const QJsonValue &value : std::as_const(clientsTable)) { + if (value.isObject()) { + QJsonObject obj = value.toObject(); + if (obj.contains(configKey::clientId) && obj[configKey::clientId].toString() == clientId) { + return true; + } + } + } + return false; +} + +int UsersController::clientIndexById(const QString &clientId, const QJsonArray &clientsTable) +{ + for (int i = 0; i < clientsTable.size(); ++i) { + if (clientsTable.at(i).isObject()) { + QJsonObject obj = clientsTable.at(i).toObject(); + if (obj.contains(configKey::clientId) && obj[configKey::clientId].toString() == clientId) { + return i; + } + } + } + return -1; +} + +void UsersController::migration(const QByteArray &clientsTableString, QJsonArray &clientsTable) +{ + QJsonObject clientsTableObj = QJsonDocument::fromJson(clientsTableString).object(); + + for (auto &clientId : clientsTableObj.keys()) { + QJsonObject client; + client[configKey::clientId] = clientId; + + QJsonObject userData; + userData[configKey::clientName] = clientsTableObj.value(clientId).toObject().value(configKey::clientName); + client[configKey::userData] = userData; + + clientsTable.push_back(client); + } +} + +ErrorCode UsersController::wgShow(const DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, std::vector &data) +{ + if (container != DockerContainer::WireGuard && !ContainerUtils::isAwgContainer(container)) { + return ErrorCode::NoError; + } + + ErrorCode error = ErrorCode::NoError; + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + QString showBin = (container == DockerContainer::Awg2) + ? QStringLiteral("awg") + : QStringLiteral("wg"); + const QString command = QString("sudo docker exec -i $CONTAINER_NAME bash -c '%1 show all'").arg(showBin); + + QString script = sshSession->replaceVars(command, amnezia::genBaseVars(credentials, container, QString(), QString())); + error = sshSession->runScript(credentials, script, cbReadStdOut); + if (error != ErrorCode::NoError) { + logger.error() << QString("Failed to execute %1 show command").arg(showBin); + return error; + } + + if (stdOut.isEmpty()) { + return error; + } + + const auto getStrValue = [](const auto str) { return str.mid(str.indexOf(":") + 1).trimmed(); }; + + const auto parts = stdOut.split('\n'); + const auto peerList = parts.filter("peer:"); + const auto latestHandshakeList = parts.filter("latest handshake:"); + const auto transferredDataList = parts.filter("transfer:"); + const auto allowedIpsList = parts.filter("allowed ips:"); + + if (allowedIpsList.isEmpty() || latestHandshakeList.isEmpty() || transferredDataList.isEmpty() || peerList.isEmpty()) { + return error; + } + + const auto changeHandshakeFormat = [](QString &latestHandshake) { + const std::vector> replaceMap = { { " days", "d" }, { " hours", "h" }, { " minutes", "m" }, + { " seconds", "s" }, { " day", "d" }, { " hour", "h" }, + { " minute", "m" }, { " second", "s" } }; + + for (const auto &item : replaceMap) { + latestHandshake.replace(item.first, item.second); + } + }; + + for (int i = 0; i < peerList.size() && i < transferredDataList.size() && i < latestHandshakeList.size() && i < allowedIpsList.size(); ++i) { + + const auto transferredData = getStrValue(transferredDataList[i]).split(","); + auto latestHandshake = getStrValue(latestHandshakeList[i]); + auto serverBytesReceived = transferredData.front().trimmed(); + auto serverBytesSent = transferredData.back().trimmed(); + auto allowedIps = getStrValue(allowedIpsList[i]); + + changeHandshakeFormat(latestHandshake); + + serverBytesReceived.chop(QStringLiteral(" received").length()); + serverBytesSent.chop(QStringLiteral(" sent").length()); + + data.push_back({ getStrValue(peerList[i]), latestHandshake, serverBytesSent, serverBytesReceived, allowedIps }); + } + + return error; +} + +ErrorCode UsersController::getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, int &count, QJsonArray &clientsTable) +{ + ErrorCode error = ErrorCode::NoError; + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + const QString getOpenVpnClientsList = "sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'"; + QString script = sshSession->replaceVars(getOpenVpnClientsList, amnezia::genBaseVars(credentials, container, QString(), QString())); + error = sshSession->runScript(credentials, script, cbReadStdOut); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to retrieve the list of issued certificates on the server"; + return error; + } + + if (!stdOut.isEmpty()) { + QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts); + certsIds.removeAll("AmneziaReq.crt"); + + for (auto &openvpnCertId : certsIds) { + openvpnCertId.replace(".crt", ""); + if (!isClientExists(openvpnCertId, clientsTable)) { + QJsonObject client; + client[configKey::clientId] = openvpnCertId; + + QJsonObject userData; + userData[configKey::clientName] = QString("Client %1").arg(count); + client[configKey::userData] = userData; + + clientsTable.push_back(client); + + count++; + } + } + } + return error; +} + +ErrorCode UsersController::getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, int &count, QJsonArray &clientsTable) +{ + ErrorCode error = ErrorCode::NoError; + + QString configPath; + if (container == DockerContainer::Awg) { + configPath = QString::fromLatin1(protocols::awg::serverLegacyConfigPath); + } else if (container == DockerContainer::Awg2) { + configPath = QString::fromLatin1(protocols::awg::serverConfigPath); + } else { + configPath = QString::fromLatin1(protocols::wireguard::serverConfigPath); + } + const QString wireguardConfigString = sshSession->getTextFileFromContainer(container, credentials, configPath, error); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to get the wg conf file from the server"; + return error; + } + + auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts); + QStringList wireguardKeys; + for (const auto &line : configLines) { + auto configPair = line.split(" = ", Qt::SkipEmptyParts); + if (configPair.front() == "PublicKey") { + wireguardKeys.push_back(configPair.back()); + } + } + + for (auto &wireguardKey : wireguardKeys) { + if (!isClientExists(wireguardKey, clientsTable)) { + QJsonObject client; + client[configKey::clientId] = wireguardKey; + + QJsonObject userData; + userData[configKey::clientName] = QString("Client %1").arg(count); + client[configKey::userData] = userData; + + clientsTable.push_back(client); + + count++; + } + } + return error; +} + +ErrorCode UsersController::getXrayClients(const DockerContainer container, const ServerCredentials& credentials, + SshSession* sshSession, int &count, QJsonArray &clientsTable) +{ + ErrorCode error = ErrorCode::NoError; + + const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath; + const QString configString = sshSession->getTextFileFromContainer(container, credentials, serverConfigPath, error); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to get the xray server config file from the server"; + return error; + } + + QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8()); + if (serverConfig.isNull()) { + logger.error() << "Failed to parse xray server config JSON"; + return ErrorCode::InternalError; + } + + if (!serverConfig.object().contains(protocols::xray::inbounds) || serverConfig.object()[protocols::xray::inbounds].toArray().isEmpty()) { + logger.error() << "Invalid xray server config structure"; + return ErrorCode::InternalError; + } + + const QJsonObject inbound = serverConfig.object()[protocols::xray::inbounds].toArray()[0].toObject(); + if (!inbound.contains(protocols::xray::settings)) { + logger.error() << "Missing settings in xray inbound config"; + return ErrorCode::InternalError; + } + + const QJsonObject settings = inbound[protocols::xray::settings].toObject(); + if (!settings.contains(protocols::xray::clients)) { + logger.error() << "Missing clients in xray settings config"; + return ErrorCode::InternalError; + } + + const QJsonArray clients = settings[protocols::xray::clients].toArray(); + for (const auto &clientValue : clients) { + const QJsonObject clientObj = clientValue.toObject(); + if (!clientObj.contains(protocols::xray::id)) { + logger.error() << "Missing id in xray client config"; + continue; + } + QString clientId = clientObj[protocols::xray::id].toString(); + + QString xrayDefaultUuid = sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, error); + xrayDefaultUuid.replace("\n", ""); + + if (!isClientExists(clientId, clientsTable) && clientId != xrayDefaultUuid) { + QJsonObject client; + client[configKey::clientId] = clientId; + + QJsonObject userData; + userData[configKey::clientName] = QString("Client %1").arg(count); + client[configKey::userData] = userData; + + clientsTable.push_back(client); + count++; + } + } + + return error; +} + +ErrorCode UsersController::updateClients(int serverIndex, const DockerContainer container) +{ + ErrorCode error = ErrorCode::NoError; + SshSession sshSession; + ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex); + + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); + if (container == DockerContainer::OpenVpn) { + clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(DockerContainer::OpenVpn)); + } else { + clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(container)); + } + + const QByteArray clientsTableString = sshSession.getTextFileFromContainer(container, credentials, clientsTableFile, error); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to get the clientsTable file from the server"; + emit clientsUpdated(QJsonArray()); + return error; + } + + m_clientsTable = QJsonDocument::fromJson(clientsTableString).array(); + + if (m_clientsTable.isEmpty()) { + migration(clientsTableString, m_clientsTable); + + int count = 0; + + if (container == DockerContainer::OpenVpn) { + error = getOpenVpnClients(container, credentials, &sshSession, count, m_clientsTable); + } else if (container == DockerContainer::WireGuard || ContainerUtils::isAwgContainer(container)) { + error = getWireGuardClients(container, credentials, &sshSession, count, m_clientsTable); + } else if (container == DockerContainer::Xray) { + error = getXrayClients(container, credentials, &sshSession, count, m_clientsTable); + } + if (error != ErrorCode::NoError) { + emit clientsUpdated(QJsonArray()); + return error; + } + + const QByteArray newClientsTableString = QJsonDocument(m_clientsTable).toJson(); + if (clientsTableString != newClientsTableString) { + error = sshSession.uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload the clientsTable file to the server"; + } + } + } + + std::vector data; + wgShow(container, credentials, &sshSession, data); + + for (const auto &client : data) { + int i = 0; + for (const auto &it : std::as_const(m_clientsTable)) { + if (it.isObject()) { + QJsonObject obj = it.toObject(); + if (obj.contains(configKey::clientId) && obj[configKey::clientId].toString() == client.clientId) { + QJsonObject userData = obj[configKey::userData].toObject(); + + if (!client.latestHandshake.isEmpty()) { + userData[configKey::latestHandshake] = client.latestHandshake; + } + + if (!client.dataReceived.isEmpty()) { + userData[configKey::dataReceived] = client.dataReceived; + } + + if (!client.dataSent.isEmpty()) { + userData[configKey::dataSent] = client.dataSent; + } + + if (!client.allowedIps.isEmpty()) { + userData[configKey::allowedIps] = client.allowedIps; + } + + obj[configKey::userData] = userData; + m_clientsTable.replace(i, obj); + break; + } + } + ++i; + } + } + + emit clientsUpdated(m_clientsTable); + return error; +} + + +ErrorCode UsersController::appendClient(int serverIndex, const QString &clientId, const QString &clientName, const DockerContainer container) +{ + ErrorCode error = ErrorCode::NoError; + SshSession sshSession; + ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex); + + error = updateClients(serverIndex, container); + if (error != ErrorCode::NoError) { + return error; + } + + int existingIndex = clientIndexById(clientId, m_clientsTable); + if (existingIndex >= 0) { + return renameClient(serverIndex, existingIndex, clientName, container, true); + } + + QJsonObject client; + client[configKey::clientId] = clientId; + + QJsonObject userData; + userData[configKey::clientName] = clientName; + userData[configKey::creationDate] = QDateTime::currentDateTime().toString(); + client[configKey::userData] = userData; + m_clientsTable.push_back(client); + + const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); + + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); + if (container == DockerContainer::OpenVpn) { + clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(DockerContainer::OpenVpn)); + } else { + clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(container)); + } + + error = sshSession.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload the clientsTable file to the server"; + return error; + } + + emit clientAdded(client); + emit clientsUpdated(m_clientsTable); + return error; +} + +ErrorCode UsersController::renameClient(int serverIndex, const int row, const QString &clientName, + const DockerContainer container, bool addTimeStamp) +{ + if (row < 0 || row >= m_clientsTable.size()) { + return ErrorCode::InternalError; + } + + SshSession sshSession; + ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex); + + auto client = m_clientsTable.at(row).toObject(); + auto userData = client[configKey::userData].toObject(); + userData[configKey::clientName] = clientName; + if (addTimeStamp) { + userData[configKey::creationDate] = QDateTime::currentDateTime().toString(); + } + client[configKey::userData] = userData; + + m_clientsTable.replace(row, client); + + const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); + + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); + if (container == DockerContainer::OpenVpn) { + clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(DockerContainer::OpenVpn)); + } else { + clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(container)); + } + + ErrorCode error = sshSession.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload the clientsTable file to the server"; + return error; + } + + if (addTimeStamp) { + emit clientsUpdated(m_clientsTable); + } else { + emit clientRenamed(row, clientName); + } + return error; +} + +ErrorCode UsersController::revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials, + const int serverIndex, SshSession* sshSession, QJsonArray &clientsTable) +{ + if (row < 0 || row >= clientsTable.size()) { + return ErrorCode::InternalError; + } + + auto client = clientsTable.at(row).toObject(); + QString clientId = client.value(configKey::clientId).toString(); + + const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '" + "cd /opt/amnezia/openvpn ;\\" + "easyrsa revoke %1 ;\\" + "easyrsa gen-crl ;\\" + "chmod 666 pki/crl.pem ;\\" + "cp pki/crl.pem .'") + .arg(clientId); + + const QString script = sshSession->replaceVars(getOpenVpnCertData, amnezia::genBaseVars(credentials, container, QString(), QString())); + ErrorCode error = sshSession->runScript(credentials, script); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to revoke the certificate"; + return error; + } + + clientsTable.removeAt(row); + + const QByteArray clientsTableString = QJsonDocument(clientsTable).toJson(); + + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); + clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(DockerContainer::OpenVpn)); + error = sshSession->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload the clientsTable file to the server"; + return error; + } + + return ErrorCode::NoError; +} + +ErrorCode UsersController::revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, QJsonArray &clientsTable) +{ + if (row < 0 || row >= clientsTable.size()) { + return ErrorCode::InternalError; + } + + ErrorCode error = ErrorCode::NoError; + + QString configPath; + if (container == DockerContainer::Awg) { + configPath = QString::fromLatin1(protocols::awg::serverLegacyConfigPath); + } else if (container == DockerContainer::Awg2) { + configPath = QString::fromLatin1(protocols::awg::serverConfigPath); + } else { + configPath = QString::fromLatin1(protocols::wireguard::serverConfigPath); + } + const QString wireguardConfigString = sshSession->getTextFileFromContainer(container, credentials, configPath, error); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to get the wg conf file from the server"; + return error; + } + + auto client = clientsTable.at(row).toObject(); + QString clientId = client.value(configKey::clientId).toString(); + + auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts); + for (auto §ion : configSections) { + if (section.contains(clientId)) { + configSections.removeOne(section); + break; + } + } + QString newWireGuardConfig = configSections.join("["); + newWireGuardConfig.insert(0, "["); + error = sshSession->uploadTextFileToContainer(container, credentials, newWireGuardConfig, configPath); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload the wg conf file to the server"; + return error; + } + + clientsTable.removeAt(row); + + const QByteArray clientsTableString = QJsonDocument(clientsTable).toJson(); + + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); + if (container == DockerContainer::OpenVpn) { + clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(DockerContainer::OpenVpn)); + } else { + clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(container)); + } + error = sshSession->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload the clientsTable file to the server"; + return error; + } + + bool isAwg2 = (container == DockerContainer::Awg2); + QString command = isAwg2 ? QStringLiteral("awg") : QStringLiteral("wg"); + QString iface = isAwg2 ? QStringLiteral("awg0") : QStringLiteral("wg0"); + QString script = QString( + "sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'" + ).arg(command, iface, configPath); + error = sshSession->runScript( + credentials, + sshSession->replaceVars(script, amnezia::genBaseVars(credentials, container, QString(), QString())) + ); + if (error != ErrorCode::NoError) { + logger.error() << QString("Failed to execute command '%1 syncconf %2' on the server").arg(command, iface); + return error; + } + + return ErrorCode::NoError; +} + +ErrorCode UsersController::revokeXray(const int row, + const DockerContainer container, + const ServerCredentials &credentials, + SshSession* sshSession, QJsonArray &clientsTable) +{ + if (row < 0 || row >= clientsTable.size()) { + return ErrorCode::InternalError; + } + + ErrorCode error = ErrorCode::NoError; + + const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath; + const QString configString = sshSession->getTextFileFromContainer(container, credentials, serverConfigPath, error); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to get the xray server config file"; + return error; + } + + QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8()); + if (serverConfig.isNull()) { + logger.error() << "Failed to parse xray server config JSON"; + return ErrorCode::InternalError; + } + + auto client = clientsTable.at(row).toObject(); + QString clientId = client.value(configKey::clientId).toString(); + + QJsonObject configObj = serverConfig.object(); + if (!configObj.contains(protocols::xray::inbounds)) { + logger.error() << "Missing inbounds in xray config"; + return ErrorCode::InternalError; + } + + QJsonArray inbounds = configObj[protocols::xray::inbounds].toArray(); + if (inbounds.isEmpty()) { + logger.error() << "Empty inbounds array in xray config"; + return ErrorCode::InternalError; + } + + QJsonObject inbound = inbounds[0].toObject(); + if (!inbound.contains(protocols::xray::settings)) { + logger.error() << "Missing settings in xray inbound config"; + return ErrorCode::InternalError; + } + + QJsonObject settings = inbound[protocols::xray::settings].toObject(); + if (!settings.contains(protocols::xray::clients)) { + logger.error() << "Missing clients in xray settings"; + return ErrorCode::InternalError; + } + + QJsonArray clients = settings[protocols::xray::clients].toArray(); + if (clients.isEmpty()) { + logger.error() << "Empty clients array in xray config"; + return ErrorCode::InternalError; + } + + for (int i = 0; i < clients.size(); ++i) { + QJsonObject clientObj = clients[i].toObject(); + if (clientObj.contains(protocols::xray::id) && clientObj[protocols::xray::id].toString() == clientId) { + clients.removeAt(i); + break; + } + } + + settings[protocols::xray::clients] = clients; + inbound[protocols::xray::settings] = settings; + inbounds[0] = inbound; + configObj[protocols::xray::inbounds] = inbounds; + + error = sshSession->uploadTextFileToContainer( + container, + credentials, + QJsonDocument(configObj).toJson(), + serverConfigPath + ); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload updated xray config"; + return error; + } + + clientsTable.removeAt(row); + + const QByteArray clientsTableString = QJsonDocument(clientsTable).toJson(); + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable") + .arg(ContainerUtils::containerTypeToString(container)); + + error = sshSession->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload the clientsTable file"; + } + + QString restartScript = QString("sudo docker restart $CONTAINER_NAME"); + error = sshSession->runScript( + credentials, + sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, QString(), QString())) + ); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to restart xray container"; + return error; + } + + return error; +} + +ErrorCode UsersController::revokeClient(int serverIndex, const int index, const DockerContainer container) +{ + if (index < 0 || index >= m_clientsTable.size()) { + return ErrorCode::InternalError; + } + + SshSession sshSession; + ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex); + + QString clientId = m_clientsTable.at(index).toObject().value(configKey::clientId).toString(); + ErrorCode errorCode = ErrorCode::NoError; + + switch(container) + { + case DockerContainer::OpenVpn: { + errorCode = revokeOpenVpn(index, container, credentials, serverIndex, &sshSession, m_clientsTable); + break; + } + case DockerContainer::WireGuard: + case DockerContainer::Awg: + case DockerContainer::Awg2: { + errorCode = revokeWireGuard(index, container, credentials, &sshSession, m_clientsTable); + break; + } + case DockerContainer::Xray: { + errorCode = revokeXray(index, container, credentials, &sshSession, m_clientsTable); + break; + } + default: { + logger.error() << "Internal error: received unexpected container type"; + return ErrorCode::InternalError; + } + } + + if (errorCode == ErrorCode::NoError) { + ServerConfig serverConfig = m_serversRepository->server(serverIndex); + ContainerConfig containerCfg = m_serversRepository->containerConfig(serverIndex, container); + QString containerClientId = containerCfg.protocolConfig.clientId(); + + if (!clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId)) { + emit adminConfigRevoked(serverIndex, container); + } + + emit clientRevoked(index); + emit clientsUpdated(m_clientsTable); + } + + return errorCode; +} + +ErrorCode UsersController::revokeClient(int serverIndex, const ContainerConfig &containerConfig, const DockerContainer container) +{ + SshSession sshSession; + ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex); + + ErrorCode errorCode = ErrorCode::NoError; + errorCode = updateClients(serverIndex, container); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + Proto protocol = containerConfig.getProtocolType(); + + switch(container) + { + case DockerContainer::OpenVpn: + case DockerContainer::WireGuard: + case DockerContainer::Awg: + case DockerContainer::Awg2: + case DockerContainer::Xray: { + protocol = ContainerUtils::defaultProtocol(container); + break; + } + default: { + logger.error() << "Internal error: received unexpected container type"; + return ErrorCode::InternalError; + } + } + + QString clientId = containerConfig.protocolConfig.clientId(); + + int row = clientIndexById(clientId, m_clientsTable); + if (row < 0) { + return errorCode; + } + + switch (container) + { + case DockerContainer::OpenVpn: { + errorCode = revokeOpenVpn(row, container, credentials, serverIndex, &sshSession, m_clientsTable); + break; + } + case DockerContainer::WireGuard: + case DockerContainer::Awg: + case DockerContainer::Awg2: { + errorCode = revokeWireGuard(row, container, credentials, &sshSession, m_clientsTable); + break; + } + case DockerContainer::Xray: { + errorCode = revokeXray(row, container, credentials, &sshSession, m_clientsTable); + break; + } + default: + logger.error() << "Internal error: received unexpected container type"; + return ErrorCode::InternalError; + } + + if (errorCode == ErrorCode::NoError) { + emit adminConfigRevoked(serverIndex, container); + emit clientRevoked(row); + emit clientsUpdated(m_clientsTable); + } + + return errorCode; +} + diff --git a/client/core/controllers/selfhosted/usersController.h b/client/core/controllers/selfhosted/usersController.h new file mode 100644 index 000000000..89bc94b90 --- /dev/null +++ b/client/core/controllers/selfhosted/usersController.h @@ -0,0 +1,76 @@ +#ifndef USERSCONTROLLER_H +#define USERSCONTROLLER_H + +#include +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/selfhosted/sshSession.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/repositories/secureServersRepository.h" +#include "core/models/containerConfig.h" +#include "core/models/protocolConfig.h" + +class UsersController : public QObject +{ + Q_OBJECT + +public: + struct WgShowData + { + QString clientId; + QString latestHandshake; + QString dataReceived; + QString dataSent; + QString allowedIps; + }; + + explicit UsersController(SecureServersRepository* serversRepository, QObject *parent = nullptr); + +signals: + void clientsUpdated(const QJsonArray &clients); + void clientAdded(const QJsonObject &client); + void clientRenamed(int row, const QString &newName); + void clientRevoked(int row); + void adminConfigRevoked(int serverIndex, DockerContainer container); + +public slots: + ErrorCode updateClients(int serverIndex, const DockerContainer container); + ErrorCode appendClient(int serverIndex, const QString &clientId, const QString &clientName, const DockerContainer container); + ErrorCode renameClient(int serverIndex, const int row, const QString &userName, const DockerContainer container, bool addTimeStamp = false); + ErrorCode revokeClient(int serverIndex, const int index, const DockerContainer container); + ErrorCode revokeClient(int serverIndex, const ContainerConfig &containerConfig, const DockerContainer container); + +private: + bool isClientExists(const QString &clientId, const QJsonArray &clientsTable); + int clientIndexById(const QString &clientId, const QJsonArray &clientsTable); + void migration(const QByteArray &clientsTableString, QJsonArray &clientsTable); + + ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials, const int serverIndex, + SshSession* sshSession, QJsonArray &clientsTable); + ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, QJsonArray &clientsTable); + ErrorCode revokeXray(const int row, const DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, QJsonArray &clientsTable); + + ErrorCode getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, int &count, QJsonArray &clientsTable); + ErrorCode getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, int &count, QJsonArray &clientsTable); + ErrorCode getXrayClients(const DockerContainer container, const ServerCredentials& credentials, + SshSession* sshSession, int &count, QJsonArray &clientsTable); + + ErrorCode wgShow(const DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, std::vector &data); + + SecureServersRepository* m_serversRepository; + QJsonArray m_clientsTable; +}; + +#endif // USERSCONTROLLER_H + diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp deleted file mode 100644 index b516f43b7..000000000 --- a/client/core/controllers/serverController.cpp +++ /dev/null @@ -1,887 +0,0 @@ -#include "serverController.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include "containers/containers_defs.h" -#include "core/networkUtilities.h" -#include "core/scripts_registry.h" -#include "core/server_defs.h" -#include "logger.h" -#include "settings.h" -#include "utilities.h" -#include "vpnConfigurationController.h" - -namespace -{ - Logger logger("ServerController"); -} - -ServerController::ServerController(std::shared_ptr settings, QObject *parent) : m_settings(settings) -{ -} - -ServerController::~ServerController() -{ - m_sshClient.disconnectFromHost(); -} - -ErrorCode ServerController::runScript(const ServerCredentials &credentials, QString script, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr) -{ - - auto error = m_sshClient.connectToHost(credentials); - if (error != ErrorCode::NoError) { - return error; - } - - script.replace("\r", ""); - - qDebug() << "ServerController::Run script"; - - QString totalLine; - const QStringList &lines = script.split("\n", Qt::SkipEmptyParts); - for (int i = 0; i < lines.count(); i++) { - QString currentLine = lines.at(i); - - if (totalLine.isEmpty()) { - totalLine = currentLine; - } else { - totalLine = totalLine + "\n" + currentLine; - } - - QString lineToExec; - if (currentLine.endsWith("\\")) { - continue; - } else { - lineToExec = totalLine; - totalLine.clear(); - } - - if (lineToExec.startsWith("#")) { - continue; - } - - qDebug().noquote() << lineToExec; - - error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr); - if (error != ErrorCode::NoError) { - return error; - } - } - - qDebug().noquote() << "ServerController::runScript finished\n"; - return ErrorCode::NoError; -} - -ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr) -{ - QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh"; - - ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName); - if (e) - return e; - - QString runner = - QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash")); - e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); - - QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName); - runScript(credentials, replaceVars(remover, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); - - return e; -} - -ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file, - const QString &path, libssh::ScpOverwriteMode overwriteMode) -{ - ErrorCode e = ErrorCode::NoError; - QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16)); - e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName); - if (e) - return e; - - QString stdOut; - auto cbReadStd = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - // mkdir - QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path); - - e = runScript(credentials, replaceVars(mkdir, genVarsForScript(credentials, container))); - if (e) - return e; - - if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) { - e = runScript(credentials, - replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path), - genVarsForScript(credentials, container)), - cbReadStd, cbReadStd); - - if (e) - return e; - } else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) { - e = runScript(credentials, - replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName), - genVarsForScript(credentials, container)), - cbReadStd, cbReadStd); - - if (e) - return e; - - e = runScript(credentials, - replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path), - genVarsForScript(credentials, container)), - cbReadStd, cbReadStd); - - if (e) - return e; - } else - return ErrorCode::NotImplementedError; - - if (stdOut.contains("Error") && stdOut.contains("No such container")) { - return ErrorCode::ServerContainerMissingError; - } - - runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container))); - return e; -} - -QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, - ErrorCode &errorCode) -{ - - errorCode = ErrorCode::NoError; - - QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2'\"").arg(ContainerProps::containerToString(container), path); - - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data; - return ErrorCode::NoError; - }; - - errorCode = runScript(credentials, script, cbReadStdOut); - return QByteArray::fromHex(stdOut.toUtf8()); -} - -ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, - libssh::ScpOverwriteMode overwriteMode) -{ - auto error = m_sshClient.connectToHost(credentials); - if (error != ErrorCode::NoError) { - return error; - } - - QTemporaryFile localFile; - localFile.open(); - localFile.write(data); - localFile.close(); - - error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc"); - - if (error != ErrorCode::NoError) { - return error; - } - return ErrorCode::NoError; -} - -ErrorCode ServerController::rebootServer(const ServerCredentials &credentials) -{ - QString script = QString("sudo reboot"); - - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data; - return ErrorCode::NoError; - }; - - auto cbReadStdErr = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - return runScript(credentials, script, cbReadStdOut, cbReadStdErr); -} - -ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials) -{ - return runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers)); -} - -ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container) -{ - return runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::remove_container), genVarsForScript(credentials, container))); -} - -ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate) -{ - qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container); - ErrorCode e = ErrorCode::NoError; - - e = isUserInSudo(credentials, container); - if (e) - return e; - - e = isServerDpkgBusy(credentials, container); - if (e) - return e; - - e = installDockerWorker(credentials, container); - if (e) - return e; - qDebug().noquote() << "ServerController::setupContainer installDockerWorker finished"; - - if (!isUpdate) { - e = isServerPortBusy(credentials, container, config); - if (e) - return e; - } - - if (!isUpdate) { - e = isServerPortBusy(credentials, container, config); - if (e) - return e; - } - - e = prepareHostWorker(credentials, container, config); - if (e) - return e; - qDebug().noquote() << "ServerController::setupContainer prepareHostWorker finished"; - - removeContainer(credentials, container); - qDebug().noquote() << "ServerController::setupContainer removeContainer finished"; - - qDebug().noquote() << "buildContainerWorker start"; - e = buildContainerWorker(credentials, container, config); - if (e) - return e; - qDebug().noquote() << "ServerController::setupContainer buildContainerWorker finished"; - - e = runContainerWorker(credentials, container, config); - if (e) - return e; - qDebug().noquote() << "ServerController::setupContainer runContainerWorker finished"; - - e = configureContainerWorker(credentials, container, config); - if (e) - return e; - qDebug().noquote() << "ServerController::setupContainer configureContainerWorker finished"; - - setupServerFirewall(credentials); - qDebug().noquote() << "ServerController::setupContainer setupServerFirewall finished"; - - return startupContainerWorker(credentials, container, config); -} - -ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig, - QJsonObject &newConfig) -{ - bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); - qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired; - - if (reinstallRequired) { - return setupContainer(credentials, container, newConfig, true); - } else { - ErrorCode e = configureContainerWorker(credentials, container, newConfig); - if (e) - return e; - - return startupContainerWorker(credentials, container, newConfig); - } -} - -bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig) -{ - Proto mainProto = ContainerProps::defaultProtocol(container); - - const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); - const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); - - if (container == DockerContainer::OpenVpn) { - if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) - != newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto)) - return true; - - if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) - != newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)) - return true; - } - - if (container == DockerContainer::Cloak) { - if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort) - != newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort)) - return true; - } - - if (container == DockerContainer::ShadowSocks) { - if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) - != newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)) - return true; - } - - if (ContainerProps::isAwgContainer(container)) { - if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) - != newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)) - || (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort) - != newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)) - || (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount) - != newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)) - || (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize) - != newProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)) - || (oldProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize) - != newProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize)) - || (oldProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize) - != newProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize)) - || (oldProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize) - != newProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize)) - || (oldProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader) - != newProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader)) - || (oldProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader) - != newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader)) - || (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader) - != newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)) - || (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)) - != newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader) - || (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize) - != newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)) - || (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize) - != newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize))) - - return true; - } - - if (container == DockerContainer::WireGuard) { - if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) - != newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)) - || (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) - != newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))) - return true; - } - - if (container == DockerContainer::Socks5Proxy) { - return true; - } - - if (container == DockerContainer::Xray) { - if (oldProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort) - != newProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)) { - return true; - } - } - - return false; -} - -ErrorCode ServerController::installDockerWorker(const ServerCredentials &credentials, DockerContainer container) -{ - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &client) { - stdOut += data + "\n"; - - if (data.contains("Automatically restart Docker daemon?")) { - return client.writeResponse("yes"); - } - return ErrorCode::NoError; - }; - auto cbReadStdErr = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - ErrorCode error = - runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)), - cbReadStdOut, cbReadStdErr); - - qDebug().noquote() << "ServerController::installDockerWorker" << stdOut; - if (container == DockerContainer::Awg2) { - QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)"); - QRegularExpressionMatch match = regex.match(stdOut); - if (match.hasMatch()) { - int majorVersion = match.captured(1).toInt(); - int minorVersion = match.captured(2).toInt(); - - if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 14)) { - return ErrorCode::ServerLinuxKernelTooOld; - } - } - } - if (stdOut.contains("lock")) - return ErrorCode::ServerPacketManagerError; - if (stdOut.contains("command not found")) - return ErrorCode::ServerDockerFailedError; - - return error; -} - -ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) -{ - // create folder on host - return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container))); -} - -ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) -{ - QString dockerFilePath = amnezia::server::getDockerfileFolder(container) + "/Dockerfile"; - QString scriptString = QString("sudo rm %1").arg(dockerFilePath); - ErrorCode errorCode = runScript(credentials, replaceVars(scriptString, genVarsForScript(credentials, container))); - if (errorCode) - return errorCode; - - errorCode = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(), dockerFilePath); - - if (errorCode) - return errorCode; - - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - auto cbReadStdErr = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - ErrorCode error = - runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)), - cbReadStdOut, cbReadStdErr); - - if (stdOut.contains("doesn't work on cgroups v2")) - return ErrorCode::ServerDockerOnCgroupsV2; - if (stdOut.contains("cgroup mountpoint does not exist")) - return ErrorCode::ServerCgroupMountpoint; - if (stdOut.contains("have reached") && stdOut.contains("pull rate limit")) - return ErrorCode::DockerPullRateLimit; - - return error; -} - -ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) -{ - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - ErrorCode e = runScript(credentials, - replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), - genVarsForScript(credentials, container, config)), - cbReadStdOut); - - if (stdOut.contains("address already in use")) - return ErrorCode::ServerPortAlreadyAllocatedError; - if (stdOut.contains("is already in use by container")) - return ErrorCode::ServerPortAlreadyAllocatedError; - if (stdOut.contains("invalid publish")) - return ErrorCode::ServerDockerFailedError; - - return e; -} - -ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) -{ - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - auto cbReadStdErr = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - ErrorCode e = runContainerScript(credentials, container, - replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), - genVarsForScript(credentials, container, config)), - cbReadStdOut, cbReadStdErr); - - VpnConfigurationsController::updateContainerConfigAfterInstallation(container, config, stdOut); - - return e; -} - -ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) -{ - QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container); - - if (script.isEmpty()) { - return ErrorCode::NoError; - } - - ErrorCode e = uploadTextFileToContainer(container, credentials, replaceVars(script, genVarsForScript(credentials, container, config)), - "/opt/amnezia/start.sh"); - if (e) - return e; - - return runScript(credentials, - replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && " - "/opt/amnezia/start.sh\"", - genVarsForScript(credentials, container, config))); -} - -ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &config) -{ - const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject(); - const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject(); - const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject(); - const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject(); - const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject(); - const QJsonObject &xrayConfig = config.value(ProtocolProps::protoToString(Proto::Xray)).toObject(); - const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject(); - const QJsonObject &socks5ProxyConfig = config.value(ProtocolProps::protoToString(Proto::Socks5Proxy)).toObject(); - - Vars vars; - - vars.append({ { "$REMOTE_HOST", credentials.hostName } }); - - // OpenVPN vars - vars.append({ { "$OPENVPN_SUBNET_IP", - openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } }); - vars.append({ { "$OPENVPN_SUBNET_CIDR", openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } }); - vars.append({ { "$OPENVPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } }); - - vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } }); - vars.append({ { "$OPENVPN_TRANSPORT_PROTO", - openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } }); - - bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); - vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } }); - - vars.append({ { "$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } }); - vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } }); - - bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); - vars.append({ { "$OPENVPN_TLS_AUTH", isTlsAuth ? protocols::openvpn::tlsAuthString : "" } }); - if (!isTlsAuth) { - // erase $OPENVPN_TA_KEY, so it will not set in OpenVpnConfigurator::genOpenVpnConfig - vars.append({ { "$OPENVPN_TA_KEY", "" } }); - } - - vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG", - openvpnConfig.value(config_key::additional_client_config).toString(protocols::openvpn::defaultAdditionalClientConfig) } }); - vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG", - openvpnConfig.value(config_key::additional_server_config).toString(protocols::openvpn::defaultAdditionalServerConfig) } }); - - // ShadowSocks vars - vars.append({ { "$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } }); - vars.append({ { "$SHADOWSOCKS_LOCAL_PORT", - ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } }); - vars.append({ { "$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } }); - - vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } }); - vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } }); - - // Cloak vars - vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } }); - vars.append({ { "$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } }); - - // Xray vars - vars.append({ { "$XRAY_SITE_NAME", xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } }); - vars.append({ { "$XRAY_SERVER_PORT", xrayConfig.value(config_key::port).toString(protocols::xray::defaultPort) } }); - - // Wireguard vars - vars.append({ { "$WIREGUARD_SUBNET_IP", - wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } }); - vars.append({ { "$WIREGUARD_SUBNET_CIDR", - wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } }); - vars.append({ { "$WIREGUARD_SUBNET_MASK", - wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } }); - - vars.append({ { "$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } }); - - // IPsec vars - vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } }); - vars.append({ { "$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250" } }); - vars.append({ { "$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1" } }); - - vars.append({ { "$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24" } }); - vars.append({ { "$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250" } }); - - vars.append({ { "$IPSEC_VPN_SHA2_TRUNCBUG", "yes" } }); - - vars.append({ { "$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes" } }); - vars.append({ { "$IPSEC_VPN_DISABLE_IKEV2", "no" } }); - vars.append({ { "$IPSEC_VPN_DISABLE_L2TP", "no" } }); - vars.append({ { "$IPSEC_VPN_DISABLE_XAUTH", "no" } }); - - vars.append({ { "$IPSEC_VPN_C2C_TRAFFIC", "no" } }); - - vars.append({ { "$PRIMARY_SERVER_DNS", m_settings->primaryDns() } }); - vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } }); - - // Sftp vars - vars.append({ { "$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } }); - vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } }); - vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } }); - - // Amnezia wireguard vars - vars.append({ { "$AWG_SUBNET_IP", - amneziaWireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } }); - vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } }); - - vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } }); - vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } }); - vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } }); - vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } }); - vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } }); - vars.append({ { "$INIT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } }); - vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } }); - vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } }); - vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } }); - - vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } }); - vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } }); - vars.append({ { "$SPECIAL_JUNK_1", amneziaWireguarConfig.value(config_key::specialJunk1).toString() } }); - vars.append({ { "$SPECIAL_JUNK_2", amneziaWireguarConfig.value(config_key::specialJunk2).toString() } }); - vars.append({ { "$SPECIAL_JUNK_3", amneziaWireguarConfig.value(config_key::specialJunk3).toString() } }); - vars.append({ { "$SPECIAL_JUNK_4", amneziaWireguarConfig.value(config_key::specialJunk4).toString() } }); - vars.append({ { "$SPECIAL_JUNK_5", amneziaWireguarConfig.value(config_key::specialJunk5).toString() } }); - - // Socks5 proxy vars - vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } }); - auto username = socks5ProxyConfig.value(config_key::userName).toString(); - auto password = socks5ProxyConfig.value(config_key::password).toString(); - QString socks5user = (!username.isEmpty() && !password.isEmpty()) ? QString("users %1:CL:%2").arg(username, password) : ""; - vars.append({ { "$SOCKS5_USER", socks5user } }); - vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } }); - - QString serverIp = (!ContainerProps::isAwgContainer(container) && - container != DockerContainer::WireGuard && container != DockerContainer::Xray) - ? NetworkUtilities::getIPAddress(credentials.hostName) - : credentials.hostName; - if (!serverIp.isEmpty()) { - vars.append({ { "$SERVER_IP_ADDRESS", serverIp } }); - } else { - qWarning() << "ServerController::genVarsForScript unable to resolve address for credentials.hostName"; - } - - return vars; -} - -QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode) -{ - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - auto cbReadStdErr = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - errorCode = runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); - - return stdOut; -} - -void ServerController::cancelInstallation() -{ - m_cancelInstallation = true; -} - -ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials) -{ - return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials))); -} - -QString ServerController::replaceVars(const QString &script, const Vars &vars) -{ - QString s = script; - for (const QPair &var : vars) { - s.replace(var.first, var.second); - } - return s; -} - -ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) -{ - if (container == DockerContainer::Dns) { - return ErrorCode::NoError; - } - - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - auto cbReadStdErr = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - const Proto protocol = ContainerProps::defaultProtocol(container); - const QString containerString = ProtocolProps::protoToString(protocol); - const QJsonObject containerConfig = config.value(containerString).toObject(); - - QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container); - - QString defaultPort("%1"); - QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol))); - QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol); - QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto); - - // TODO reimplement with netstat - QString script = QString("which lsof > /dev/null 2>&1 || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port); - for (auto &port : fixedPorts) { - script = script.append("|:%1").arg(port); - } - - if (transportProto == "tcpandudp") { - QString tcpProtoScript = script; - QString udpProtoScript = script; - tcpProtoScript.append("' | grep -i tcp"); - udpProtoScript.append("' | grep -i udp"); - tcpProtoScript.append(" | grep LISTEN"); - - ErrorCode errorCode = - runScript(credentials, replaceVars(tcpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - errorCode = runScript(credentials, replaceVars(udpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - if (!stdOut.isEmpty()) { - return ErrorCode::ServerPortAlreadyAllocatedError; - } - return ErrorCode::NoError; - } - - script = script.append("' | grep -i %1").arg(transportProto); - - if (transportProto == "tcp") { - script = script.append(" | grep LISTEN"); - } - - ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - if (!stdOut.isEmpty()) { - return ErrorCode::ServerPortAlreadyAllocatedError; - } - return ErrorCode::NoError; -} - -ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container) -{ - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - auto cbReadStdErr = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo); - ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); - - if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found")) - return ErrorCode::ServerSudoPackageIsNotPreinstalled; - if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel")) - 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")) - return ErrorCode::ServerUserNotAllowedInSudoers; - if (stdOut.contains("password is required")) - return ErrorCode::ServerUserPasswordRequired; - - return error; -} - -ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container) -{ - m_cancelInstallation = false; - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - auto cbReadStdErr = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - QFutureWatcher watcher; - - QFuture future = QtConcurrent::run([this, &stdOut, &cbReadStdOut, &cbReadStdErr, &credentials]() { - // max 100 attempts - for (int i = 0; i < 30; ++i) { - if (m_cancelInstallation) { - return ErrorCode::ServerCancelInstallation; - } - stdOut.clear(); - runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), genVarsForScript(credentials)), - cbReadStdOut, cbReadStdErr); - - if (stdOut.contains("Packet manager not found")) - return ErrorCode::ServerPacketManagerError; - if (stdOut.contains("fuser not installed") || stdOut.contains("cat not installed")) - return ErrorCode::NoError; - - if (stdOut.isEmpty()) { - return ErrorCode::NoError; - } else { -#ifdef MZ_DEBUG - qDebug().noquote() << stdOut; -#endif - emit serverIsBusy(true); - QThread::msleep(10000); - } - } - return ErrorCode::ServerPacketManagerError; - }); - - QEventLoop wait; - QObject::connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); - watcher.setFuture(future); - wait.exec(); - - emit serverIsBusy(false); - - return future.result(); -} - -ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, - const std::function &callback) -{ - auto error = m_sshClient.getDecryptedPrivateKey(credentials, decryptedPrivateKey, callback); - return error; -} diff --git a/client/core/controllers/serverController.h b/client/core/controllers/serverController.h deleted file mode 100644 index c87d15235..000000000 --- a/client/core/controllers/serverController.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef SERVERCONTROLLER_H -#define SERVERCONTROLLER_H - -#include -#include - -#include "containers/containers_defs.h" -#include "core/defs.h" -#include "core/sshclient.h" - -class Settings; -class VpnConfigurator; - -using namespace amnezia; - -class ServerController : public QObject -{ - Q_OBJECT -public: - ServerController(std::shared_ptr settings, QObject *parent = nullptr); - ~ServerController(); - - typedef QList> Vars; - - ErrorCode rebootServer(const ServerCredentials &credentials); - ErrorCode removeAllContainers(const ServerCredentials &credentials); - ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); - ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate = false); - ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig, - QJsonObject &newConfig); - - ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &config = QJsonObject()); - - ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file, - const QString &path, - libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting); - QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, - ErrorCode &errorCode); - - QString replaceVars(const QString &script, const Vars &vars); - Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, - const QJsonObject &config = QJsonObject()); - - ErrorCode runScript(const ServerCredentials &credentials, QString script, - const std::function &cbReadStdOut = nullptr, - const std::function &cbReadStdErr = nullptr); - - ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, - const std::function &cbReadStdOut = nullptr, - const std::function &cbReadStdErr = nullptr); - - QString checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode); - - void cancelInstallation(); - - ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, - const std::function &callback); - -private: - ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); - ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); - ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &config = QJsonObject()); - ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); - ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); - - ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config); - bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig); - ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container); - ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container); - - ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, - libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting); - - ErrorCode setupServerFirewall(const ServerCredentials &credentials); - - std::shared_ptr m_settings; - std::shared_ptr m_configurator; - - bool m_cancelInstallation = false; - libssh::Client m_sshClient; -signals: - void serverIsBusy(const bool isBusy); -}; - -#endif // SERVERCONTROLLER_H diff --git a/client/core/controllers/serversController.cpp b/client/core/controllers/serversController.cpp new file mode 100644 index 000000000..1842775b8 --- /dev/null +++ b/client/core/controllers/serversController.cpp @@ -0,0 +1,205 @@ +#include "serversController.h" +#include "core/utils/networkUtilities.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.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/models/serverConfig.h" +#include "core/models/containerConfig.h" + +#if defined(Q_OS_IOS) || defined(MACOS_NE) + #include +#endif + + +ServersController::ServersController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository, + QObject *parent) + : QObject(parent), m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository) +{ + recomputeGatewayStacks(); +} + +void ServersController::addServer(const ServerConfig &server) +{ + m_serversRepository->addServer(server); +} + +void ServersController::editServer(int index, const ServerConfig &server) +{ + m_serversRepository->editServer(index, server); +} + +void ServersController::removeServer(int index) +{ + m_serversRepository->removeServer(index); +} + +void ServersController::setDefaultServerIndex(int index) +{ + m_serversRepository->setDefaultServer(index); +} + +void ServersController::setDefaultContainer(int serverIndex, DockerContainer container) +{ + m_serversRepository->setDefaultContainer(serverIndex, container); +} + +void ServersController::updateContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config) +{ + m_serversRepository->setContainerConfig(serverIndex, container, config); +} + +void ServersController::clearCachedProfile(int serverIndex, DockerContainer container) +{ + m_serversRepository->clearLastConnectionConfig(serverIndex, container); +} + +QJsonArray ServersController::getServersArray() const +{ + QJsonArray result; + QVector servers = m_serversRepository->servers(); + for (const ServerConfig& server : servers) { + result.append(server.toJson()); + } + return result; +} + +QVector ServersController::getServers() const +{ + return m_serversRepository->servers(); +} + +ContainerConfig ServersController::getContainerConfig(int serverIndex, DockerContainer container) const +{ + return m_serversRepository->containerConfig(serverIndex, container); +} + +int ServersController::getDefaultServerIndex() const +{ + return m_serversRepository->defaultServerIndex(); +} + +int ServersController::getServersCount() const +{ + return m_serversRepository->serversCount(); +} + +ServerConfig ServersController::getServerConfig(int serverIndex) const +{ + return m_serversRepository->server(serverIndex); +} + +ServerCredentials ServersController::getServerCredentials(int serverIndex) const +{ + return m_serversRepository->serverCredentials(serverIndex); +} + +QPair ServersController::getDnsPair(int serverIndex, bool isAmneziaDnsEnabled) const +{ + ServerConfig serverConfig = m_serversRepository->server(serverIndex); + return serverConfig.getDnsPair(isAmneziaDnsEnabled, + m_appSettingsRepository->primaryDns(), + m_appSettingsRepository->secondaryDns()); +} + +ServersController::GatewayStacksData ServersController::gatewayStacks() const +{ + return m_gatewayStacks; +} + +void ServersController::recomputeGatewayStacks() +{ + GatewayStacksData computed; + bool hasNewTags = false; + QVector servers = m_serversRepository->servers(); + + for (const ServerConfig& serverConfig : servers) { + if (serverConfig.isApiV2()) { + const ApiV2ServerConfig* apiV2 = serverConfig.as(); + if (!apiV2) continue; + const QString userCountryCode = apiV2->apiConfig.userCountryCode; + const QString serviceType = apiV2->serviceType(); + + if (!userCountryCode.isEmpty()) { + if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) { + hasNewTags = true; + } + computed.userCountryCodes.insert(userCountryCode); + } + + if (!serviceType.isEmpty()) { + if (!m_gatewayStacks.serviceTypes.contains(serviceType)) { + hasNewTags = true; + } + computed.serviceTypes.insert(serviceType); + } + } + } + + m_gatewayStacks = std::move(computed); + if (hasNewTags) { + emit gatewayStacksExpanded(); + } +} + +bool ServersController::GatewayStacksData::operator==(const GatewayStacksData &other) const +{ + return userCountryCodes == other.userCountryCodes && serviceTypes == other.serviceTypes; +} + +QJsonObject ServersController::GatewayStacksData::toJson() const +{ + QJsonObject json; + + QJsonArray userCountryCodesArray; + for (const QString &code : userCountryCodes) { + userCountryCodesArray.append(code); + } + json[apiDefs::key::userCountryCode] = userCountryCodesArray; + + QJsonArray serviceTypesArray; + for (const QString &type : serviceTypes) { + serviceTypesArray.append(type); + } + json[apiDefs::key::serviceType] = serviceTypesArray; + + return json; +} + +bool ServersController::isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) const +{ + QVector servers = m_serversRepository->servers(); + for (const ServerConfig& serverConfig : servers) { + if (serverConfig.isApiV2()) { + const ApiV2ServerConfig* apiV2 = serverConfig.as(); + if (!apiV2) return false; + if (apiV2->apiConfig.userCountryCode == userCountryCode + && apiV2->serviceType() == serviceType + && apiV2->serviceProtocol() == serviceProtocol) { + return true; + } + } + } + return false; +} + +bool ServersController::hasInstalledContainers(int serverIndex) const +{ + ServerConfig serverConfig = m_serversRepository->server(serverIndex); + QMap containers = serverConfig.containers(); + for (auto it = containers.begin(); it != containers.end(); ++it) { + DockerContainer container = it.key(); + if (ContainerUtils::containerService(container) == ServiceType::Vpn) { + return true; + } + if (container == DockerContainer::SSXray) { + return true; + } + } + return false; +} + diff --git a/client/core/controllers/serversController.h b/client/core/controllers/serversController.h new file mode 100644 index 000000000..3b91e5558 --- /dev/null +++ b/client/core/controllers/serversController.h @@ -0,0 +1,96 @@ +#ifndef SERVERSCONTROLLER_H +#define SERVERSCONTROLLER_H + +#include +#include +#include +#include +#include + +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/repositories/secureServersRepository.h" +#include "core/repositories/secureAppSettingsRepository.h" +#include "core/models/serverConfig.h" +#include "core/models/containerConfig.h" + +class SshSession; +class InstallController; + +using namespace amnezia; + +/** + * @brief Core business logic controller for server operations + * + * This controller contains pure business logic for managing servers. + */ +class ServersController : public QObject +{ + Q_OBJECT + +public: + struct GatewayStacksData + { + QSet userCountryCodes; + QSet serviceTypes; + + bool isEmpty() const { return userCountryCodes.isEmpty() && serviceTypes.isEmpty(); } + bool operator==(const GatewayStacksData &other) const; + QJsonObject toJson() const; + }; + +public: + explicit ServersController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository = nullptr, + QObject *parent = nullptr); + ~ServersController() = default; + + // Server management + void addServer(const ServerConfig &server); + void editServer(int index, const ServerConfig &server); + void removeServer(int index); + void setDefaultServerIndex(int index); + + // Container management + void setDefaultContainer(int serverIndex, DockerContainer container); + void updateContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config); + + // Cache management + void clearCachedProfile(int serverIndex, DockerContainer container); + + // Getters + QJsonArray getServersArray() const; + QVector getServers() const; + int getDefaultServerIndex() const; + int getServersCount() const; + ServerConfig getServerConfig(int serverIndex) const; + ServerCredentials getServerCredentials(int serverIndex) const; + ContainerConfig getContainerConfig(int serverIndex, DockerContainer container) const; + QPair getDnsPair(int serverIndex, bool isAmneziaDnsEnabled) const; + + GatewayStacksData gatewayStacks() const; + + // Validation + bool isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) const; + bool hasInstalledContainers(int serverIndex) const; + +signals: + void gatewayStacksExpanded(); + +public slots: + void recomputeGatewayStacks(); + +private: + SecureServersRepository* m_serversRepository; + SecureAppSettingsRepository* m_appSettingsRepository; + GatewayStacksData m_gatewayStacks; +}; + +#endif // SERVERSCONTROLLER_H + diff --git a/client/core/controllers/settingsController.cpp b/client/core/controllers/settingsController.cpp new file mode 100644 index 000000000..f4f2e4327 --- /dev/null +++ b/client/core/controllers/settingsController.cpp @@ -0,0 +1,366 @@ +#include "settingsController.h" + +#include +#include +#include +#include + +#include "version.h" +#include "ui/utils/qAutoStart.h" +#include "logger.h" +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" +#endif + +QString getPlatformName() +{ +#if defined(Q_OS_WINDOWS) + return "Windows"; +#elif defined(Q_OS_ANDROID) + return "Android"; +#elif defined(Q_OS_LINUX) + return "Linux"; +#elif defined(Q_OS_MACX) + return "MacOS"; +#elif defined(Q_OS_IOS) + return "iOS"; +#else + return "Unknown"; +#endif +} + +SettingsController::SettingsController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository, + QObject* parent) + : QObject(parent), + m_serversRepository(serversRepository), + m_appSettingsRepository(appSettingsRepository) +{ + m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH); + m_isDevModeEnabled = m_appSettingsRepository->isDevGatewayEnv(); +} + +void SettingsController::toggleAmneziaDns(bool enable) +{ + m_appSettingsRepository->setUseAmneziaDns(enable); +} + +bool SettingsController::isAmneziaDnsEnabled() const +{ + return m_appSettingsRepository->useAmneziaDns(); +} + +QString SettingsController::getPrimaryDns() const +{ + return m_appSettingsRepository->primaryDns(); +} + +void SettingsController::setPrimaryDns(const QString &dns) +{ + m_appSettingsRepository->setPrimaryDns(dns); +} + +QString SettingsController::getSecondaryDns() const +{ + return m_appSettingsRepository->secondaryDns(); +} + +void SettingsController::setSecondaryDns(const QString &dns) +{ + m_appSettingsRepository->setSecondaryDns(dns); +} + +bool SettingsController::isLoggingEnabled() const +{ + return m_appSettingsRepository->isSaveLogs(); +} + +void SettingsController::toggleLogging(bool enable) +{ + m_appSettingsRepository->setSaveLogs(enable); +#ifndef Q_OS_ANDROID + if (!enable) { + Logger::deInit(); + } else { + if (!Logger::init(false)) { + qWarning() << "Initialization of debug subsystem failed"; + } + } +#endif + Logger::setServiceLogsEnabled(enable); + + if (enable) { + m_appSettingsRepository->setLogEnableDate(QDateTime::currentDateTime()); + } +} + +void SettingsController::clearLogs() +{ +#ifdef Q_OS_ANDROID + AndroidController::instance()->clearLogs(); +#else + Logger::clearLogs(false); + Logger::clearServiceLogs(); +#endif +} + +QByteArray SettingsController::backupAppConfig() const +{ + QByteArray data = m_appSettingsRepository->backupAppConfig(); + QJsonDocument doc = QJsonDocument::fromJson(data); + QJsonObject config = doc.object(); + + config["AppPlatform"] = getPlatform(); + config["Conf/autoStart"] = isAutoStartEnabled(); + config["Conf/killSwitchEnabled"] = isKillSwitchEnabled(); + config["Conf/strictKillSwitchEnabled"] = isStrictKillSwitchEnabled(); + config["Conf/useAmneziaDns"] = isAmneziaDnsEnabled(); + + return QJsonDocument(config).toJson(); +} + +ErrorCode SettingsController::restoreAppConfigFromData(const QByteArray &data) +{ + if (!m_appSettingsRepository->restoreAppConfig(data)) { + return ErrorCode::RestoreBackupInvalidError; + } + + m_serversRepository->invalidateCache(); + + QJsonObject newConfigData = QJsonDocument::fromJson(data).object(); + +#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) || defined(Q_OS_MACX) + bool autoStart = false; + if (newConfigData.contains("Conf/autoStart")) { + autoStart = newConfigData["Conf/autoStart"].toBool(); + } + toggleAutoStart(autoStart); +#endif + +#if defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID) + int appSplitTunnelingRouteMode = newConfigData.value("Conf/appsRouteMode").toInt(); + bool appSplittunnelingEnabled = + newConfigData.value("Conf/appsSplitTunnelingEnabled").toVariant().toString().toLower() == "true"; + emit appSplitTunnelingRouteModeChanged(static_cast(appSplitTunnelingRouteMode)); + + #if defined(Q_OS_WINDOWS) + emit appSplitTunnelingRouteModeChanged(AppsRouteMode::VpnAllExceptApps); + #endif + + if (newConfigData.contains("AppPlatform")) { + if (newConfigData.value("AppPlatform").toString() != getPlatform()) { + emit appSplitTunnelingClearAppsList(); + } + } + + emit appSplitTunnelingToggled(appSplittunnelingEnabled); +#endif + + int siteSplitTunnelingRouteMode = newConfigData.value("Conf/routeMode").toInt(); + bool siteSplittunnelingEnabled = + newConfigData.value("Conf/sitesSplitTunnelingEnabled").toVariant().toString().toLower() == "true"; + emit siteSplitTunnelingRouteModeChanged(static_cast(siteSplitTunnelingRouteMode)); + emit siteSplitTunnelingToggled(siteSplittunnelingEnabled); + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + m_appSettingsRepository->setAutoConnect(false); + m_appSettingsRepository->setStartMinimized(false); + m_appSettingsRepository->setKillSwitchEnabled(false); + m_appSettingsRepository->setStrictKillSwitchEnabled(false); +#endif + + return ErrorCode::NoError; +} + +QString SettingsController::getAppVersion() const +{ + return m_appVersion; +} + +void SettingsController::clearSettings() +{ + int serverCount = m_serversRepository->serversCount(); + + m_appSettingsRepository->clearSettings(); + + m_serversRepository->setServersArray(QJsonArray()); + m_serversRepository->setDefaultServer(0); + + emit siteSplitTunnelingRouteModeChanged(RouteMode::VpnOnlyForwardSites); + emit siteSplitTunnelingToggled(false); + + emit appSplitTunnelingRouteModeChanged(AppsRouteMode::VpnAllExceptApps); + emit appSplitTunnelingToggled(false); + + toggleAutoStart(false); +} + +bool SettingsController::isAutoConnectEnabled() const +{ + return m_appSettingsRepository->isAutoConnect(); +} + +void SettingsController::toggleAutoConnect(bool enable) +{ + m_appSettingsRepository->setAutoConnect(enable); +} + +bool SettingsController::isAutoStartEnabled() const +{ + return Autostart::isAutostart(); +} + +void SettingsController::toggleAutoStart(bool enable) +{ + Autostart::setAutostart(enable); + if (!enable) { + toggleStartMinimized(false); + } +} + +bool SettingsController::isStartMinimizedEnabled() const +{ + return m_appSettingsRepository->isStartMinimized(); +} + +void SettingsController::toggleStartMinimized(bool enable) +{ + m_appSettingsRepository->setStartMinimized(enable); +} + +bool SettingsController::isScreenshotsEnabled() const +{ + return m_appSettingsRepository->isScreenshotsEnabled(); +} + +void SettingsController::toggleScreenshotsEnabled(bool enable) +{ + m_appSettingsRepository->setScreenshotsEnabled(enable); +} + +bool SettingsController::isNewsNotificationsEnabled() const +{ + return m_appSettingsRepository->isNewsNotifications(); +} + +void SettingsController::toggleNewsNotificationsEnabled(bool enable) +{ + m_appSettingsRepository->setNewsNotifications(enable); +} + +bool SettingsController::isKillSwitchEnabled() const +{ + return m_appSettingsRepository->isKillSwitchEnabled(); +} + +void SettingsController::toggleKillSwitch(bool enable) +{ + m_appSettingsRepository->setKillSwitchEnabled(enable); +} + +bool SettingsController::isStrictKillSwitchEnabled() const +{ + return m_appSettingsRepository->isStrictKillSwitchEnabled(); +} + +void SettingsController::toggleStrictKillSwitch(bool enable) +{ + m_appSettingsRepository->setStrictKillSwitchEnabled(enable); +} + +QString SettingsController::getInstallationUuid(bool createIfNotExists) const +{ + return m_appSettingsRepository->getInstallationUuid(createIfNotExists); +} + +void SettingsController::enableDevMode() +{ + m_isDevModeEnabled = true; +} + +bool SettingsController::isDevModeEnabled() const +{ + return m_isDevModeEnabled; +} + +void SettingsController::resetGatewayEndpoint() +{ + m_appSettingsRepository->resetGatewayEndpoint(); +} + +void SettingsController::setGatewayEndpoint(const QString &endpoint) +{ + m_appSettingsRepository->setGatewayEndpoint(endpoint); +} + +QString SettingsController::getGatewayEndpoint() const +{ + return m_appSettingsRepository->isDevGatewayEnv() ? "Dev endpoint" : m_appSettingsRepository->getGatewayEndpoint(); +} + +bool SettingsController::isDevGatewayEnv() const +{ + return m_appSettingsRepository->isDevGatewayEnv(); +} + +void SettingsController::toggleDevGatewayEnv(bool enabled) +{ + m_appSettingsRepository->toggleDevGatewayEnv(enabled); + if (enabled) { + m_appSettingsRepository->setDevGatewayEndpoint(); + } else { + m_appSettingsRepository->resetGatewayEndpoint(); + } +} + +bool SettingsController::isHomeAdLabelVisible() const +{ + return m_appSettingsRepository->isHomeAdLabelVisible(); +} + +void SettingsController::disableHomeAdLabel() +{ + m_appSettingsRepository->disableHomeAdLabel(); +} + +void SettingsController::checkIfNeedDisableLogs() +{ + if (m_appSettingsRepository->isSaveLogs()) { + m_loggingDisableDate = m_appSettingsRepository->getLogEnableDate().addDays(14); + if (m_loggingDisableDate <= QDateTime::currentDateTime()) { + toggleLogging(false); + clearLogs(); + } + } +} + +QString SettingsController::getPlatform() const +{ + return getPlatformName(); +} + +QLocale SettingsController::getAppLanguage() const +{ + return m_appSettingsRepository->getAppLanguage(); +} + +void SettingsController::setAppLanguage(const QLocale &locale) +{ + m_appSettingsRepository->setAppLanguage(locale); +} + +bool SettingsController::isPremV1MigrationReminderActive() const +{ + return m_appSettingsRepository->isPremV1MigrationReminderActive(); +} + +void SettingsController::disablePremV1MigrationReminder() +{ + m_appSettingsRepository->disablePremV1MigrationReminder(); +} + +QString SettingsController::nextAvailableServerName() const +{ + return m_appSettingsRepository->nextAvailableServerName(); +} + diff --git a/client/core/controllers/settingsController.h b/client/core/controllers/settingsController.h new file mode 100644 index 000000000..6edb410e4 --- /dev/null +++ b/client/core/controllers/settingsController.h @@ -0,0 +1,112 @@ +#ifndef SETTINGSCONTROLLER_H +#define SETTINGSCONTROLLER_H + +#include +#include +#include +#include + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/repositories/secureServersRepository.h" +#include "core/repositories/secureAppSettingsRepository.h" + +using namespace amnezia; + +class SettingsController : public QObject +{ + Q_OBJECT +public: + explicit SettingsController(SecureServersRepository* serversRepository, + SecureAppSettingsRepository* appSettingsRepository, + QObject* parent = nullptr); + ~SettingsController() = default; + + void toggleAmneziaDns(bool enable); + bool isAmneziaDnsEnabled() const; + + QString getPrimaryDns() const; + void setPrimaryDns(const QString &dns); + + QString getSecondaryDns() const; + void setSecondaryDns(const QString &dns); + + bool isLoggingEnabled() const; + void toggleLogging(bool enable); + + void clearLogs(); + + QByteArray backupAppConfig() const; + ErrorCode restoreAppConfigFromData(const QByteArray &data); + + QString getAppVersion() const; + + void clearSettings(); + + bool isAutoConnectEnabled() const; + void toggleAutoConnect(bool enable); + + bool isAutoStartEnabled() const; + void toggleAutoStart(bool enable); + + bool isStartMinimizedEnabled() const; + void toggleStartMinimized(bool enable); + + bool isScreenshotsEnabled() const; + void toggleScreenshotsEnabled(bool enable); + + bool isNewsNotificationsEnabled() const; + void toggleNewsNotificationsEnabled(bool enable); + + bool isKillSwitchEnabled() const; + void toggleKillSwitch(bool enable); + + bool isStrictKillSwitchEnabled() const; + void toggleStrictKillSwitch(bool enable); + + QString getInstallationUuid(bool createIfNotExists = true) const; + + void enableDevMode(); + + bool isPremV1MigrationReminderActive() const; + void disablePremV1MigrationReminder(); + + QString nextAvailableServerName() const; + bool isDevModeEnabled() const; + + void resetGatewayEndpoint(); + void setGatewayEndpoint(const QString &endpoint); + QString getGatewayEndpoint() const; + bool isDevGatewayEnv() const; + void toggleDevGatewayEnv(bool enabled); + + bool isHomeAdLabelVisible() const; + void disableHomeAdLabel(); + + void checkIfNeedDisableLogs(); + + QLocale getAppLanguage() const; + void setAppLanguage(const QLocale &locale); + +signals: + void siteSplitTunnelingRouteModeChanged(RouteMode mode); + void siteSplitTunnelingToggled(bool enabled); + void appSplitTunnelingRouteModeChanged(AppsRouteMode mode); + void appSplitTunnelingToggled(bool enabled); + void appSplitTunnelingClearAppsList(); + +private: + QString getPlatform() const; + + SecureServersRepository* m_serversRepository; + SecureAppSettingsRepository* m_appSettingsRepository; + + QString m_appVersion; + QDateTime m_loggingDisableDate; + bool m_isDevModeEnabled = false; +}; + +#endif + + diff --git a/client/core/controllers/vpnConfigurationController.cpp b/client/core/controllers/vpnConfigurationController.cpp deleted file mode 100644 index 27b18fd5b..000000000 --- a/client/core/controllers/vpnConfigurationController.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "vpnConfigurationController.h" - -#include "configurators/awg_configurator.h" -#include "configurators/cloak_configurator.h" -#include "configurators/ikev2_configurator.h" -#include "configurators/openvpn_configurator.h" -#include "configurators/shadowsocks_configurator.h" -#include "configurators/wireguard_configurator.h" -#include "configurators/xray_configurator.h" - -VpnConfigurationsController::VpnConfigurationsController(const std::shared_ptr &settings, - QSharedPointer serverController, QObject *parent) - : QObject { parent }, m_settings(settings), m_serverController(serverController) -{ -} - -QScopedPointer VpnConfigurationsController::createConfigurator(const Proto protocol) -{ - switch (protocol) { - case Proto::OpenVpn: return QScopedPointer(new OpenVpnConfigurator(m_settings, m_serverController)); - case Proto::ShadowSocks: return QScopedPointer(new ShadowSocksConfigurator(m_settings, m_serverController)); - case Proto::Cloak: return QScopedPointer(new CloakConfigurator(m_settings, m_serverController)); - case Proto::WireGuard: return QScopedPointer(new WireguardConfigurator(m_settings, m_serverController, false)); - case Proto::Awg: return QScopedPointer(new AwgConfigurator(m_settings, m_serverController)); - case Proto::Ikev2: return QScopedPointer(new Ikev2Configurator(m_settings, m_serverController)); - case Proto::Xray: return QScopedPointer(new XrayConfigurator(m_settings, m_serverController)); - case Proto::SSXray: return QScopedPointer(new XrayConfigurator(m_settings, m_serverController)); - default: return QScopedPointer(); - } -} - -ErrorCode VpnConfigurationsController::createProtocolConfigForContainer(const ServerCredentials &credentials, - const DockerContainer container, QJsonObject &containerConfig) -{ - ErrorCode errorCode = ErrorCode::NoError; - - if (ContainerProps::containerService(container) == ServiceType::Other) { - return errorCode; - } - - for (Proto protocol : ContainerProps::protocolsForContainer(container)) { - QJsonObject protocolConfig = containerConfig.value(ProtocolProps::protoToString(protocol)).toObject(); - - auto configurator = createConfigurator(protocol); - QString protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - protocolConfig.insert(config_key::last_config, protocolConfigString); - containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig); - } - - return errorCode; -} - -ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isApiConfig, const QPair &dns, - const ServerCredentials &credentials, const DockerContainer container, - const QJsonObject &containerConfig, const Proto protocol, - QString &protocolConfigString) -{ - ErrorCode errorCode = ErrorCode::NoError; - - if (ContainerProps::containerService(container) == ServiceType::Other) { - return errorCode; - } - - auto configurator = createConfigurator(protocol); - - protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - protocolConfigString = configurator->processConfigWithExportSettings(dns, isApiConfig, protocolConfigString); - - return errorCode; -} - -QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair &dns, const QJsonObject &serverConfig, - const QJsonObject &containerConfig, const DockerContainer container) -{ - QJsonObject vpnConfiguration {}; - - if (ContainerProps::containerService(container) == ServiceType::Other) { - return vpnConfiguration; - } - - bool isApiConfig = serverConfig.value(config_key::configVersion).toInt(); - - for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) { - if (isApiConfig && container == DockerContainer::Cloak && proto == ProtocolEnumNS::Proto::ShadowSocks) { - continue; - } - - QString protocolConfigString = - containerConfig.value(ProtocolProps::protoToString(proto)).toObject().value(config_key::last_config).toString(); - - auto configurator = createConfigurator(proto); - protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString); - - QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); - if (ContainerProps::isAwgContainer(container) || container == DockerContainer::WireGuard) { - // add mtu for old configs - if (vpnConfigData[config_key::mtu].toString().isEmpty()) { - vpnConfigData[config_key::mtu] = - ContainerProps::isAwgContainer(container) ? protocols::awg::defaultMtu : - protocols::wireguard::defaultMtu; - } - } - - vpnConfiguration.insert(ProtocolProps::key_proto_config_data(proto), vpnConfigData); - } - - Proto proto = ContainerProps::defaultProtocol(container); - vpnConfiguration[config_key::vpnproto] = ProtocolProps::protoToString(proto); - - vpnConfiguration[config_key::dns1] = dns.first; - vpnConfiguration[config_key::dns2] = dns.second; - - vpnConfiguration[config_key::hostName] = serverConfig.value(config_key::hostName).toString(); - vpnConfiguration[config_key::description] = serverConfig.value(config_key::description).toString(); - - vpnConfiguration[config_key::configVersion] = serverConfig.value(config_key::configVersion).toInt(); - // TODO: try to get hostName, port, description for 3rd party configs - // vpnConfiguration[config_key::port] = ...; - - return vpnConfiguration; -} - -void VpnConfigurationsController::updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, - const QString &stdOut) -{ - Proto mainProto = ContainerProps::defaultProtocol(container); - - if (container == DockerContainer::TorWebSite) { - QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); - - qDebug() << "amnezia-tor onions" << stdOut; - - QString onion = stdOut; - onion.replace("\n", ""); - protocol.insert(config_key::site, onion); - - containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol); - } -} diff --git a/client/core/controllers/vpnConfigurationController.h b/client/core/controllers/vpnConfigurationController.h deleted file mode 100644 index 6d0d43b05..000000000 --- a/client/core/controllers/vpnConfigurationController.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef VPNCONFIGIRATIONSCONTROLLER_H -#define VPNCONFIGIRATIONSCONTROLLER_H - -#include - -#include "configurators/configurator_base.h" -#include "containers/containers_defs.h" -#include "core/defs.h" -#include "settings.h" - -class VpnConfigurationsController : public QObject -{ - Q_OBJECT -public: - explicit VpnConfigurationsController(const std::shared_ptr &settings, QSharedPointer serverController, - QObject *parent = nullptr); - -public slots: - ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container, - QJsonObject &containerConfig); - ErrorCode createProtocolConfigString(const bool isApiConfig, const QPair &dns, const ServerCredentials &credentials, - const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol, - QString &protocolConfigString); - QJsonObject createVpnConfiguration(const QPair &dns, const QJsonObject &serverConfig, - const QJsonObject &containerConfig, const DockerContainer container); - - static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut); -signals: - -private: - QScopedPointer createConfigurator(const Proto protocol); - - std::shared_ptr m_settings; - QSharedPointer m_serverController; -}; - -#endif // VPNCONFIGIRATIONSCONTROLLER_H diff --git a/client/core/installers/awgInstaller.cpp b/client/core/installers/awgInstaller.cpp new file mode 100644 index 000000000..c05896e6a --- /dev/null +++ b/client/core/installers/awgInstaller.cpp @@ -0,0 +1,200 @@ +#include "awgInstaller.h" + +#include +#include +#include +#include +#include + +#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 "core/models/protocols/awgProtocolConfig.h" + +using namespace amnezia; +using namespace ProtocolUtils; + +AwgInstaller::AwgInstaller(QObject *parent) + : InstallerBase(parent) +{ +} + +ContainerConfig AwgInstaller::generateConfig(DockerContainer container, int port, TransportProto transportProto) +{ + ContainerConfig config = createBaseConfig(container, port, transportProto); + + bool isAwg2 = (container == DockerContainer::Awg2); + + if (auto* awgConfig = config.getAwgProtocolConfig()) { + generateAwgParameters(awgConfig->serverConfig, isAwg2); + + if (isAwg2) { + awgConfig->serverConfig.protocolVersion = "2"; + } + } + + return config; +} + +void AwgInstaller::generateAwgParameters(AwgServerConfig &serverConfig, bool isAwg2) +{ + QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7)); + QString junkPacketMinSize = QString::number(10); + QString junkPacketMaxSize = QString::number(50); + + int s1 = QRandomGenerator::global()->bounded(15, 150); + int s2 = QRandomGenerator::global()->bounded(15, 150); + int s3 = QRandomGenerator::global()->bounded(0, 64); + int s4 = QRandomGenerator::global()->bounded(0, 20); + + // Ensure all values are unique and don't create equal packet sizes + QSet usedValues; + usedValues.insert(s1); + + while (usedValues.contains(s2) || s1 + amnezia::AwgConstant::messageInitiationSize == s2 + amnezia::AwgConstant::messageResponseSize) { + s2 = QRandomGenerator::global()->bounded(15, 150); + } + usedValues.insert(s2); + + while (usedValues.contains(s3) || s1 + amnezia::AwgConstant::messageInitiationSize == s3 + amnezia::AwgConstant::messageCookieReplySize + || s2 + amnezia::AwgConstant::messageResponseSize == s3 + amnezia::AwgConstant::messageCookieReplySize) { + s3 = QRandomGenerator::global()->bounded(0, 64); + } + usedValues.insert(s3); + + while (usedValues.contains(s4)) { + s4 = QRandomGenerator::global()->bounded(0, 20); + } + + QString initPacketJunkSize = QString::number(s1); + QString responsePacketJunkSize = QString::number(s2); + QString cookieReplyPacketJunkSize = QString::number(s3); + QString transportPacketJunkSize = QString::number(s4); + + QString initPacketMagicHeader; + QString responsePacketMagicHeader; + QString underloadPacketMagicHeader; + QString transportPacketMagicHeader; + + if (isAwg2) { + // AWG 2.0: use range format for magic headers + QVector> headersValue; + int min = 5; + auto max = (std::numeric_limits::max)(); + while (headersValue.size() != 4) { + auto first = QRandomGenerator::global()->bounded(min, max); + auto second = QRandomGenerator::global()->bounded(first, max); + min = second; + headersValue.push_back(QPair(QString::number(first), QString::number(second))); + } + + initPacketMagicHeader = headersValue.at(0).first + "-" + headersValue.at(0).second; + responsePacketMagicHeader = headersValue.at(1).first + "-" + headersValue.at(1).second; + underloadPacketMagicHeader = headersValue.at(2).first + "-" + headersValue.at(2).second; + transportPacketMagicHeader = headersValue.at(3).first + "-" + headersValue.at(3).second; + } else { + // AWG legacy: use single values for magic headers + QSet headersValue; + while (headersValue.size() != 4) { + auto max = (std::numeric_limits::max)(); + headersValue.insert(QString::number(QRandomGenerator::global()->bounded(5, max))); + } + + auto headersValueList = headersValue.values(); + initPacketMagicHeader = headersValueList.at(0); + responsePacketMagicHeader = headersValueList.at(1); + underloadPacketMagicHeader = headersValueList.at(2); + transportPacketMagicHeader = headersValueList.at(3); + } + + serverConfig.junkPacketCount = junkPacketCount; + serverConfig.junkPacketMinSize = junkPacketMinSize; + serverConfig.junkPacketMaxSize = junkPacketMaxSize; + serverConfig.initPacketJunkSize = initPacketJunkSize; + serverConfig.responsePacketJunkSize = responsePacketJunkSize; + serverConfig.initPacketMagicHeader = initPacketMagicHeader; + serverConfig.responsePacketMagicHeader = responsePacketMagicHeader; + serverConfig.underloadPacketMagicHeader = underloadPacketMagicHeader; + serverConfig.transportPacketMagicHeader = transportPacketMagicHeader; + + serverConfig.cookieReplyPacketJunkSize = cookieReplyPacketJunkSize; + serverConfig.transportPacketJunkSize = transportPacketJunkSize; + + serverConfig.specialJunk1 = protocols::awg::defaultSpecialJunk1; + serverConfig.specialJunk2 = protocols::awg::defaultSpecialJunk2; + serverConfig.specialJunk3 = protocols::awg::defaultSpecialJunk3; + serverConfig.specialJunk4 = protocols::awg::defaultSpecialJunk4; + serverConfig.specialJunk5 = protocols::awg::defaultSpecialJunk5; +} + +ErrorCode AwgInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, ContainerConfig &config) +{ + ErrorCode errorCode = ErrorCode::NoError; + + // Use appropriate config path based on container type + QString configPath = protocols::awg::serverConfigPath; + if (container == DockerContainer::Awg) { + configPath = protocols::awg::serverLegacyConfigPath; + } + + QString serverConfig = sshSession->getTextFileFromContainer(container, credentials, configPath, errorCode); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + QMap serverConfigMap; + auto serverConfigLines = serverConfig.split("\n"); + for (auto &line : serverConfigLines) { + auto trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed()); + } + } + } + + if (auto* awgConfig = config.getAwgProtocolConfig()) { + QString addressValue = serverConfigMap.value("Address"); + QStringList addressParts = addressValue.split("/"); + awgConfig->serverConfig.subnetAddress = addressParts.value(0); + if (addressParts.size() > 1) { + awgConfig->serverConfig.subnetCidr = addressParts.value(1); + } + awgConfig->serverConfig.junkPacketCount = serverConfigMap.value(configKey::junkPacketCount); + awgConfig->serverConfig.junkPacketMinSize = serverConfigMap.value(configKey::junkPacketMinSize); + awgConfig->serverConfig.junkPacketMaxSize = serverConfigMap.value(configKey::junkPacketMaxSize); + awgConfig->serverConfig.initPacketJunkSize = serverConfigMap.value(configKey::initPacketJunkSize); + awgConfig->serverConfig.responsePacketJunkSize = serverConfigMap.value(configKey::responsePacketJunkSize); + awgConfig->serverConfig.initPacketMagicHeader = serverConfigMap.value(configKey::initPacketMagicHeader); + awgConfig->serverConfig.responsePacketMagicHeader = serverConfigMap.value(configKey::responsePacketMagicHeader); + awgConfig->serverConfig.underloadPacketMagicHeader = serverConfigMap.value(configKey::underloadPacketMagicHeader); + awgConfig->serverConfig.transportPacketMagicHeader = serverConfigMap.value(configKey::transportPacketMagicHeader); + + // hack to parse i1-i5 from commented lines in server config + awgConfig->serverConfig.specialJunk1 = serverConfigMap.value(QString("# ") + configKey::specialJunk1); + awgConfig->serverConfig.specialJunk2 = serverConfigMap.value(QString("# ") + configKey::specialJunk2); + awgConfig->serverConfig.specialJunk3 = serverConfigMap.value(QString("# ") + configKey::specialJunk3); + awgConfig->serverConfig.specialJunk4 = serverConfigMap.value(QString("# ") + configKey::specialJunk4); + awgConfig->serverConfig.specialJunk5 = serverConfigMap.value(QString("# ") + configKey::specialJunk5); + + // AWG 2.0 specific fields + if (container == DockerContainer::Awg2) { + awgConfig->serverConfig.protocolVersion = "2"; + awgConfig->serverConfig.cookieReplyPacketJunkSize = serverConfigMap.value(configKey::cookieReplyPacketJunkSize); + awgConfig->serverConfig.transportPacketJunkSize = serverConfigMap.value(configKey::transportPacketJunkSize); + } + } + + return ErrorCode::NoError; +} + diff --git a/client/core/installers/awgInstaller.h b/client/core/installers/awgInstaller.h new file mode 100644 index 000000000..4ff03ca5d --- /dev/null +++ b/client/core/installers/awgInstaller.h @@ -0,0 +1,21 @@ +#ifndef AWGINSTALLER_H +#define AWGINSTALLER_H + +#include "installerBase.h" + +class AwgInstaller : public InstallerBase +{ + Q_OBJECT +public: + explicit AwgInstaller(QObject *parent = nullptr); + + amnezia::ContainerConfig generateConfig(amnezia::DockerContainer container, int port, amnezia::TransportProto transportProto) override; + amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials, + SshSession* serverController, amnezia::ContainerConfig &config) override; + +private: + void generateAwgParameters(amnezia::AwgServerConfig &serverConfig, bool isAwg2 = false); +}; + +#endif // AWGINSTALLER_H + diff --git a/client/core/installers/installerBase.cpp b/client/core/installers/installerBase.cpp new file mode 100644 index 000000000..5bf9caf05 --- /dev/null +++ b/client/core/installers/installerBase.cpp @@ -0,0 +1,116 @@ +#include "installerBase.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/models/protocolConfig.h" +#include "core/models/protocols/awgProtocolConfig.h" +#include "core/models/protocols/wireGuardProtocolConfig.h" +#include "core/models/protocols/openVpnProtocolConfig.h" +#include "core/models/protocols/xrayProtocolConfig.h" +#include "core/models/protocols/sftpProtocolConfig.h" +#include "core/models/protocols/socks5ProxyProtocolConfig.h" +#include "core/models/protocols/ikev2ProtocolConfig.h" +#include "core/models/protocols/torProtocolConfig.h" + +using namespace amnezia; +using namespace ProtocolUtils; + +InstallerBase::InstallerBase(QObject *parent) + : QObject(parent) +{ +} + +ContainerConfig InstallerBase::generateConfig(DockerContainer container, int port, TransportProto transportProto) +{ + return createBaseConfig(container, port, transportProto); +} + +ErrorCode InstallerBase::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, ContainerConfig &config) +{ + Q_UNUSED(container); + Q_UNUSED(credentials); + Q_UNUSED(sshSession); + Q_UNUSED(config); + return ErrorCode::NoError; +} + +ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int port, TransportProto transportProto) +{ + ContainerConfig config; + config.container = container; + + Proto protocol = ContainerUtils::defaultProtocol(container); + QString portStr = QString::number(port); + QString transportProtoStr = ProtocolUtils::transportProtoToString(transportProto, protocol); + + switch (protocol) { + case Proto::Awg: { + AwgProtocolConfig awgConfig; + awgConfig.serverConfig.port = portStr; + awgConfig.serverConfig.transportProto = transportProtoStr; + config.protocolConfig = awgConfig; + break; + } + case Proto::WireGuard: { + WireGuardProtocolConfig wgConfig; + wgConfig.serverConfig.port = portStr; + wgConfig.serverConfig.transportProto = transportProtoStr; + config.protocolConfig = wgConfig; + break; + } + case Proto::OpenVpn: { + OpenVpnProtocolConfig ovpnConfig; + ovpnConfig.serverConfig.port = portStr; + ovpnConfig.serverConfig.transportProto = transportProtoStr; + config.protocolConfig = ovpnConfig; + break; + } + case Proto::Xray: + case Proto::SSXray: { + XrayProtocolConfig xrayConfig; + xrayConfig.serverConfig.port = portStr; + xrayConfig.serverConfig.transportProto = transportProtoStr; + config.protocolConfig = xrayConfig; + break; + } + case Proto::Sftp: { + SftpProtocolConfig sftpConfig; + sftpConfig.port = portStr; + config.protocolConfig = sftpConfig; + break; + } + case Proto::Socks5Proxy: { + Socks5ProxyProtocolConfig socks5Config; + socks5Config.port = portStr; + config.protocolConfig = socks5Config; + break; + } + case Proto::Ikev2: { + Ikev2ProtocolConfig ikev2Config; + config.protocolConfig = ikev2Config; + break; + } + case Proto::TorWebSite: { + TorProtocolConfig torConfig; + config.protocolConfig = torConfig; + break; + } + case Proto::Dns: { + DnsProtocolConfig dnsConfig; + config.protocolConfig = dnsConfig; + break; + } + default: { + break; + } + } + + return config; +} + diff --git a/client/core/installers/installerBase.h b/client/core/installers/installerBase.h new file mode 100644 index 000000000..3defed368 --- /dev/null +++ b/client/core/installers/installerBase.h @@ -0,0 +1,32 @@ +#ifndef INSTALLERBASE_H +#define INSTALLERBASE_H + +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/utils/selfhosted/sshSession.h" +#include "core/models/containerConfig.h" + +class InstallerBase : public QObject +{ + Q_OBJECT +public: + explicit InstallerBase(QObject *parent = nullptr); + + virtual amnezia::ContainerConfig generateConfig(amnezia::DockerContainer container, int port, amnezia::TransportProto transportProto); + + virtual amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials, + SshSession* sshSession, amnezia::ContainerConfig &config); + + amnezia::ContainerConfig createBaseConfig(amnezia::DockerContainer container, int port, amnezia::TransportProto transportProto); + +protected: +}; + +#endif // INSTALLERBASE_H + diff --git a/client/core/installers/openvpnInstaller.cpp b/client/core/installers/openvpnInstaller.cpp new file mode 100644 index 000000000..8a5f59b54 --- /dev/null +++ b/client/core/installers/openvpnInstaller.cpp @@ -0,0 +1,73 @@ +#include "openvpnInstaller.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" + +using namespace amnezia; +using namespace ProtocolUtils; + +OpenVpnInstaller::OpenVpnInstaller(QObject *parent) + : InstallerBase(parent) +{ +} + +ErrorCode OpenVpnInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, ContainerConfig &config) +{ + ErrorCode errorCode = ErrorCode::NoError; + + QString serverConfig = sshSession->getTextFileFromContainer(container, credentials, + protocols::openvpn::serverConfigPath, errorCode); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + QMap serverConfigMap; + auto serverConfigLines = serverConfig.split("\n"); + for (auto &line : serverConfigLines) { + auto trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) { + continue; + } else { + QStringList parts = trimmedLine.split(" "); + if (parts.count() >= 2) { + QString key = parts[0]; + QString value = parts.mid(1).join(" "); + serverConfigMap.insert(key, value); + } + } + } + + if (auto* ovpnConfig = config.getOpenVpnProtocolConfig()) { + QString serverValue = serverConfigMap.value("server"); + + if (!serverValue.isEmpty()) { + QStringList serverParts = serverValue.split(" "); + if (serverParts.count() >= 1) { + ovpnConfig->serverConfig.subnetAddress = serverParts[0]; + } + } + + ovpnConfig->serverConfig.ncpDisable = serverConfig.contains("ncp-disable"); + ovpnConfig->serverConfig.tlsAuth = serverConfig.contains("tls-auth"); + + QString cipher = serverConfigMap.value("cipher"); + if (!cipher.isEmpty()) { + ovpnConfig->serverConfig.cipher = cipher; + } + + QString hash = serverConfigMap.value("auth"); + if (!hash.isEmpty()) { + ovpnConfig->serverConfig.hash = hash; + } + } + + return ErrorCode::NoError; +} + diff --git a/client/core/installers/openvpnInstaller.h b/client/core/installers/openvpnInstaller.h new file mode 100644 index 000000000..32e6f4b40 --- /dev/null +++ b/client/core/installers/openvpnInstaller.h @@ -0,0 +1,17 @@ +#ifndef OPENVPNINSTALLER_H +#define OPENVPNINSTALLER_H + +#include "installerBase.h" + +class OpenVpnInstaller : public InstallerBase +{ + Q_OBJECT +public: + explicit OpenVpnInstaller(QObject *parent = nullptr); + + amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials, + SshSession* serverController, amnezia::ContainerConfig &config) override; +}; + +#endif // OPENVPNINSTALLER_H + diff --git a/client/core/installers/sftpInstaller.cpp b/client/core/installers/sftpInstaller.cpp new file mode 100644 index 000000000..7797a2b97 --- /dev/null +++ b/client/core/installers/sftpInstaller.cpp @@ -0,0 +1,69 @@ +#include "sftpInstaller.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 "core/models/protocols/sftpProtocolConfig.h" + +using namespace amnezia; +using namespace ProtocolUtils; + +SftpInstaller::SftpInstaller(QObject *parent) + : InstallerBase(parent) +{ +} + +ContainerConfig SftpInstaller::generateConfig(DockerContainer container, int port, TransportProto transportProto) +{ + ContainerConfig config = createBaseConfig(container, port, transportProto); + + if (auto* sftpConfig = config.getSftpProtocolConfig()) { + sftpConfig->userName = protocols::sftp::defaultUserName; + sftpConfig->password = Utils::getRandomString(16); + } + + return config; +} + +ErrorCode SftpInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, ContainerConfig &config) +{ + ErrorCode errorCode = ErrorCode::NoError; + + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + QString containerName = ContainerUtils::containerToString(container); + QString script = QString("sudo docker inspect --format '{{.Config.Cmd}}' %1").arg(containerName); + + errorCode = sshSession->runScript(credentials, script, cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + auto sftpInfo = stdOut.split(":"); + if (sftpInfo.size() < 2) { + return ErrorCode::ServerContainerMissingError; + } + + if (auto* sftpConfig = config.getSftpProtocolConfig()) { + sftpConfig->userName = sftpInfo.at(0).trimmed(); + sftpConfig->password = sftpInfo.at(1).trimmed(); + } + + return ErrorCode::NoError; +} + diff --git a/client/core/installers/sftpInstaller.h b/client/core/installers/sftpInstaller.h new file mode 100644 index 000000000..9c3882563 --- /dev/null +++ b/client/core/installers/sftpInstaller.h @@ -0,0 +1,18 @@ +#ifndef SFTPINSTALLER_H +#define SFTPINSTALLER_H + +#include "installerBase.h" + +class SftpInstaller : public InstallerBase +{ + Q_OBJECT +public: + explicit SftpInstaller(QObject *parent = nullptr); + + amnezia::ContainerConfig generateConfig(amnezia::DockerContainer container, int port, amnezia::TransportProto transportProto) override; + amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials, + SshSession* serverController, amnezia::ContainerConfig &config) override; +}; + +#endif // SFTPINSTALLER_H + diff --git a/client/core/installers/socks5Installer.cpp b/client/core/installers/socks5Installer.cpp new file mode 100644 index 000000000..3a58d36ee --- /dev/null +++ b/client/core/installers/socks5Installer.cpp @@ -0,0 +1,42 @@ +#include "socks5Installer.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" + +using namespace amnezia; +using namespace ProtocolUtils; + +Socks5Installer::Socks5Installer(QObject *parent) + : InstallerBase(parent) +{ +} + +ContainerConfig Socks5Installer::generateConfig(DockerContainer container, int port, TransportProto transportProto) +{ + ContainerConfig config = createBaseConfig(container, port, transportProto); + + if (auto* socks5Config = config.getSocks5ProxyProtocolConfig()) { + socks5Config->userName = protocols::socks5Proxy::defaultUserName; + socks5Config->password = Utils::getRandomString(16); + } + + return config; +} + +ErrorCode Socks5Installer::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, ContainerConfig &config) +{ + Q_UNUSED(container); + Q_UNUSED(credentials); + Q_UNUSED(sshSession); + Q_UNUSED(config); + return ErrorCode::NoError; +} + diff --git a/client/core/installers/socks5Installer.h b/client/core/installers/socks5Installer.h new file mode 100644 index 000000000..b35703497 --- /dev/null +++ b/client/core/installers/socks5Installer.h @@ -0,0 +1,18 @@ +#ifndef SOCKS5INSTALLER_H +#define SOCKS5INSTALLER_H + +#include "installerBase.h" + +class Socks5Installer : public InstallerBase +{ + Q_OBJECT +public: + explicit Socks5Installer(QObject *parent = nullptr); + + amnezia::ContainerConfig generateConfig(amnezia::DockerContainer container, int port, amnezia::TransportProto transportProto) override; + amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials, + SshSession* serverController, amnezia::ContainerConfig &config) override; +}; + +#endif // SOCKS5INSTALLER_H + diff --git a/client/core/installers/torInstaller.cpp b/client/core/installers/torInstaller.cpp new file mode 100644 index 000000000..f1455823e --- /dev/null +++ b/client/core/installers/torInstaller.cpp @@ -0,0 +1,57 @@ +#include "torInstaller.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/models/protocols/torProtocolConfig.h" + +using namespace amnezia; +using namespace ProtocolUtils; + +TorInstaller::TorInstaller(QObject *parent) + : InstallerBase(parent) +{ +} + +ErrorCode TorInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, ContainerConfig &config) +{ + ErrorCode errorCode = ErrorCode::NoError; + + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + QString containerName = ContainerUtils::containerToString(container); + QString script = QString("sudo docker exec -i %1 sh -c 'cat /var/lib/tor/hidden_service/hostname'").arg(containerName); + + errorCode = sshSession->runScript(credentials, script, cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + if (stdOut.isEmpty()) { + return ErrorCode::ServerContainerMissingError; + } + + QString onion = stdOut; + onion.replace("\n", ""); + + if (auto* torConfig = config.getTorProtocolConfig()) { + torConfig->serverConfig.site = onion; + } + + return ErrorCode::NoError; +} + diff --git a/client/core/installers/torInstaller.h b/client/core/installers/torInstaller.h new file mode 100644 index 000000000..07ea4ce22 --- /dev/null +++ b/client/core/installers/torInstaller.h @@ -0,0 +1,17 @@ +#ifndef TORINSTALLER_H +#define TORINSTALLER_H + +#include "installerBase.h" + +class TorInstaller : public InstallerBase +{ + Q_OBJECT +public: + explicit TorInstaller(QObject *parent = nullptr); + + amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials, + SshSession* serverController, amnezia::ContainerConfig &config) override; +}; + +#endif // TORINSTALLER_H + diff --git a/client/core/installers/wireguardInstaller.cpp b/client/core/installers/wireguardInstaller.cpp new file mode 100644 index 000000000..0bdb52066 --- /dev/null +++ b/client/core/installers/wireguardInstaller.cpp @@ -0,0 +1,51 @@ +#include "wireguardInstaller.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" + +using namespace amnezia; +using namespace ProtocolUtils; + +WireguardInstaller::WireguardInstaller(QObject *parent) + : InstallerBase(parent) +{ +} + +ErrorCode WireguardInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, ContainerConfig &config) +{ + ErrorCode errorCode = ErrorCode::NoError; + + QString serverConfig = sshSession->getTextFileFromContainer(container, credentials, + protocols::wireguard::serverConfigPath, errorCode); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + QMap serverConfigMap; + auto serverConfigLines = serverConfig.split("\n"); + for (auto &line : serverConfigLines) { + auto trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed()); + } + } + } + + if (auto* wgConfig = config.getWireGuardProtocolConfig()) { + wgConfig->serverConfig.subnetAddress = serverConfigMap.value("Address").remove("/24"); + } + + return ErrorCode::NoError; +} + diff --git a/client/core/installers/wireguardInstaller.h b/client/core/installers/wireguardInstaller.h new file mode 100644 index 000000000..59f2afab8 --- /dev/null +++ b/client/core/installers/wireguardInstaller.h @@ -0,0 +1,17 @@ +#ifndef WIREGUARDINSTALLER_H +#define WIREGUARDINSTALLER_H + +#include "installerBase.h" + +class WireguardInstaller : public InstallerBase +{ + Q_OBJECT +public: + explicit WireguardInstaller(QObject *parent = nullptr); + + amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials, + SshSession* serverController, amnezia::ContainerConfig &config) override; +}; + +#endif // WIREGUARDINSTALLER_H + diff --git a/client/core/installers/xrayInstaller.cpp b/client/core/installers/xrayInstaller.cpp new file mode 100644 index 000000000..12a4b9833 --- /dev/null +++ b/client/core/installers/xrayInstaller.cpp @@ -0,0 +1,80 @@ +#include "xrayInstaller.h" + +#include +#include + +#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/models/protocols/xrayProtocolConfig.h" +#include "logger.h" + +namespace { + Logger logger("XrayInstaller"); +} + +using namespace amnezia; +using namespace ProtocolUtils; + +XrayInstaller::XrayInstaller(QObject *parent) + : InstallerBase(parent) +{ +} + +ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials, + SshSession* sshSession, ContainerConfig &config) +{ + ErrorCode errorCode = ErrorCode::NoError; + + QString currentConfig = sshSession->getTextFileFromContainer( + container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode); + + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8()); + if (doc.isNull() || !doc.isObject()) { + logger.error() << "Failed to parse server config JSON"; + return ErrorCode::InternalError; + } + QJsonObject serverConfig = doc.object(); + + if (!serverConfig.contains(protocols::xray::inbounds)) { + logger.error() << "Server config missing 'inbounds' field"; + return ErrorCode::InternalError; + } + + QJsonArray inbounds = serverConfig[protocols::xray::inbounds].toArray(); + if (inbounds.isEmpty()) { + logger.error() << "Server config has empty 'inbounds' array"; + return ErrorCode::InternalError; + } + + QJsonObject inbound = inbounds[0].toObject(); + if (!inbound.contains(protocols::xray::streamSettings)) { + logger.error() << "Inbound missing 'streamSettings' field"; + return ErrorCode::InternalError; + } + + QJsonObject streamSettings = inbound[protocols::xray::streamSettings].toObject(); + QJsonObject realitySettings = streamSettings[protocols::xray::realitySettings].toObject(); + if (!realitySettings.contains(protocols::xray::serverNames)) { + logger.error() << "Settings missing 'serverNames' field"; + return ErrorCode::InternalError; + } + + QString siteName = realitySettings[protocols::xray::serverNames][0].toString(); + + if (auto* xrayConfig = config.getXrayProtocolConfig()) { + xrayConfig->serverConfig.site = siteName; + } + + return ErrorCode::NoError; +} + diff --git a/client/core/installers/xrayInstaller.h b/client/core/installers/xrayInstaller.h new file mode 100644 index 000000000..76915102b --- /dev/null +++ b/client/core/installers/xrayInstaller.h @@ -0,0 +1,17 @@ +#ifndef XRAYINSTALLER_H +#define XRAYINSTALLER_H + +#include "installerBase.h" + +class XrayInstaller : public InstallerBase +{ + Q_OBJECT +public: + explicit XrayInstaller(QObject *parent = nullptr); + + amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials, + SshSession* serverController, amnezia::ContainerConfig &config) override; +}; + +#endif // XRAYINSTALLER_H + diff --git a/client/core/models/api/apiConfig.cpp b/client/core/models/api/apiConfig.cpp new file mode 100644 index 000000000..fa9e9b61f --- /dev/null +++ b/client/core/models/api/apiConfig.cpp @@ -0,0 +1,223 @@ +#include "apiConfig.h" + +#include +#include + +#include "core/utils/api/apiUtils.h" +#include "core/utils/constants/apiKeys.h" + +namespace amnezia +{ + +QJsonObject ApiConfig::Subscription::toJson() const +{ + QJsonObject obj; + if (!endDate.isEmpty()) { + obj[apiDefs::key::endDate] = endDate; + } + return obj; +} + +ApiConfig::Subscription ApiConfig::Subscription::fromJson(const QJsonObject& json) +{ + Subscription sub; + sub.endDate = json.value(apiDefs::key::endDate).toString(); + return sub; +} + +QJsonObject ApiConfig::ServiceInfo::toJson() const +{ + QJsonObject obj; + obj[apiDefs::key::isAdVisible] = isAdVisible; + obj[apiDefs::key::isRenewalAvailable] = isRenewalAvailable; + if (!adHeader.isEmpty()) { + obj[apiDefs::key::adHeader] = adHeader; + } + if (!adDescription.isEmpty()) { + obj[apiDefs::key::adDescription] = adDescription; + } + if (!adEndpoint.isEmpty()) { + obj[apiDefs::key::adEndpoint] = adEndpoint; + } + return obj; +} + +ApiConfig::ServiceInfo ApiConfig::ServiceInfo::fromJson(const QJsonObject& json) +{ + ServiceInfo info; + info.isAdVisible = json.value(apiDefs::key::isAdVisible).toBool(false); + info.isRenewalAvailable = json.value(apiDefs::key::isRenewalAvailable).toBool(false); + info.adHeader = json.value(apiDefs::key::adHeader).toString(); + info.adDescription = json.value(apiDefs::key::adDescription).toString(); + info.adEndpoint = json.value(apiDefs::key::adEndpoint).toString(); + return info; +} + +QJsonObject ApiConfig::PublicKeyInfo::toJson() const +{ + QJsonObject obj; + if (!expiresAt.isEmpty()) { + obj[apiDefs::key::expiresAt] = expiresAt; + } + return obj; +} + +ApiConfig::PublicKeyInfo ApiConfig::PublicKeyInfo::fromJson(const QJsonObject& json) +{ + PublicKeyInfo info; + info.expiresAt = json.value(apiDefs::key::expiresAt).toString(); + return info; +} + +bool ApiConfig::isPremium() const +{ + return serviceType == "amnezia-premium"; +} + +bool ApiConfig::isFree() const +{ + return serviceType == "amnezia-free"; +} + +bool ApiConfig::isExternalPremium() const +{ + return serviceType == "external-premium"; +} + +bool ApiConfig::isSubscriptionExpired() const +{ + if (subscription.endDate.isEmpty()) { + return false; + } + + QDateTime endDate = QDateTime::fromString(subscription.endDate, Qt::ISODateWithMs); + if (!endDate.isValid()) { + endDate = QDateTime::fromString(subscription.endDate, Qt::ISODate); + } + + if (!endDate.isValid()) { + return false; + } + + return endDate < QDateTime::currentDateTimeUtc(); +} + +QJsonObject ApiConfig::toJson() const +{ + QJsonObject obj; + + if (!serviceType.isEmpty()) { + obj[apiDefs::key::serviceType] = serviceType; + } + if (!serviceProtocol.isEmpty()) { + obj[QLatin1String("service_protocol")] = serviceProtocol; + } + if (!userCountryCode.isEmpty()) { + obj[QLatin1String("user_country_code")] = userCountryCode; + } + if (!serverCountryCode.isEmpty()) { + obj[apiDefs::key::serverCountryCode] = serverCountryCode; + } + if (!serverCountryName.isEmpty()) { + obj[apiDefs::key::serverCountryName] = serverCountryName; + } + if (!vpnKey.isEmpty()) { + obj[apiDefs::key::vpnKey] = vpnKey; + } + + QJsonObject subscriptionObj = subscription.toJson(); + if (!subscriptionObj.isEmpty()) { + obj[apiDefs::key::subscription] = subscriptionObj; + } + + if (activeDeviceCount > 0) { + obj[apiDefs::key::activeDeviceCount] = activeDeviceCount; + } + if (maxDeviceCount > 0) { + obj[apiDefs::key::maxDeviceCount] = maxDeviceCount; + } + if (issuedConfigs > 0) { + obj[apiDefs::key::issuedConfigs] = issuedConfigs; + } + + if (!availableCountries.isEmpty()) { + obj[apiDefs::key::availableCountries] = availableCountries; + } + + if (!supportedProtocols.isEmpty()) { + obj[apiDefs::key::supportedProtocols] = supportedProtocols; + } + + QJsonObject serviceInfoObj = serviceInfo.toJson(); + if (!serviceInfoObj.isEmpty()) { + obj[apiDefs::key::serviceInfo] = serviceInfoObj; + } + + QJsonObject publicKeyObj = publicKey.toJson(); + if (!publicKeyObj.isEmpty()) { + obj[apiDefs::key::publicKey] = publicKeyObj; + } + + if (!stackType.isEmpty()) { + obj[apiDefs::key::stackType] = stackType; + } + if (!cliVersion.isEmpty()) { + obj[apiDefs::key::cliVersion] = cliVersion; + } + if (isTestPurchase) { + obj[apiDefs::key::isTestPurchase] = isTestPurchase; + } + if (isInAppPurchase) { + obj[apiDefs::key::isInAppPurchase] = isInAppPurchase; + } + if (subscriptionExpiredByServer) { + obj[apiDefs::key::subscriptionExpiredByServer] = subscriptionExpiredByServer; + } + + return obj; +} + +ApiConfig ApiConfig::fromJson(const QJsonObject& json) +{ + ApiConfig config; + + config.serviceType = json.value(apiDefs::key::serviceType).toString(); + config.serviceProtocol = json.value(QLatin1String("service_protocol")).toString(); + config.userCountryCode = json.value(QLatin1String("user_country_code")).toString(); + config.serverCountryCode = json.value(apiDefs::key::serverCountryCode).toString(); + config.serverCountryName = json.value(apiDefs::key::serverCountryName).toString(); + config.vpnKey = json.value(apiDefs::key::vpnKey).toString(); + + QJsonObject subscriptionObj = json.value(apiDefs::key::subscription).toObject(); + if (!subscriptionObj.isEmpty()) { + config.subscription = Subscription::fromJson(subscriptionObj); + } + + config.activeDeviceCount = json.value(apiDefs::key::activeDeviceCount).toInt(0); + config.maxDeviceCount = json.value(apiDefs::key::maxDeviceCount).toInt(0); + config.issuedConfigs = json.value(apiDefs::key::issuedConfigs).toInt(0); + + config.availableCountries = json.value(apiDefs::key::availableCountries).toArray(); + config.supportedProtocols = json.value(apiDefs::key::supportedProtocols).toArray(); + + QJsonObject serviceInfoObj = json.value(apiDefs::key::serviceInfo).toObject(); + if (!serviceInfoObj.isEmpty()) { + config.serviceInfo = ServiceInfo::fromJson(serviceInfoObj); + } + + QJsonObject publicKeyObj = json.value(apiDefs::key::publicKey).toObject(); + if (!publicKeyObj.isEmpty()) { + config.publicKey = PublicKeyInfo::fromJson(publicKeyObj); + } + + config.stackType = json.value(apiDefs::key::stackType).toString(); + config.cliVersion = json.value(apiDefs::key::cliVersion).toString(); + config.isTestPurchase = json.value(apiDefs::key::isTestPurchase).toBool(false); + config.isInAppPurchase = json.value(apiDefs::key::isInAppPurchase).toBool(false); + config.subscriptionExpiredByServer = json.value(apiDefs::key::subscriptionExpiredByServer).toBool(false); + + return config; +} + +} // namespace amnezia + diff --git a/client/core/models/api/apiConfig.h b/client/core/models/api/apiConfig.h new file mode 100644 index 000000000..a2cff58d0 --- /dev/null +++ b/client/core/models/api/apiConfig.h @@ -0,0 +1,77 @@ +#ifndef APICONFIG_H +#define APICONFIG_H + +#include +#include +#include +#include + +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" + +namespace amnezia +{ + +struct ApiConfig +{ + QString serviceType; + QString serviceProtocol; + QString userCountryCode; + QString serverCountryCode; + QString serverCountryName; + QString vpnKey; + + struct Subscription { + QString endDate; + + QJsonObject toJson() const; + static Subscription fromJson(const QJsonObject& json); + }; + Subscription subscription; + + int activeDeviceCount; + int maxDeviceCount; + int issuedConfigs; + QJsonArray availableCountries; + QJsonArray supportedProtocols; + + struct ServiceInfo { + bool isAdVisible = false; + bool isRenewalAvailable = false; + QString adHeader; + QString adDescription; + QString adEndpoint; + + QJsonObject toJson() const; + static ServiceInfo fromJson(const QJsonObject& json); + }; + ServiceInfo serviceInfo; + + struct PublicKeyInfo { + QString expiresAt; + + QJsonObject toJson() const; + static PublicKeyInfo fromJson(const QJsonObject& json); + }; + PublicKeyInfo publicKey; + + QString stackType; + QString cliVersion; + bool isTestPurchase; + bool isInAppPurchase = false; + bool subscriptionExpiredByServer = false; + + bool isPremium() const; + bool isFree() const; + bool isExternalPremium() const; + bool isSubscriptionExpired() const; + + QJsonObject toJson() const; + static ApiConfig fromJson(const QJsonObject& json); +}; + +} // namespace amnezia + +#endif // APICONFIG_H + diff --git a/client/core/models/api/apiV1ServerConfig.cpp b/client/core/models/api/apiV1ServerConfig.cpp new file mode 100644 index 000000000..9e4c0e6d2 --- /dev/null +++ b/client/core/models/api/apiV1ServerConfig.cpp @@ -0,0 +1,140 @@ +#include "apiV1ServerConfig.h" + +#include +#include + +#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/apiKeys.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" +#include "core/utils/api/apiUtils.h" + +namespace amnezia +{ + +using namespace ContainerEnumNS; + +bool ApiV1ServerConfig::isPremium() const +{ + constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT); + return apiEndpoint.contains(premiumV1Endpoint); +} + +bool ApiV1ServerConfig::isFree() const +{ + constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT); + return apiEndpoint.contains(freeV2Endpoint); +} + +QString ApiV1ServerConfig::vpnKey() const +{ + QJsonObject json = toJson(); + return apiUtils::getPremiumV1VpnKey(json); +} + +bool ApiV1ServerConfig::hasContainers() const +{ + return !containers.isEmpty(); +} + +ContainerConfig ApiV1ServerConfig::containerConfig(DockerContainer container) const +{ + if (!containers.contains(container)) { + return ContainerConfig{}; + } + return containers.value(container); +} + +QJsonObject ApiV1ServerConfig::toJson() const +{ + QJsonObject obj; + + if (!name.isEmpty()) { + obj[configKey::name] = name; + } + if (!description.isEmpty()) { + obj[configKey::description] = description; + } + if (!protocol.isEmpty()) { + obj[apiDefs::key::protocol] = protocol; + } + if (!apiEndpoint.isEmpty()) { + obj[apiDefs::key::apiEndpoint] = apiEndpoint; + } + if (!apiKey.isEmpty()) { + obj[apiDefs::key::apiKey] = apiKey; + } + + obj[configKey::configVersion] = configVersion; + + if (!hostName.isEmpty()) { + obj[configKey::hostName] = hostName; + } + + QJsonArray containersArray; + for (auto it = containers.begin(); it != containers.end(); ++it) { + QJsonObject containerObj = it.value().toJson(); + containersArray.append(containerObj); + } + if (!containersArray.isEmpty()) { + obj[configKey::containers] = containersArray; + } + + if (defaultContainer != DockerContainer::None) { + obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer); + } + + if (!dns1.isEmpty()) { + obj[configKey::dns1] = dns1; + } + if (!dns2.isEmpty()) { + obj[configKey::dns2] = dns2; + } + + if (crc > 0) { + obj[configKey::crc] = crc; + } + + return obj; +} + +ApiV1ServerConfig ApiV1ServerConfig::fromJson(const QJsonObject& json) +{ + ApiV1ServerConfig config; + + config.name = json.value(configKey::name).toString(); + config.description = json.value(configKey::description).toString(); + config.protocol = json.value(apiDefs::key::protocol).toString(); + config.apiEndpoint = json.value(apiDefs::key::apiEndpoint).toString(); + config.apiKey = json.value(apiDefs::key::apiKey).toString(); + config.configVersion = json.value(configKey::configVersion).toInt(1); + config.hostName = json.value(configKey::hostName).toString(); + + QJsonArray containersArray = json.value(configKey::containers).toArray(); + for (const QJsonValue& val : containersArray) { + QJsonObject containerObj = val.toObject(); + ContainerConfig containerConfig = ContainerConfig::fromJson(containerObj); + + QString containerStr = containerObj.value(configKey::container).toString(); + DockerContainer container = ContainerUtils::containerFromString(containerStr); + + config.containers.insert(container, containerConfig); + } + + QString defaultContainerStr = json.value(configKey::defaultContainer).toString(); + config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr); + + config.dns1 = json.value(configKey::dns1).toString(); + config.dns2 = json.value(configKey::dns2).toString(); + + config.crc = json.value(configKey::crc).toInt(0); + + return config; +} + +} // namespace amnezia + diff --git a/client/core/models/api/apiV1ServerConfig.h b/client/core/models/api/apiV1ServerConfig.h new file mode 100644 index 000000000..be414cef5 --- /dev/null +++ b/client/core/models/api/apiV1ServerConfig.h @@ -0,0 +1,47 @@ +#ifndef APIV1SERVERCONFIG_H +#define APIV1SERVERCONFIG_H + +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/containerConfig.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" + +namespace amnezia +{ + +using namespace ContainerEnumNS; + +struct ApiV1ServerConfig { + QString description; + QString hostName; + QMap containers; + DockerContainer defaultContainer; + QString dns1; + QString dns2; + + QString name; + QString protocol; + QString apiEndpoint; + QString apiKey; + int crc; + int configVersion; + + bool isPremium() const; + bool isFree() const; + QString vpnKey() const; + bool hasContainers() const; + ContainerConfig containerConfig(DockerContainer container) const; + QJsonObject toJson() const; + static ApiV1ServerConfig fromJson(const QJsonObject& json); +}; + +} // namespace amnezia + +#endif // APIV1SERVERCONFIG_H + diff --git a/client/core/models/api/apiV2ServerConfig.cpp b/client/core/models/api/apiV2ServerConfig.cpp new file mode 100644 index 000000000..08d6323f8 --- /dev/null +++ b/client/core/models/api/apiV2ServerConfig.cpp @@ -0,0 +1,170 @@ +#include "apiV2ServerConfig.h" + +#include +#include + +#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/api/apiUtils.h" +#include "core/models/api/apiConfig.h" +#include "core/models/api/authData.h" + +namespace amnezia +{ + +using namespace ContainerEnumNS; + +QString ApiV2ServerConfig::vpnKey() const +{ + if (!apiConfig.vpnKey.isEmpty()) { + return apiConfig.vpnKey; + } + + QJsonObject json = toJson(); + return apiUtils::getPremiumV2VpnKey(json); +} + +QString ApiV2ServerConfig::serviceType() const +{ + return apiConfig.serviceType; +} + +QString ApiV2ServerConfig::serviceProtocol() const +{ + return apiConfig.serviceProtocol; +} + +bool ApiV2ServerConfig::isPremium() const +{ + return apiConfig.isPremium(); +} + +bool ApiV2ServerConfig::isFree() const +{ + return apiConfig.isFree(); +} + +bool ApiV2ServerConfig::isExternalPremium() const +{ + return apiConfig.isExternalPremium(); +} + +bool ApiV2ServerConfig::hasContainers() const +{ + return !containers.isEmpty(); +} + +ContainerConfig ApiV2ServerConfig::containerConfig(DockerContainer container) const +{ + if (!containers.contains(container)) { + return ContainerConfig{}; + } + return containers.value(container); +} + +QJsonObject ApiV2ServerConfig::toJson() const +{ + QJsonObject obj; + + if (!name.isEmpty()) { + obj[configKey::name] = name; + } + if (nameOverriddenByUser) { + obj[configKey::nameOverriddenByUser] = true; + } + if (!description.isEmpty()) { + obj[configKey::description] = description; + } + + obj[configKey::configVersion] = configVersion; + + if (!hostName.isEmpty()) { + obj[configKey::hostName] = hostName; + } + + QJsonArray containersArray; + for (auto it = containers.begin(); it != containers.end(); ++it) { + QJsonObject containerObj = it.value().toJson(); + containersArray.append(containerObj); + } + if (!containersArray.isEmpty()) { + obj[configKey::containers] = containersArray; + } + + if (defaultContainer != DockerContainer::None) { + obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer); + } + + if (!dns1.isEmpty()) { + obj[configKey::dns1] = dns1; + } + if (!dns2.isEmpty()) { + obj[configKey::dns2] = dns2; + } + + if (crc > 0) { + obj[configKey::crc] = crc; + } + + QJsonObject apiConfigObj = apiConfig.toJson(); + if (!apiConfigObj.isEmpty()) { + obj[apiDefs::key::apiConfig] = apiConfigObj; + } + + QJsonObject authDataObj = authData.toJson(); + if (!authDataObj.isEmpty()) { + obj[QLatin1String("auth_data")] = authDataObj; + } + + return obj; +} + +ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json) +{ + ApiV2ServerConfig config; + + config.name = json.value(configKey::name).toString(); + config.nameOverriddenByUser = json.value(configKey::nameOverriddenByUser).toBool(false); + config.description = json.value(configKey::description).toString(); + config.configVersion = json.value(configKey::configVersion).toInt(2); + config.hostName = json.value(configKey::hostName).toString(); + + QJsonArray containersArray = json.value(configKey::containers).toArray(); + for (const QJsonValue& val : containersArray) { + QJsonObject containerObj = val.toObject(); + ContainerConfig containerConfig = ContainerConfig::fromJson(containerObj); + + QString containerStr = containerObj.value(configKey::container).toString(); + DockerContainer container = ContainerUtils::containerFromString(containerStr); + + config.containers.insert(container, containerConfig); + } + + QString defaultContainerStr = json.value(configKey::defaultContainer).toString(); + config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr); + + config.dns1 = json.value(configKey::dns1).toString(); + config.dns2 = json.value(configKey::dns2).toString(); + + config.crc = json.value(configKey::crc).toInt(0); + + QJsonObject apiConfigObj = json.value(apiDefs::key::apiConfig).toObject(); + if (!apiConfigObj.isEmpty()) { + config.apiConfig = ApiConfig::fromJson(apiConfigObj); + } + + QJsonObject authDataObj = json.value(QLatin1String("auth_data")).toObject(); + if (!authDataObj.isEmpty()) { + config.authData = AuthData::fromJson(authDataObj); + } + + return config; +} + +} // namespace amnezia + diff --git a/client/core/models/api/apiV2ServerConfig.h b/client/core/models/api/apiV2ServerConfig.h new file mode 100644 index 000000000..e3625ae96 --- /dev/null +++ b/client/core/models/api/apiV2ServerConfig.h @@ -0,0 +1,52 @@ +#ifndef APIV2SERVERCONFIG_H +#define APIV2SERVERCONFIG_H + +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/containerConfig.h" +#include "core/models/api/apiConfig.h" +#include "core/models/api/authData.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" + +namespace amnezia +{ + +using namespace ContainerEnumNS; + +struct ApiV2ServerConfig { + QString description; + QString hostName; + QMap containers; + DockerContainer defaultContainer; + QString dns1; + QString dns2; + + QString name; + bool nameOverriddenByUser = false; + int crc; + int configVersion; + ApiConfig apiConfig; + AuthData authData; + + QString vpnKey() const; + QString serviceType() const; + QString serviceProtocol() const; + bool isPremium() const; + bool isFree() const; + bool isExternalPremium() const; + bool hasContainers() const; + ContainerConfig containerConfig(DockerContainer container) const; + QJsonObject toJson() const; + static ApiV2ServerConfig fromJson(const QJsonObject& json); +}; + +} // namespace amnezia + +#endif // APIV2SERVERCONFIG_H + diff --git a/client/core/models/api/authData.cpp b/client/core/models/api/authData.cpp new file mode 100644 index 000000000..bb13e9587 --- /dev/null +++ b/client/core/models/api/authData.cpp @@ -0,0 +1,23 @@ +#include "authData.h" + +namespace amnezia +{ + +QJsonObject AuthData::toJson() const +{ + QJsonObject obj; + if (!apiKey.isEmpty()) { + obj[apiDefs::key::apiKey] = apiKey; + } + return obj; +} + +AuthData AuthData::fromJson(const QJsonObject& json) +{ + AuthData data; + data.apiKey = json.value(apiDefs::key::apiKey).toString(); + return data; +} + +} // namespace amnezia + diff --git a/client/core/models/api/authData.h b/client/core/models/api/authData.h new file mode 100644 index 000000000..1d2061ac9 --- /dev/null +++ b/client/core/models/api/authData.h @@ -0,0 +1,24 @@ +#ifndef AUTHDATA_H +#define AUTHDATA_H + +#include +#include + +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" + +namespace amnezia +{ + +struct AuthData { + QString apiKey; + + QJsonObject toJson() const; + static AuthData fromJson(const QJsonObject& json); +}; + +} // namespace amnezia + +#endif // AUTHDATA_H + diff --git a/client/core/models/containerConfig.cpp b/client/core/models/containerConfig.cpp new file mode 100644 index 000000000..834f1b540 --- /dev/null +++ b/client/core/models/containerConfig.cpp @@ -0,0 +1,147 @@ +#include "containerConfig.h" + +#include + +#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" + +namespace amnezia +{ + +using namespace ContainerEnumNS; +using namespace ProtocolEnumNS; +using namespace ProtocolUtils; + +Proto ContainerConfig::getProtocolType() const +{ + return ContainerUtils::defaultProtocol(container); +} + +QJsonObject ContainerConfig::toJson() const +{ + QJsonObject obj; + + obj[configKey::container] = ContainerUtils::containerToString(container); + + Proto protoType = getProtocolType(); + QString protoName = ProtocolUtils::protoToString(protoType); + + obj[protoName] = protocolConfig.toJson(); + + return obj; +} + +ContainerConfig ContainerConfig::fromJson(const QJsonObject& json) +{ + ContainerConfig config; + + QString containerStr = json.value(configKey::container).toString(); + config.container = ContainerUtils::containerFromString(containerStr); + + Proto protoType = ContainerUtils::defaultProtocol(config.container); + QString protoName = ProtocolUtils::protoToString(protoType); + + QJsonObject protoJson = json.value(protoName).toObject(); + + config.protocolConfig = ProtocolConfig::fromJson(protoJson, protoType); + + return config; +} + +AwgProtocolConfig* ContainerConfig::getAwgProtocolConfig() +{ + return protocolConfig.as(); +} + +const AwgProtocolConfig* ContainerConfig::getAwgProtocolConfig() const +{ + return protocolConfig.as(); +} + +WireGuardProtocolConfig* ContainerConfig::getWireGuardProtocolConfig() +{ + return protocolConfig.as(); +} + +const WireGuardProtocolConfig* ContainerConfig::getWireGuardProtocolConfig() const +{ + return protocolConfig.as(); +} + +OpenVpnProtocolConfig* ContainerConfig::getOpenVpnProtocolConfig() +{ + return protocolConfig.as(); +} + +const OpenVpnProtocolConfig* ContainerConfig::getOpenVpnProtocolConfig() const +{ + return protocolConfig.as(); +} + +XrayProtocolConfig* ContainerConfig::getXrayProtocolConfig() +{ + return protocolConfig.as(); +} + +const XrayProtocolConfig* ContainerConfig::getXrayProtocolConfig() const +{ + return protocolConfig.as(); +} + +SftpProtocolConfig* ContainerConfig::getSftpProtocolConfig() +{ + return protocolConfig.as(); +} + +const SftpProtocolConfig* ContainerConfig::getSftpProtocolConfig() const +{ + return protocolConfig.as(); +} + +Socks5ProxyProtocolConfig* ContainerConfig::getSocks5ProxyProtocolConfig() +{ + return protocolConfig.as(); +} + +const Socks5ProxyProtocolConfig* ContainerConfig::getSocks5ProxyProtocolConfig() const +{ + return protocolConfig.as(); +} + +Ikev2ProtocolConfig* ContainerConfig::getIkev2ProtocolConfig() +{ + return protocolConfig.as(); +} + +const Ikev2ProtocolConfig* ContainerConfig::getIkev2ProtocolConfig() const +{ + return protocolConfig.as(); +} + +TorProtocolConfig* ContainerConfig::getTorProtocolConfig() +{ + return protocolConfig.as(); +} + +const TorProtocolConfig* ContainerConfig::getTorProtocolConfig() const +{ + return protocolConfig.as(); +} + +DnsProtocolConfig* ContainerConfig::getDnsProtocolConfig() +{ + return protocolConfig.as(); +} + +const DnsProtocolConfig* ContainerConfig::getDnsProtocolConfig() const +{ + return protocolConfig.as(); +} + +} // namespace amnezia + diff --git a/client/core/models/containerConfig.h b/client/core/models/containerConfig.h new file mode 100644 index 000000000..7f94cce76 --- /dev/null +++ b/client/core/models/containerConfig.h @@ -0,0 +1,73 @@ +#ifndef CONTAINERCONFIG_H +#define CONTAINERCONFIG_H + +#include +#include + +#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/models/protocolConfig.h" + +namespace amnezia +{ + +using namespace ContainerEnumNS; +using namespace ProtocolEnumNS; + +struct ContainerConfig { + DockerContainer container; + ProtocolConfig protocolConfig; + + Proto getProtocolType() const; + QJsonObject toJson() const; + static ContainerConfig fromJson(const QJsonObject& json); + + template + auto visitProtocol(Visitor&& visitor) + { + return std::visit(std::forward(visitor), protocolConfig); + } + + template + auto visitProtocol(Visitor&& visitor) const + { + return std::visit(std::forward(visitor), protocolConfig); + } + + AwgProtocolConfig* getAwgProtocolConfig(); + const AwgProtocolConfig* getAwgProtocolConfig() const; + + WireGuardProtocolConfig* getWireGuardProtocolConfig(); + const WireGuardProtocolConfig* getWireGuardProtocolConfig() const; + + OpenVpnProtocolConfig* getOpenVpnProtocolConfig(); + const OpenVpnProtocolConfig* getOpenVpnProtocolConfig() const; + + XrayProtocolConfig* getXrayProtocolConfig(); + const XrayProtocolConfig* getXrayProtocolConfig() const; + + SftpProtocolConfig* getSftpProtocolConfig(); + const SftpProtocolConfig* getSftpProtocolConfig() const; + + Socks5ProxyProtocolConfig* getSocks5ProxyProtocolConfig(); + const Socks5ProxyProtocolConfig* getSocks5ProxyProtocolConfig() const; + + Ikev2ProtocolConfig* getIkev2ProtocolConfig(); + const Ikev2ProtocolConfig* getIkev2ProtocolConfig() const; + + TorProtocolConfig* getTorProtocolConfig(); + const TorProtocolConfig* getTorProtocolConfig() const; + + DnsProtocolConfig* getDnsProtocolConfig(); + const DnsProtocolConfig* getDnsProtocolConfig() const; +}; + +} // namespace amnezia + +#endif // CONTAINERCONFIG_H + diff --git a/client/core/models/protocolConfig.cpp b/client/core/models/protocolConfig.cpp new file mode 100644 index 000000000..ed91f7beb --- /dev/null +++ b/client/core/models/protocolConfig.cpp @@ -0,0 +1,307 @@ +#include "protocolConfig.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/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/protocols/ikev2ProtocolConfig.h" +#include "core/models/protocols/dnsProtocolConfig.h" + +namespace amnezia +{ + +using namespace ProtocolEnumNS; +using namespace ProtocolUtils; + +Proto ProtocolConfig::type() const +{ + return std::visit([](auto&& arg) -> Proto { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return Proto::Awg; + } else if constexpr (std::is_same_v) { + return Proto::WireGuard; + } else if constexpr (std::is_same_v) { + return Proto::OpenVpn; + } else if constexpr (std::is_same_v) { + return Proto::Xray; + } else if constexpr (std::is_same_v) { + return Proto::Sftp; + } else if constexpr (std::is_same_v) { + return Proto::Socks5Proxy; + } else if constexpr (std::is_same_v) { + return Proto::Ikev2; + } else if constexpr (std::is_same_v) { + return Proto::TorWebSite; + } else if constexpr (std::is_same_v) { + return Proto::Dns; + } + return Proto::Unknown; + }, data); +} + +QString ProtocolConfig::port() const +{ + return std::visit([](auto&& arg) -> QString { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return arg.serverConfig.port; + } else if constexpr (std::is_same_v) { + return arg.serverConfig.port; + } else if constexpr (std::is_same_v) { + return arg.serverConfig.port; + } else if constexpr (std::is_same_v) { + return arg.serverConfig.port; + } else if constexpr (std::is_same_v) { + return arg.port; + } else if constexpr (std::is_same_v) { + return arg.port; + } else if constexpr (std::is_same_v) { + return QString(); + } else if constexpr (std::is_same_v) { + return QString(); + } else if constexpr (std::is_same_v) { + return QString(); + } + return QString(); + }, data); +} + +QString ProtocolConfig::transportProto() const +{ + return std::visit([](auto&& arg) -> QString { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return arg.serverConfig.transportProto; + } else if constexpr (std::is_same_v) { + return arg.serverConfig.transportProto; + } else if constexpr (std::is_same_v) { + return arg.serverConfig.transportProto; + } else if constexpr (std::is_same_v) { + return arg.serverConfig.transportProto; + } else if constexpr (std::is_same_v) { + return QString(); + } else if constexpr (std::is_same_v) { + return QString(); + } else if constexpr (std::is_same_v) { + return QString(); + } + return QString(); + }, data); +} + +bool ProtocolConfig::hasClientConfig() const +{ + return std::visit([](auto&& arg) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v) { + return arg.hasClientConfig(); + } + return false; + }, data); +} + +QString ProtocolConfig::clientId() const +{ + return std::visit([](auto&& arg) -> QString { + using T = std::decay_t; + if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + return arg.clientConfig->clientId; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + return arg.clientConfig->clientId; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + return arg.clientConfig->clientId; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + return arg.clientConfig->id; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + return arg.clientConfig->clientId; + } + } + return QString(); + }, data); +} + +QJsonObject ProtocolConfig::getClientConfigJson() const +{ + return std::visit([](auto&& arg) -> QJsonObject { + using T = std::decay_t; + if constexpr (std::is_same_v) { + if (arg.hasClientConfig()) { + return arg.clientConfig->toJson(); + } + } else if constexpr (std::is_same_v) { + if (arg.hasClientConfig()) { + return arg.clientConfig->toJson(); + } + } else if constexpr (std::is_same_v) { + if (arg.hasClientConfig()) { + return arg.clientConfig->toJson(); + } + } else if constexpr (std::is_same_v) { + if (arg.hasClientConfig()) { + return arg.clientConfig->toJson(); + } + } else if constexpr (std::is_same_v) { + if (arg.hasClientConfig()) { + return arg.clientConfig->toJson(); + } + } + return QJsonObject(); + }, data); +} + +void ProtocolConfig::setClientConfigJson(const QJsonObject& json) +{ + std::visit([&json](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + arg.setClientConfig(AwgClientConfig::fromJson(json)); + } else if constexpr (std::is_same_v) { + arg.setClientConfig(WireGuardClientConfig::fromJson(json)); + } else if constexpr (std::is_same_v) { + arg.setClientConfig(OpenVpnClientConfig::fromJson(json)); + } else if constexpr (std::is_same_v) { + arg.setClientConfig(XrayClientConfig::fromJson(json)); + } else if constexpr (std::is_same_v) { + arg.setClientConfig(Ikev2ClientConfig::fromJson(json)); + } + }, data); +} + +void ProtocolConfig::clearClientConfig() +{ + std::visit([](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v) { + arg.clearClientConfig(); + } + }, data); +} + +QString ProtocolConfig::nativeConfig() const +{ + return std::visit([](auto&& arg) -> QString { + using T = std::decay_t; + if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + return arg.clientConfig->nativeConfig; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + return arg.clientConfig->nativeConfig; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + return arg.clientConfig->nativeConfig; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + return arg.clientConfig->nativeConfig; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + return arg.clientConfig->nativeConfig; + } + } + return QString(); + }, data); +} + +void ProtocolConfig::setNativeConfig(const QString &config) +{ + std::visit([&config](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + arg.clientConfig->nativeConfig = config; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + arg.clientConfig->nativeConfig = config; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + arg.clientConfig->nativeConfig = config; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + arg.clientConfig->nativeConfig = config; + } + } else if constexpr (std::is_same_v) { + if (arg.clientConfig.has_value()) { + arg.clientConfig->nativeConfig = config; + } + } + }, data); +} + +bool ProtocolConfig::isThirdPartyConfig() const +{ + return std::visit([](auto&& arg) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v) { + return arg.serverConfig.isThirdPartyConfig; + } + return false; + }, data); +} + +QJsonObject ProtocolConfig::toJson() const +{ + return std::visit([](auto&& arg) -> QJsonObject { + return arg.toJson(); + }, data); +} + +ProtocolConfig ProtocolConfig::fromJson(const QJsonObject& json, Proto type) +{ + switch (type) { + case Proto::Awg: + return ProtocolConfig{AwgProtocolConfig::fromJson(json)}; + case Proto::WireGuard: + return ProtocolConfig{WireGuardProtocolConfig::fromJson(json)}; + case Proto::OpenVpn: + return ProtocolConfig{OpenVpnProtocolConfig::fromJson(json)}; + case Proto::Xray: + case Proto::SSXray: + return ProtocolConfig{XrayProtocolConfig::fromJson(json)}; + case Proto::Sftp: + return ProtocolConfig{SftpProtocolConfig::fromJson(json)}; + case Proto::Socks5Proxy: + return ProtocolConfig{Socks5ProxyProtocolConfig::fromJson(json)}; + case Proto::Ikev2: + return ProtocolConfig{Ikev2ProtocolConfig::fromJson(json)}; + case Proto::TorWebSite: + return ProtocolConfig{TorProtocolConfig::fromJson(json)}; + case Proto::Dns: + return ProtocolConfig{DnsProtocolConfig::fromJson(json)}; + default: + return ProtocolConfig{AwgProtocolConfig{}}; + } +} + +} // namespace amnezia diff --git a/client/core/models/protocolConfig.h b/client/core/models/protocolConfig.h new file mode 100644 index 000000000..325f52ab9 --- /dev/null +++ b/client/core/models/protocolConfig.h @@ -0,0 +1,88 @@ +#ifndef PROTOCOLCONFIG_H +#define PROTOCOLCONFIG_H + +#include +#include +#include + +#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/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" + +#include "core/models/protocols/awgProtocolConfig.h" +#include "core/models/protocols/wireGuardProtocolConfig.h" +#include "core/models/protocols/openVpnProtocolConfig.h" +#include "core/models/protocols/xrayProtocolConfig.h" +#include "core/models/protocols/sftpProtocolConfig.h" +#include "core/models/protocols/socks5ProxyProtocolConfig.h" +#include "core/models/protocols/ikev2ProtocolConfig.h" +#include "core/models/protocols/torProtocolConfig.h" +#include "core/models/protocols/dnsProtocolConfig.h" + +namespace amnezia +{ + +using Proto = ProtocolEnumNS::Proto; + +struct ProtocolConfig { + using Variant = std::variant< + AwgProtocolConfig, + WireGuardProtocolConfig, + OpenVpnProtocolConfig, + XrayProtocolConfig, + SftpProtocolConfig, + Socks5ProxyProtocolConfig, + Ikev2ProtocolConfig, + TorProtocolConfig, + DnsProtocolConfig + >; + + Variant data; + + ProtocolConfig() = default; + ProtocolConfig(const Variant& v) : data(v) {} + ProtocolConfig(Variant&& v) : data(std::move(v)) {} + + template>, ProtocolConfig>::value>> + ProtocolConfig(const T& v) : data(v) {} + + template>, ProtocolConfig>::value>> + ProtocolConfig(T&& v) : data(std::forward(v)) {} + + Proto type() const; + + QString port() const; + QString transportProto() const; + + bool hasClientConfig() const; + QString clientId() const; + QJsonObject getClientConfigJson() const; + void setClientConfigJson(const QJsonObject& json); + void clearClientConfig(); + + QString nativeConfig() const; + void setNativeConfig(const QString &config); + + bool isThirdPartyConfig() const; + + QJsonObject toJson() const; + static ProtocolConfig fromJson(const QJsonObject& json, Proto type); + + template + T* as() { + return std::get_if(&data); + } + + template + const T* as() const { + return std::get_if(&data); + } +}; + +} // namespace amnezia + +#endif // PROTOCOLCONFIG_H diff --git a/client/core/models/protocols/awgProtocolConfig.cpp b/client/core/models/protocols/awgProtocolConfig.cpp new file mode 100644 index 000000000..30909a42b --- /dev/null +++ b/client/core/models/protocols/awgProtocolConfig.cpp @@ -0,0 +1,345 @@ +#include "awgProtocolConfig.h" + +#include +#include + +#include "../../../core/utils/protocolEnum.h" +#include "../../../core/protocols/protocolUtils.h" +#include "../../../core/utils/constants/configKeys.h" +#include "../../../core/utils/constants/protocolConstants.h" + +using namespace amnezia; +using namespace ProtocolUtils; +namespace amnezia +{ + +QJsonObject AwgServerConfig::toJson() const +{ + QJsonObject obj; + + if (!port.isEmpty()) { + obj[configKey::port] = this->port; + } + if (!transportProto.isEmpty()) { + obj[configKey::transportProto] = transportProto; + } + if (!protocolVersion.isEmpty()) { + obj[configKey::protocolVersion] = protocolVersion; + } + if (!subnetAddress.isEmpty()) { + obj[configKey::subnetAddress] = subnetAddress; + } + if (!subnetCidr.isEmpty()) { + obj[configKey::subnetCidr] = subnetCidr; + } + + if (!junkPacketCount.isEmpty()) { + obj[configKey::junkPacketCount] = junkPacketCount; + } + if (!junkPacketMinSize.isEmpty()) { + obj[configKey::junkPacketMinSize] = junkPacketMinSize; + } + if (!junkPacketMaxSize.isEmpty()) { + obj[configKey::junkPacketMaxSize] = junkPacketMaxSize; + } + if (!initPacketJunkSize.isEmpty()) { + obj[configKey::initPacketJunkSize] = initPacketJunkSize; + } + if (!responsePacketJunkSize.isEmpty()) { + obj[configKey::responsePacketJunkSize] = responsePacketJunkSize; + } + if (!cookieReplyPacketJunkSize.isEmpty()) { + obj[configKey::cookieReplyPacketJunkSize] = cookieReplyPacketJunkSize; + } + if (!transportPacketJunkSize.isEmpty()) { + obj[configKey::transportPacketJunkSize] = transportPacketJunkSize; + } + + if (!initPacketMagicHeader.isEmpty()) { + obj[configKey::initPacketMagicHeader] = initPacketMagicHeader; + } + if (!responsePacketMagicHeader.isEmpty()) { + obj[configKey::responsePacketMagicHeader] = responsePacketMagicHeader; + } + if (!underloadPacketMagicHeader.isEmpty()) { + obj[configKey::underloadPacketMagicHeader] = underloadPacketMagicHeader; + } + if (!transportPacketMagicHeader.isEmpty()) { + obj[configKey::transportPacketMagicHeader] = transportPacketMagicHeader; + } + + obj[configKey::specialJunk1] = specialJunk1; + obj[configKey::specialJunk2] = specialJunk2; + obj[configKey::specialJunk3] = specialJunk3; + obj[configKey::specialJunk4] = specialJunk4; + obj[configKey::specialJunk5] = specialJunk5; + + if (isThirdPartyConfig) { + obj[configKey::isThirdPartyConfig] = isThirdPartyConfig; + } + + return obj; +} + +AwgServerConfig AwgServerConfig::fromJson(const QJsonObject& json) +{ + AwgServerConfig config; + + config.port = json.value(configKey::port).toString(); + config.transportProto = json.value(configKey::transportProto).toString(); + config.protocolVersion = json.value(configKey::protocolVersion).toString(); + config.subnetAddress = json.value(configKey::subnetAddress).toString(); + config.subnetCidr = json.value(configKey::subnetCidr).toString(); + + config.junkPacketCount = json.value(configKey::junkPacketCount).toString(); + config.junkPacketMinSize = json.value(configKey::junkPacketMinSize).toString(); + config.junkPacketMaxSize = json.value(configKey::junkPacketMaxSize).toString(); + config.initPacketJunkSize = json.value(configKey::initPacketJunkSize).toString(); + config.responsePacketJunkSize = json.value(configKey::responsePacketJunkSize).toString(); + config.cookieReplyPacketJunkSize = json.value(configKey::cookieReplyPacketJunkSize).toString(); + config.transportPacketJunkSize = json.value(configKey::transportPacketJunkSize).toString(); + + config.initPacketMagicHeader = json.value(configKey::initPacketMagicHeader).toString(); + config.responsePacketMagicHeader = json.value(configKey::responsePacketMagicHeader).toString(); + config.underloadPacketMagicHeader = json.value(configKey::underloadPacketMagicHeader).toString(); + config.transportPacketMagicHeader = json.value(configKey::transportPacketMagicHeader).toString(); + + config.specialJunk1 = json.value(configKey::specialJunk1).toString(); + config.specialJunk2 = json.value(configKey::specialJunk2).toString(); + config.specialJunk3 = json.value(configKey::specialJunk3).toString(); + config.specialJunk4 = json.value(configKey::specialJunk4).toString(); + config.specialJunk5 = json.value(configKey::specialJunk5).toString(); + + config.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false); + + return config; +} + +QJsonObject AwgClientConfig::toJson() const +{ + QJsonObject obj; + + if (!nativeConfig.isEmpty()) { + obj[configKey::config] = nativeConfig; + } + if (!hostName.isEmpty()) { + obj[configKey::hostName] = hostName; + } + if (port > 0) { + obj[configKey::port] = port; + } + if (!clientIp.isEmpty()) { + obj[configKey::clientIp] = clientIp; + } + if (!clientPrivateKey.isEmpty()) { + obj[configKey::clientPrivKey] = clientPrivateKey; + } + if (!clientPublicKey.isEmpty()) { + obj[configKey::clientPubKey] = clientPublicKey; + } + if (!serverPublicKey.isEmpty()) { + obj[configKey::serverPubKey] = serverPublicKey; + } + if (!presharedKey.isEmpty()) { + obj[configKey::pskKey] = presharedKey; + } + if (!clientId.isEmpty()) { + obj[configKey::clientId] = clientId; + } + + if (!allowedIps.isEmpty()) { + QJsonArray arr; + for (const QString& ip : allowedIps) { + arr.append(ip); + } + obj[configKey::allowedIps] = arr; + } + if (!persistentKeepAlive.isEmpty()) { + obj[configKey::persistentKeepAlive] = persistentKeepAlive; + } + if (!mtu.isEmpty()) { + obj[configKey::mtu] = mtu; + } + + if (!junkPacketCount.isEmpty()) { + obj[configKey::junkPacketCount] = junkPacketCount; + } + if (!junkPacketMinSize.isEmpty()) { + obj[configKey::junkPacketMinSize] = junkPacketMinSize; + } + if (!junkPacketMaxSize.isEmpty()) { + obj[configKey::junkPacketMaxSize] = junkPacketMaxSize; + } + if (!initPacketJunkSize.isEmpty()) { + obj[configKey::initPacketJunkSize] = initPacketJunkSize; + } + if (!responsePacketJunkSize.isEmpty()) { + obj[configKey::responsePacketJunkSize] = responsePacketJunkSize; + } + if (!cookieReplyPacketJunkSize.isEmpty()) { + obj[configKey::cookieReplyPacketJunkSize] = cookieReplyPacketJunkSize; + } + if (!transportPacketJunkSize.isEmpty()) { + obj[configKey::transportPacketJunkSize] = transportPacketJunkSize; + } + + if (!initPacketMagicHeader.isEmpty()) { + obj[configKey::initPacketMagicHeader] = initPacketMagicHeader; + } + if (!responsePacketMagicHeader.isEmpty()) { + obj[configKey::responsePacketMagicHeader] = responsePacketMagicHeader; + } + if (!underloadPacketMagicHeader.isEmpty()) { + obj[configKey::underloadPacketMagicHeader] = underloadPacketMagicHeader; + } + if (!transportPacketMagicHeader.isEmpty()) { + obj[configKey::transportPacketMagicHeader] = transportPacketMagicHeader; + } + + obj[configKey::specialJunk1] = specialJunk1; + obj[configKey::specialJunk2] = specialJunk2; + obj[configKey::specialJunk3] = specialJunk3; + obj[configKey::specialJunk4] = specialJunk4; + obj[configKey::specialJunk5] = specialJunk5; + + if (isObfuscationEnabled) { + obj[configKey::isObfuscationEnabled] = isObfuscationEnabled; + } + + return obj; +} + +AwgClientConfig AwgClientConfig::fromJson(const QJsonObject& json) +{ + AwgClientConfig config; + + config.nativeConfig = json.value(configKey::config).toString(); + config.hostName = json.value(configKey::hostName).toString(); + config.port = json.value(configKey::port).toInt(0); + config.clientIp = json.value(configKey::clientIp).toString(); + config.clientPrivateKey = json.value(configKey::clientPrivKey).toString(); + config.clientPublicKey = json.value(configKey::clientPubKey).toString(); + config.serverPublicKey = json.value(configKey::serverPubKey).toString(); + config.presharedKey = json.value(configKey::pskKey).toString(); + config.clientId = json.value(configKey::clientId).toString(); + + QJsonArray allowedIpsArr = json.value(configKey::allowedIps).toArray(); + for (const QJsonValue& val : allowedIpsArr) { + config.allowedIps.append(val.toString()); + } + config.persistentKeepAlive = json.value(configKey::persistentKeepAlive).toString(); + config.mtu = json.value(configKey::mtu).toString(); + + config.junkPacketCount = json.value(configKey::junkPacketCount).toString(); + config.junkPacketMinSize = json.value(configKey::junkPacketMinSize).toString(); + config.junkPacketMaxSize = json.value(configKey::junkPacketMaxSize).toString(); + config.initPacketJunkSize = json.value(configKey::initPacketJunkSize).toString(); + config.responsePacketJunkSize = json.value(configKey::responsePacketJunkSize).toString(); + config.cookieReplyPacketJunkSize = json.value(configKey::cookieReplyPacketJunkSize).toString(); + config.transportPacketJunkSize = json.value(configKey::transportPacketJunkSize).toString(); + + config.initPacketMagicHeader = json.value(configKey::initPacketMagicHeader).toString(); + config.responsePacketMagicHeader = json.value(configKey::responsePacketMagicHeader).toString(); + config.underloadPacketMagicHeader = json.value(configKey::underloadPacketMagicHeader).toString(); + config.transportPacketMagicHeader = json.value(configKey::transportPacketMagicHeader).toString(); + + config.specialJunk1 = json.value(configKey::specialJunk1).toString(); + config.specialJunk2 = json.value(configKey::specialJunk2).toString(); + config.specialJunk3 = json.value(configKey::specialJunk3).toString(); + config.specialJunk4 = json.value(configKey::specialJunk4).toString(); + config.specialJunk5 = json.value(configKey::specialJunk5).toString(); + + config.isObfuscationEnabled = json.value(configKey::isObfuscationEnabled).toBool(false); + + return config; +} + +QJsonObject AwgProtocolConfig::toJson() const +{ + QJsonObject obj = serverConfig.toJson(); + + if (clientConfig.has_value()) { + QJsonObject clientJson = clientConfig->toJson(); + obj[configKey::lastConfig] = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact)); + } + + return obj; +} + +AwgProtocolConfig AwgProtocolConfig::fromJson(const QJsonObject& json) +{ + AwgProtocolConfig config; + + config.serverConfig = AwgServerConfig::fromJson(json); + + QString lastConfigStr = json.value(configKey::lastConfig).toString(); + if (!lastConfigStr.isEmpty()) { + QJsonDocument doc = QJsonDocument::fromJson(lastConfigStr.toUtf8()); + if (doc.isObject()) { + config.clientConfig = AwgClientConfig::fromJson(doc.object()); + } + } + + return config; +} + +bool AwgProtocolConfig::hasClientConfig() const +{ + return clientConfig.has_value(); +} + +void AwgProtocolConfig::setClientConfig(const AwgClientConfig& config) +{ + clientConfig = config; +} + +void AwgProtocolConfig::clearClientConfig() +{ + clientConfig.reset(); +} + +bool AwgServerConfig::hasEqualServerSettings(const AwgServerConfig& other) const +{ + if (subnetAddress != other.subnetAddress || port != other.port || + junkPacketCount != other.junkPacketCount || + junkPacketMinSize != other.junkPacketMinSize || junkPacketMaxSize != other.junkPacketMaxSize || + initPacketJunkSize != other.initPacketJunkSize || responsePacketJunkSize != other.responsePacketJunkSize || + initPacketMagicHeader != other.initPacketMagicHeader || + responsePacketMagicHeader != other.responsePacketMagicHeader || + underloadPacketMagicHeader != other.underloadPacketMagicHeader || + transportPacketMagicHeader != other.transportPacketMagicHeader || + specialJunk1 != other.specialJunk1 || specialJunk2 != other.specialJunk2 || + specialJunk3 != other.specialJunk3 || specialJunk4 != other.specialJunk4 || + specialJunk5 != other.specialJunk5) { + return false; + } + + bool isV2 = protocolVersion == protocols::awg::awgV2; + if (isV2) { + if (cookieReplyPacketJunkSize != other.cookieReplyPacketJunkSize || + transportPacketJunkSize != other.transportPacketJunkSize) { + return false; + } + } + + return true; +} + +bool AwgProtocolConfig::isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4) +{ + return (h1 == h2) || (h1 == h3) || (h1 == h4) || (h2 == h3) || (h2 == h4) || (h3 == h4); +} + +bool AwgProtocolConfig::isPacketSizeEqual(int s1, int s2, int s3, int s4) +{ + int initSize = AwgConstant::messageInitiationSize + s1; + int responseSize = AwgConstant::messageResponseSize + s2; + int cookieSize = AwgConstant::messageCookieReplySize + s3; + int transportSize = AwgConstant::messageTransportSize + s4; + + return (initSize == responseSize || initSize == cookieSize || initSize == transportSize || responseSize == cookieSize + || responseSize == transportSize || cookieSize == transportSize); +} + +} // namespace amnezia + diff --git a/client/core/models/protocols/awgProtocolConfig.h b/client/core/models/protocols/awgProtocolConfig.h new file mode 100644 index 000000000..9c5351236 --- /dev/null +++ b/client/core/models/protocols/awgProtocolConfig.h @@ -0,0 +1,103 @@ +#ifndef AWGPROTOCOLCONFIG_H +#define AWGPROTOCOLCONFIG_H + +#include +#include +#include +#include + +namespace amnezia +{ + +namespace AwgConstant +{ + const int messageInitiationSize = 148; + const int messageResponseSize = 92; + const int messageCookieReplySize = 64; + const int messageTransportSize = 32; +} + +struct AwgServerConfig { + QString port; + QString transportProto; + QString protocolVersion; + QString subnetAddress; + QString subnetCidr; + QString junkPacketCount; + QString junkPacketMinSize; + QString junkPacketMaxSize; + QString initPacketJunkSize; + QString responsePacketJunkSize; + QString cookieReplyPacketJunkSize; + QString transportPacketJunkSize; + QString initPacketMagicHeader; + QString responsePacketMagicHeader; + QString underloadPacketMagicHeader; + QString transportPacketMagicHeader; + QString specialJunk1; + QString specialJunk2; + QString specialJunk3; + QString specialJunk4; + QString specialJunk5; + bool isThirdPartyConfig = false; + + QJsonObject toJson() const; + static AwgServerConfig fromJson(const QJsonObject& json); + + bool hasEqualServerSettings(const AwgServerConfig& other) const; +}; + +struct AwgClientConfig { + QString nativeConfig; + QString hostName; + int port; + QString clientIp; + QString clientPrivateKey; + QString clientPublicKey; + QString serverPublicKey; + QString presharedKey; + QString clientId; + QStringList allowedIps; + QString persistentKeepAlive; + QString mtu; + QString junkPacketCount; + QString junkPacketMinSize; + QString junkPacketMaxSize; + QString initPacketJunkSize; + QString responsePacketJunkSize; + QString cookieReplyPacketJunkSize; + QString transportPacketJunkSize; + QString initPacketMagicHeader; + QString responsePacketMagicHeader; + QString underloadPacketMagicHeader; + QString transportPacketMagicHeader; + QString specialJunk1; + QString specialJunk2; + QString specialJunk3; + QString specialJunk4; + QString specialJunk5; + bool isObfuscationEnabled = false; + + QJsonObject toJson() const; + static AwgClientConfig fromJson(const QJsonObject& json); +}; + +struct AwgProtocolConfig { + AwgServerConfig serverConfig; + std::optional clientConfig; + + QJsonObject toJson() const; + static AwgProtocolConfig fromJson(const QJsonObject& json); + + bool hasClientConfig() const; + void setClientConfig(const AwgClientConfig& config); + void clearClientConfig(); + + static bool isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4); + static bool isPacketSizeEqual(int s1, int s2, int s3, int s4); +}; + +} // namespace amnezia + +#endif // AWGPROTOCOLCONFIG_H + diff --git a/client/core/models/protocols/dnsProtocolConfig.cpp b/client/core/models/protocols/dnsProtocolConfig.cpp new file mode 100644 index 000000000..fd6b9c1b7 --- /dev/null +++ b/client/core/models/protocols/dnsProtocolConfig.cpp @@ -0,0 +1,18 @@ +#include "dnsProtocolConfig.h" + +namespace amnezia +{ + +QJsonObject DnsProtocolConfig::toJson() const +{ + return QJsonObject(); +} + +DnsProtocolConfig DnsProtocolConfig::fromJson(const QJsonObject& json) +{ + Q_UNUSED(json); + return DnsProtocolConfig(); +} + +} // namespace amnezia + diff --git a/client/core/models/protocols/dnsProtocolConfig.h b/client/core/models/protocols/dnsProtocolConfig.h new file mode 100644 index 000000000..d2de14837 --- /dev/null +++ b/client/core/models/protocols/dnsProtocolConfig.h @@ -0,0 +1,17 @@ +#ifndef DNSPROTOCOLCONFIG_H +#define DNSPROTOCOLCONFIG_H + +#include + +namespace amnezia +{ + +struct DnsProtocolConfig { + QJsonObject toJson() const; + static DnsProtocolConfig fromJson(const QJsonObject& json); +}; + +} // namespace amnezia + +#endif // DNSPROTOCOLCONFIG_H + diff --git a/client/core/models/protocols/ikev2ProtocolConfig.cpp b/client/core/models/protocols/ikev2ProtocolConfig.cpp new file mode 100644 index 000000000..1d8d19fb5 --- /dev/null +++ b/client/core/models/protocols/ikev2ProtocolConfig.cpp @@ -0,0 +1,125 @@ +#include "ikev2ProtocolConfig.h" + +#include + +#include "../../../core/utils/protocolEnum.h" +#include "../../../core/protocols/protocolUtils.h" +#include "../../../core/utils/constants/configKeys.h" +#include "../../../core/utils/constants/protocolConstants.h" + +using namespace amnezia; +using namespace ProtocolUtils; +namespace amnezia +{ + +QJsonObject Ikev2ServerConfig::toJson() const +{ + QJsonObject obj; + + if (!hostName.isEmpty()) { + obj[configKey::hostName] = hostName; + } + + if (isThirdPartyConfig) { + obj[configKey::isThirdPartyConfig] = isThirdPartyConfig; + } + + return obj; +} + +Ikev2ServerConfig Ikev2ServerConfig::fromJson(const QJsonObject& json) +{ + Ikev2ServerConfig config; + + config.hostName = json.value(configKey::hostName).toString(); + config.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false); + + return config; +} + +QJsonObject Ikev2ClientConfig::toJson() const +{ + QJsonObject obj; + + if (!nativeConfig.isEmpty()) { + obj[configKey::config] = nativeConfig; + } + if (!hostName.isEmpty()) { + obj[configKey::hostName] = hostName; + } + if (!userName.isEmpty()) { + obj[configKey::userName] = userName; + } + if (!cert.isEmpty()) { + obj[configKey::cert] = cert; + } + if (!password.isEmpty()) { + obj[configKey::password] = password; + } + if (!clientId.isEmpty()) { + obj[configKey::clientId] = clientId; + } + + return obj; +} + +Ikev2ClientConfig Ikev2ClientConfig::fromJson(const QJsonObject& json) +{ + Ikev2ClientConfig config; + + config.nativeConfig = json.value(configKey::config).toString(); + config.hostName = json.value(configKey::hostName).toString(); + config.userName = json.value(configKey::userName).toString(); + config.cert = json.value(configKey::cert).toString(); + config.password = json.value(configKey::password).toString(); + config.clientId = json.value(configKey::clientId).toString(); + + return config; +} + +QJsonObject Ikev2ProtocolConfig::toJson() const +{ + QJsonObject obj = serverConfig.toJson(); + + if (clientConfig.has_value()) { + QJsonObject clientJson = clientConfig->toJson(); + obj[configKey::lastConfig] = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact)); + } + + return obj; +} + +Ikev2ProtocolConfig Ikev2ProtocolConfig::fromJson(const QJsonObject& json) +{ + Ikev2ProtocolConfig config; + + config.serverConfig = Ikev2ServerConfig::fromJson(json); + + QString lastConfigStr = json.value(configKey::lastConfig).toString(); + if (!lastConfigStr.isEmpty()) { + QJsonDocument doc = QJsonDocument::fromJson(lastConfigStr.toUtf8()); + if (doc.isObject()) { + config.clientConfig = Ikev2ClientConfig::fromJson(doc.object()); + } + } + + return config; +} + +bool Ikev2ProtocolConfig::hasClientConfig() const +{ + return clientConfig.has_value(); +} + +void Ikev2ProtocolConfig::setClientConfig(const Ikev2ClientConfig& config) +{ + clientConfig = config; +} + +void Ikev2ProtocolConfig::clearClientConfig() +{ + clientConfig.reset(); +} + +} // namespace amnezia + diff --git a/client/core/models/protocols/ikev2ProtocolConfig.h b/client/core/models/protocols/ikev2ProtocolConfig.h new file mode 100644 index 000000000..ebb706daf --- /dev/null +++ b/client/core/models/protocols/ikev2ProtocolConfig.h @@ -0,0 +1,48 @@ +#ifndef IKEV2PROTOCOLCONFIG_H +#define IKEV2PROTOCOLCONFIG_H + +#include +#include +#include +#include + +namespace amnezia +{ + +struct Ikev2ServerConfig { + QString hostName; + bool isThirdPartyConfig = false; + + QJsonObject toJson() const; + static Ikev2ServerConfig fromJson(const QJsonObject& json); +}; + +struct Ikev2ClientConfig { + QString nativeConfig; + QString hostName; + QString userName; + QString cert; + QString password; + QString clientId; + + QJsonObject toJson() const; + static Ikev2ClientConfig fromJson(const QJsonObject& json); +}; + +struct Ikev2ProtocolConfig { + Ikev2ServerConfig serverConfig; + std::optional clientConfig; + + QJsonObject toJson() const; + static Ikev2ProtocolConfig fromJson(const QJsonObject& json); + + bool hasClientConfig() const; + void setClientConfig(const Ikev2ClientConfig& config); + void clearClientConfig(); +}; + +} // namespace amnezia + +#endif // IKEV2PROTOCOLCONFIG_H + + diff --git a/client/core/models/protocols/openVpnProtocolConfig.cpp b/client/core/models/protocols/openVpnProtocolConfig.cpp new file mode 100644 index 000000000..46000ce14 --- /dev/null +++ b/client/core/models/protocols/openVpnProtocolConfig.cpp @@ -0,0 +1,164 @@ +#include "openVpnProtocolConfig.h" + +#include + +#include "../../../core/utils/protocolEnum.h" +#include "../../../core/protocols/protocolUtils.h" +#include "../../../core/utils/constants/configKeys.h" +#include "../../../core/utils/constants/protocolConstants.h" + +using namespace amnezia; +using namespace ProtocolUtils; +namespace amnezia +{ + +QJsonObject OpenVpnServerConfig::toJson() const +{ + QJsonObject obj; + + if (!port.isEmpty()) { + obj[configKey::port] = port; + } + if (!transportProto.isEmpty()) { + obj[configKey::transportProto] = transportProto; + } + if (!subnetAddress.isEmpty()) { + obj[configKey::subnetAddress] = subnetAddress; + } + if (!subnetMask.isEmpty()) { + obj[configKey::subnetMask] = subnetMask; + } + if (!subnetCidr.isEmpty()) { + obj[configKey::subnetCidr] = subnetCidr; + } + if (!cipher.isEmpty()) { + obj[configKey::cipher] = cipher; + } + if (!hash.isEmpty()) { + obj[configKey::hash] = hash; + } + if (ncpDisable != false) { + obj[configKey::ncpDisable] = ncpDisable; + } + if (tlsAuth != true) { + obj[configKey::tlsAuth] = tlsAuth; + } + if (!additionalClientConfig.isEmpty()) { + obj[configKey::additionalClientConfig] = additionalClientConfig; + } + if (!additionalServerConfig.isEmpty()) { + obj[configKey::additionalServerConfig] = additionalServerConfig; + } + + if (isThirdPartyConfig) { + obj[configKey::isThirdPartyConfig] = isThirdPartyConfig; + } + + return obj; +} + +OpenVpnServerConfig OpenVpnServerConfig::fromJson(const QJsonObject& json) +{ + OpenVpnServerConfig config; + + config.port = json.value(configKey::port).toString(); + config.transportProto = json.value(configKey::transportProto).toString(); + config.subnetAddress = json.value(configKey::subnetAddress).toString(); + config.subnetMask = json.value(configKey::subnetMask).toString(); + config.subnetCidr = json.value(configKey::subnetCidr).toString(); + config.cipher = json.value(configKey::cipher).toString(); + config.hash = json.value(configKey::hash).toString(); + config.ncpDisable = json.value(configKey::ncpDisable).toBool(false); + config.tlsAuth = json.value(configKey::tlsAuth).toBool(true); + config.additionalClientConfig = json.value(configKey::additionalClientConfig).toString(); + config.additionalServerConfig = json.value(configKey::additionalServerConfig).toString(); + + config.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false); + + return config; +} + +bool OpenVpnServerConfig::hasEqualServerSettings(const OpenVpnServerConfig& other) const +{ + return subnetAddress == other.subnetAddress && + port == other.port && + transportProto == other.transportProto && + cipher == other.cipher && + hash == other.hash && + ncpDisable == other.ncpDisable && + tlsAuth == other.tlsAuth && + additionalServerConfig == other.additionalServerConfig; +} + +QJsonObject OpenVpnClientConfig::toJson() const +{ + QJsonObject obj; + + if (!nativeConfig.isEmpty()) { + obj[configKey::config] = nativeConfig; + } + if (!clientId.isEmpty()) { + obj[configKey::clientId] = clientId; + } + obj[configKey::blockOutsideDns] = blockOutsideDns; + + return obj; +} + +OpenVpnClientConfig OpenVpnClientConfig::fromJson(const QJsonObject& json) +{ + OpenVpnClientConfig config; + + config.nativeConfig = json.value(configKey::config).toString(); + config.clientId = json.value(configKey::clientId).toString(); + config.blockOutsideDns = json.value(configKey::blockOutsideDns).toBool(false); + + return config; +} + +QJsonObject OpenVpnProtocolConfig::toJson() const +{ + QJsonObject obj = serverConfig.toJson(); + + if (clientConfig.has_value()) { + QJsonObject clientJson = clientConfig->toJson(); + obj[configKey::lastConfig] = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact)); + } + + return obj; +} + +OpenVpnProtocolConfig OpenVpnProtocolConfig::fromJson(const QJsonObject& json) +{ + OpenVpnProtocolConfig config; + + config.serverConfig = OpenVpnServerConfig::fromJson(json); + + QString lastConfigStr = json.value(configKey::lastConfig).toString(); + if (!lastConfigStr.isEmpty()) { + QJsonDocument doc = QJsonDocument::fromJson(lastConfigStr.toUtf8()); + if (doc.isObject()) { + config.clientConfig = OpenVpnClientConfig::fromJson(doc.object()); + } + } + + return config; +} + +bool OpenVpnProtocolConfig::hasClientConfig() const +{ + return clientConfig.has_value(); +} + +void OpenVpnProtocolConfig::setClientConfig(const OpenVpnClientConfig& config) +{ + clientConfig = config; +} + +void OpenVpnProtocolConfig::clearClientConfig() +{ + clientConfig.reset(); +} + +} // namespace amnezia + diff --git a/client/core/models/protocols/openVpnProtocolConfig.h b/client/core/models/protocols/openVpnProtocolConfig.h new file mode 100644 index 000000000..8c126fbff --- /dev/null +++ b/client/core/models/protocols/openVpnProtocolConfig.h @@ -0,0 +1,55 @@ +#ifndef OPENVPNPROTOCOLCONFIG_H +#define OPENVPNPROTOCOLCONFIG_H + +#include +#include +#include + +namespace amnezia +{ + +struct OpenVpnServerConfig { + QString port; + QString transportProto; + QString subnetAddress; + QString subnetMask; + QString subnetCidr; + QString cipher; + QString hash; + bool ncpDisable = false; + bool tlsAuth = true; + QString additionalClientConfig; + QString additionalServerConfig; + bool isThirdPartyConfig = false; + + QJsonObject toJson() const; + static OpenVpnServerConfig fromJson(const QJsonObject& json); + + bool hasEqualServerSettings(const OpenVpnServerConfig& other) const; +}; + +struct OpenVpnClientConfig { + QString nativeConfig; + QString clientId; + bool blockOutsideDns = false; + + QJsonObject toJson() const; + static OpenVpnClientConfig fromJson(const QJsonObject& json); +}; + +struct OpenVpnProtocolConfig { + OpenVpnServerConfig serverConfig; + std::optional clientConfig; + + QJsonObject toJson() const; + static OpenVpnProtocolConfig fromJson(const QJsonObject& json); + + bool hasClientConfig() const; + void setClientConfig(const OpenVpnClientConfig& config); + void clearClientConfig(); +}; + +} // namespace amnezia + +#endif // OPENVPNPROTOCOLCONFIG_H + diff --git a/client/core/models/protocols/sftpProtocolConfig.cpp b/client/core/models/protocols/sftpProtocolConfig.cpp new file mode 100644 index 000000000..90bbe16c3 --- /dev/null +++ b/client/core/models/protocols/sftpProtocolConfig.cpp @@ -0,0 +1,42 @@ +#include "sftpProtocolConfig.h" + +#include "../../../core/utils/protocolEnum.h" +#include "../../../core/protocols/protocolUtils.h" +#include "../../../core/utils/constants/configKeys.h" +#include "../../../core/utils/constants/protocolConstants.h" + +using namespace amnezia; +using namespace ProtocolUtils; +namespace amnezia +{ + +QJsonObject SftpProtocolConfig::toJson() const +{ + QJsonObject obj; + + if (!port.isEmpty()) { + obj[configKey::port] = port; + } + if (!userName.isEmpty()) { + obj[configKey::userName] = userName; + } + if (!password.isEmpty()) { + obj[configKey::password] = password; + } + + return obj; +} + +SftpProtocolConfig SftpProtocolConfig::fromJson(const QJsonObject& json) +{ + SftpProtocolConfig config; + + config.port = json.value(configKey::port).toString(); + config.userName = json.value(configKey::userName).toString(); + config.password = json.value(configKey::password).toString(); + + return config; +} + +} // namespace amnezia + diff --git a/client/core/models/protocols/sftpProtocolConfig.h b/client/core/models/protocols/sftpProtocolConfig.h new file mode 100644 index 000000000..aac2e5e2c --- /dev/null +++ b/client/core/models/protocols/sftpProtocolConfig.h @@ -0,0 +1,22 @@ +#ifndef SFTPPROTOCOLCONFIG_H +#define SFTPPROTOCOLCONFIG_H + +#include +#include + +namespace amnezia +{ + +struct SftpProtocolConfig { + QString port; + QString userName; + QString password; + + QJsonObject toJson() const; + static SftpProtocolConfig fromJson(const QJsonObject& json); +}; + +} // namespace amnezia + +#endif // SFTPPROTOCOLCONFIG_H + diff --git a/client/core/models/protocols/socks5ProxyProtocolConfig.cpp b/client/core/models/protocols/socks5ProxyProtocolConfig.cpp new file mode 100644 index 000000000..35410ec19 --- /dev/null +++ b/client/core/models/protocols/socks5ProxyProtocolConfig.cpp @@ -0,0 +1,42 @@ +#include "socks5ProxyProtocolConfig.h" + +#include "../../../core/utils/protocolEnum.h" +#include "../../../core/protocols/protocolUtils.h" +#include "../../../core/utils/constants/configKeys.h" +#include "../../../core/utils/constants/protocolConstants.h" + +using namespace amnezia; +using namespace ProtocolUtils; +namespace amnezia +{ + +QJsonObject Socks5ProxyProtocolConfig::toJson() const +{ + QJsonObject obj; + + if (!port.isEmpty()) { + obj[configKey::port] = port; + } + if (!userName.isEmpty()) { + obj[configKey::userName] = userName; + } + if (!password.isEmpty()) { + obj[configKey::password] = password; + } + + return obj; +} + +Socks5ProxyProtocolConfig Socks5ProxyProtocolConfig::fromJson(const QJsonObject& json) +{ + Socks5ProxyProtocolConfig config; + + config.port = json.value(configKey::port).toString(); + config.userName = json.value(configKey::userName).toString(); + config.password = json.value(configKey::password).toString(); + + return config; +} + +} // namespace amnezia + diff --git a/client/core/models/protocols/socks5ProxyProtocolConfig.h b/client/core/models/protocols/socks5ProxyProtocolConfig.h new file mode 100644 index 000000000..10318fda2 --- /dev/null +++ b/client/core/models/protocols/socks5ProxyProtocolConfig.h @@ -0,0 +1,22 @@ +#ifndef SOCKS5PROXYPROTOCOLCONFIG_H +#define SOCKS5PROXYPROTOCOLCONFIG_H + +#include +#include + +namespace amnezia +{ + +struct Socks5ProxyProtocolConfig { + QString port; + QString userName; + QString password; + + QJsonObject toJson() const; + static Socks5ProxyProtocolConfig fromJson(const QJsonObject& json); +}; + +} // namespace amnezia + +#endif // SOCKS5PROXYPROTOCOLCONFIG_H + diff --git a/client/core/models/protocols/torProtocolConfig.cpp b/client/core/models/protocols/torProtocolConfig.cpp new file mode 100644 index 000000000..5304fccd6 --- /dev/null +++ b/client/core/models/protocols/torProtocolConfig.cpp @@ -0,0 +1,45 @@ +#include "torProtocolConfig.h" + +#include "core/utils/constants/configKeys.h" + +using namespace amnezia; + +namespace amnezia +{ + +QJsonObject TorServerConfig::toJson() const +{ + QJsonObject obj; + + if (!site.isEmpty()) { + obj[configKey::site] = site; + } + + return obj; +} + +TorServerConfig TorServerConfig::fromJson(const QJsonObject& json) +{ + TorServerConfig config; + + config.site = json.value(configKey::site).toString(); + + return config; +} + +QJsonObject TorProtocolConfig::toJson() const +{ + return serverConfig.toJson(); +} + +TorProtocolConfig TorProtocolConfig::fromJson(const QJsonObject& json) +{ + TorProtocolConfig config; + + config.serverConfig = TorServerConfig::fromJson(json); + + return config; +} + +} // namespace amnezia + diff --git a/client/core/models/protocols/torProtocolConfig.h b/client/core/models/protocols/torProtocolConfig.h new file mode 100644 index 000000000..e6a4d6f86 --- /dev/null +++ b/client/core/models/protocols/torProtocolConfig.h @@ -0,0 +1,27 @@ +#ifndef TORPROTOCOLCONFIG_H +#define TORPROTOCOLCONFIG_H + +#include +#include + +namespace amnezia +{ + +struct TorServerConfig { + QString site; + + QJsonObject toJson() const; + static TorServerConfig fromJson(const QJsonObject& json); +}; + +struct TorProtocolConfig { + TorServerConfig serverConfig; + + QJsonObject toJson() const; + static TorProtocolConfig fromJson(const QJsonObject& json); +}; + +} // namespace amnezia + +#endif // TORPROTOCOLCONFIG_H + diff --git a/client/core/models/protocols/wireGuardProtocolConfig.cpp b/client/core/models/protocols/wireGuardProtocolConfig.cpp new file mode 100644 index 000000000..23911b947 --- /dev/null +++ b/client/core/models/protocols/wireGuardProtocolConfig.cpp @@ -0,0 +1,187 @@ +#include "wireGuardProtocolConfig.h" + +#include +#include + +#include "../../../core/utils/protocolEnum.h" +#include "../../../core/protocols/protocolUtils.h" +#include "../../../core/utils/constants/configKeys.h" +#include "../../../core/utils/constants/protocolConstants.h" + +using namespace amnezia; +using namespace ProtocolUtils; +namespace amnezia +{ + +QJsonObject WireGuardServerConfig::toJson() const +{ + QJsonObject obj; + + if (!port.isEmpty()) { + obj[configKey::port] = port; + } + if (!transportProto.isEmpty()) { + obj[configKey::transportProto] = transportProto; + } + if (!subnetAddress.isEmpty()) { + obj[configKey::subnetAddress] = subnetAddress; + } + if (!subnetMask.isEmpty()) { + obj[configKey::subnetMask] = subnetMask; + } + if (!subnetCidr.isEmpty()) { + obj[configKey::subnetCidr] = subnetCidr; + } + + if (isThirdPartyConfig) { + obj[configKey::isThirdPartyConfig] = isThirdPartyConfig; + } + + return obj; +} + +WireGuardServerConfig WireGuardServerConfig::fromJson(const QJsonObject& json) +{ + WireGuardServerConfig config; + + config.port = json.value(configKey::port).toString(); + config.transportProto = json.value(configKey::transportProto).toString(); + config.subnetAddress = json.value(configKey::subnetAddress).toString(); + config.subnetMask = json.value(configKey::subnetMask).toString(); + config.subnetCidr = json.value(configKey::subnetCidr).toString(); + + config.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false); + + return config; +} + +bool WireGuardServerConfig::hasEqualServerSettings(const WireGuardServerConfig& other) const +{ + return subnetAddress == other.subnetAddress && port == other.port; +} + +QJsonObject WireGuardClientConfig::toJson() const +{ + QJsonObject obj; + + if (!nativeConfig.isEmpty()) { + obj[configKey::config] = nativeConfig; + } + if (!hostName.isEmpty()) { + obj[configKey::hostName] = hostName; + } + if (port > 0) { + obj[configKey::port] = port; + } + if (!clientIp.isEmpty()) { + obj[configKey::clientIp] = clientIp; + } + if (!clientPrivateKey.isEmpty()) { + obj[configKey::clientPrivKey] = clientPrivateKey; + } + if (!clientPublicKey.isEmpty()) { + obj[configKey::clientPubKey] = clientPublicKey; + } + if (!serverPublicKey.isEmpty()) { + obj[configKey::serverPubKey] = serverPublicKey; + } + if (!presharedKey.isEmpty()) { + obj[configKey::pskKey] = presharedKey; + } + if (!clientId.isEmpty()) { + obj[configKey::clientId] = clientId; + } + + if (!allowedIps.isEmpty()) { + QJsonArray arr; + for (const QString& ip : allowedIps) { + arr.append(ip); + } + obj[configKey::allowedIps] = arr; + } + if (!persistentKeepAlive.isEmpty()) { + obj[configKey::persistentKeepAlive] = persistentKeepAlive; + } + if (!mtu.isEmpty()) { + obj[configKey::mtu] = mtu; + } + + if (isObfuscationEnabled) { + obj[configKey::isObfuscationEnabled] = isObfuscationEnabled; + } + + return obj; +} + +WireGuardClientConfig WireGuardClientConfig::fromJson(const QJsonObject& json) +{ + WireGuardClientConfig config; + + config.nativeConfig = json.value(configKey::config).toString(); + config.hostName = json.value(configKey::hostName).toString(); + config.port = json.value(configKey::port).toInt(0); + config.clientIp = json.value(configKey::clientIp).toString(); + config.clientPrivateKey = json.value(configKey::clientPrivKey).toString(); + config.clientPublicKey = json.value(configKey::clientPubKey).toString(); + config.serverPublicKey = json.value(configKey::serverPubKey).toString(); + config.presharedKey = json.value(configKey::pskKey).toString(); + config.clientId = json.value(configKey::clientId).toString(); + + QJsonArray allowedIpsArr = json.value(configKey::allowedIps).toArray(); + for (const QJsonValue& val : allowedIpsArr) { + config.allowedIps.append(val.toString()); + } + config.persistentKeepAlive = json.value(configKey::persistentKeepAlive).toString(); + config.mtu = json.value(configKey::mtu).toString(); + + config.isObfuscationEnabled = json.value(configKey::isObfuscationEnabled).toBool(false); + + return config; +} + +QJsonObject WireGuardProtocolConfig::toJson() const +{ + QJsonObject obj = serverConfig.toJson(); + + if (clientConfig.has_value()) { + QJsonObject clientJson = clientConfig->toJson(); + obj[configKey::lastConfig] = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact)); + } + + return obj; +} + +WireGuardProtocolConfig WireGuardProtocolConfig::fromJson(const QJsonObject& json) +{ + WireGuardProtocolConfig config; + + config.serverConfig = WireGuardServerConfig::fromJson(json); + + QString lastConfigStr = json.value(configKey::lastConfig).toString(); + if (!lastConfigStr.isEmpty()) { + QJsonDocument doc = QJsonDocument::fromJson(lastConfigStr.toUtf8()); + if (doc.isObject()) { + config.clientConfig = WireGuardClientConfig::fromJson(doc.object()); + } + } + + return config; +} + +bool WireGuardProtocolConfig::hasClientConfig() const +{ + return clientConfig.has_value(); +} + +void WireGuardProtocolConfig::setClientConfig(const WireGuardClientConfig& config) +{ + clientConfig = config; +} + +void WireGuardProtocolConfig::clearClientConfig() +{ + clientConfig.reset(); +} + +} // namespace amnezia + diff --git a/client/core/models/protocols/wireGuardProtocolConfig.h b/client/core/models/protocols/wireGuardProtocolConfig.h new file mode 100644 index 000000000..96e5d82e3 --- /dev/null +++ b/client/core/models/protocols/wireGuardProtocolConfig.h @@ -0,0 +1,60 @@ +#ifndef WIREGUARDPROTOCOLCONFIG_H +#define WIREGUARDPROTOCOLCONFIG_H + +#include +#include +#include +#include + +namespace amnezia +{ + +struct WireGuardServerConfig { + QString port; + QString transportProto; + QString subnetAddress; + QString subnetMask; + QString subnetCidr; + bool isThirdPartyConfig = false; + + QJsonObject toJson() const; + static WireGuardServerConfig fromJson(const QJsonObject& json); + + bool hasEqualServerSettings(const WireGuardServerConfig& other) const; +}; + +struct WireGuardClientConfig { + QString nativeConfig; + QString hostName; + int port; + QString clientIp; + QString clientPrivateKey; + QString clientPublicKey; + QString serverPublicKey; + QString presharedKey; + QString clientId; + QStringList allowedIps; + QString persistentKeepAlive; + QString mtu; + bool isObfuscationEnabled = false; + + QJsonObject toJson() const; + static WireGuardClientConfig fromJson(const QJsonObject& json); +}; + +struct WireGuardProtocolConfig { + WireGuardServerConfig serverConfig; + std::optional clientConfig; + + QJsonObject toJson() const; + static WireGuardProtocolConfig fromJson(const QJsonObject& json); + + bool hasClientConfig() const; + void setClientConfig(const WireGuardClientConfig& config); + void clearClientConfig(); +}; + +} // namespace amnezia + +#endif // WIREGUARDPROTOCOLCONFIG_H + diff --git a/client/core/models/protocols/xrayProtocolConfig.cpp b/client/core/models/protocols/xrayProtocolConfig.cpp new file mode 100644 index 000000000..bb4e61457 --- /dev/null +++ b/client/core/models/protocols/xrayProtocolConfig.cpp @@ -0,0 +1,187 @@ +#include "xrayProtocolConfig.h" + +#include +#include + +#include "../../../core/utils/protocolEnum.h" +#include "../../../core/protocols/protocolUtils.h" +#include "../../../core/utils/constants/configKeys.h" +#include "../../../core/utils/constants/protocolConstants.h" + +using namespace amnezia; +using namespace ProtocolUtils; +namespace amnezia +{ + +QJsonObject XrayServerConfig::toJson() const +{ + QJsonObject obj; + + if (!port.isEmpty()) { + obj[configKey::port] = port; + } + if (!transportProto.isEmpty()) { + obj[configKey::transportProto] = transportProto; + } + if (!subnetAddress.isEmpty()) { + obj[configKey::subnetAddress] = subnetAddress; + } + if (!site.isEmpty()) { + obj[configKey::site] = site; + } + + if (isThirdPartyConfig) { + obj[configKey::isThirdPartyConfig] = isThirdPartyConfig; + } + + return obj; +} + +XrayServerConfig XrayServerConfig::fromJson(const QJsonObject& json) +{ + XrayServerConfig config; + + config.port = json.value(configKey::port).toString(); + config.transportProto = json.value(configKey::transportProto).toString(); + config.subnetAddress = json.value(configKey::subnetAddress).toString(); + config.site = json.value(configKey::site).toString(); + + config.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false); + + return config; +} + +bool XrayServerConfig::hasEqualServerSettings(const XrayServerConfig& other) const +{ + return port == other.port && site == other.site; +} + +QJsonObject XrayClientConfig::toJson() const +{ + QJsonObject obj; + + if (!nativeConfig.isEmpty()) { + obj[configKey::config] = nativeConfig; + } + if (!localPort.isEmpty()) { + obj[configKey::localPort] = localPort; + } + if (!id.isEmpty()) { + obj[configKey::clientId] = id; + } + + return obj; +} + +XrayClientConfig XrayClientConfig::fromJson(const QJsonObject& json) +{ + XrayClientConfig config; + + config.nativeConfig = json.value(configKey::config).toString(); + config.localPort = json.value(configKey::localPort).toString(); + config.id = json.value(configKey::clientId).toString(); + + if (config.id.isEmpty() && !config.nativeConfig.isEmpty()) { + QJsonDocument doc = QJsonDocument::fromJson(config.nativeConfig.toUtf8()); + if (!doc.isNull() && doc.isObject()) { + QJsonObject configObj = doc.object(); + if (configObj.contains(protocols::xray::outbounds)) { + QJsonArray outbounds = configObj.value(protocols::xray::outbounds).toArray(); + if (!outbounds.isEmpty()) { + QJsonObject outbound = outbounds[0].toObject(); + if (outbound.contains(protocols::xray::settings)) { + QJsonObject settings = outbound[protocols::xray::settings].toObject(); + if (settings.contains(protocols::xray::vnext)) { + QJsonArray vnext = settings[protocols::xray::vnext].toArray(); + if (!vnext.isEmpty()) { + QJsonObject vnextObj = vnext[0].toObject(); + if (vnextObj.contains(protocols::xray::users)) { + QJsonArray users = vnextObj[protocols::xray::users].toArray(); + if (!users.isEmpty()) { + QJsonObject user = users[0].toObject(); + if (user.contains(protocols::xray::id)) { + config.id = user[protocols::xray::id].toString(); + } + } + } + } + } + } + } + } + } + } + + return config; +} + +QJsonObject XrayProtocolConfig::toJson() const +{ + QJsonObject obj = serverConfig.toJson(); + + if (clientConfig.has_value()) { + // Third-party import: nativeConfig is raw Xray JSON (inbounds/outbounds) + QJsonDocument doc = QJsonDocument::fromJson(clientConfig->nativeConfig.toUtf8()); + if (!doc.isNull() && doc.isObject() && doc.object().contains(protocols::xray::outbounds) + && !doc.object().contains(configKey::config)) { + obj[configKey::lastConfig] = clientConfig->nativeConfig; + } else { + QJsonObject clientJson = clientConfig->toJson(); + obj[configKey::lastConfig] = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact)); + } + } + + return obj; +} + +XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject& json) +{ + XrayProtocolConfig config; + + config.serverConfig = XrayServerConfig::fromJson(json); + + QString lastConfigStr = json.value(configKey::lastConfig).toString(); + if (!lastConfigStr.isEmpty()) { + QJsonDocument doc = QJsonDocument::fromJson(lastConfigStr.toUtf8()); + if (doc.isObject()) { + QJsonObject parsed = doc.object(); + // Third-party import stores raw Xray config (inbounds/outbounds) directly + if (parsed.contains(protocols::xray::outbounds) && !parsed.contains(configKey::config)) { + XrayClientConfig clientCfg; + clientCfg.nativeConfig = lastConfigStr; + if (parsed.contains(protocols::xray::inbounds)) { + QJsonArray inbounds = parsed.value(protocols::xray::inbounds).toArray(); + if (!inbounds.isEmpty()) { + QJsonObject inbound = inbounds[0].toObject(); + if (inbound.contains(protocols::xray::port)) { + clientCfg.localPort = QString::number(inbound.value(protocols::xray::port).toInt()); + } + } + } + config.clientConfig = clientCfg; + } else { + config.clientConfig = XrayClientConfig::fromJson(parsed); + } + } + } + + return config; +} + +bool XrayProtocolConfig::hasClientConfig() const +{ + return clientConfig.has_value(); +} + +void XrayProtocolConfig::setClientConfig(const XrayClientConfig& config) +{ + clientConfig = config; +} + +void XrayProtocolConfig::clearClientConfig() +{ + clientConfig.reset(); +} + +} // namespace amnezia + diff --git a/client/core/models/protocols/xrayProtocolConfig.h b/client/core/models/protocols/xrayProtocolConfig.h new file mode 100644 index 000000000..fc52a81b3 --- /dev/null +++ b/client/core/models/protocols/xrayProtocolConfig.h @@ -0,0 +1,48 @@ +#ifndef XRAYPROTOCOLCONFIG_H +#define XRAYPROTOCOLCONFIG_H + +#include +#include +#include + +namespace amnezia +{ + +struct XrayServerConfig { + QString port; + QString transportProto; + QString subnetAddress; + QString site; + bool isThirdPartyConfig = false; + + QJsonObject toJson() const; + static XrayServerConfig fromJson(const QJsonObject& json); + + bool hasEqualServerSettings(const XrayServerConfig& other) const; +}; + +struct XrayClientConfig { + QString nativeConfig; + QString localPort; + QString id; + + QJsonObject toJson() const; + static XrayClientConfig fromJson(const QJsonObject& json); +}; + +struct XrayProtocolConfig { + XrayServerConfig serverConfig; + std::optional clientConfig; + + QJsonObject toJson() const; + static XrayProtocolConfig fromJson(const QJsonObject& json); + + bool hasClientConfig() const; + void setClientConfig(const XrayClientConfig& config); + void clearClientConfig(); +}; + +} // namespace amnezia + +#endif // XRAYPROTOCOLCONFIG_H + diff --git a/client/core/models/selfhosted/nativeServerConfig.cpp b/client/core/models/selfhosted/nativeServerConfig.cpp new file mode 100644 index 000000000..34577ac29 --- /dev/null +++ b/client/core/models/selfhosted/nativeServerConfig.cpp @@ -0,0 +1,93 @@ +#include "nativeServerConfig.h" + +#include + +#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" + +namespace amnezia +{ + +using namespace ContainerEnumNS; + +bool NativeServerConfig::hasContainers() const +{ + return !containers.isEmpty(); +} + +ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) const +{ + if (!containers.contains(container)) { + return ContainerConfig{}; + } + return containers.value(container); +} + +QJsonObject NativeServerConfig::toJson() const +{ + QJsonObject obj; + + if (!description.isEmpty()) { + obj[configKey::description] = this->description; + } + if (!hostName.isEmpty()) { + obj[configKey::hostName] = hostName; + } + + QJsonArray containersArray; + for (auto it = containers.begin(); it != containers.end(); ++it) { + QJsonObject containerObj = it.value().toJson(); + containersArray.append(containerObj); + } + if (!containersArray.isEmpty()) { + obj[configKey::containers] = containersArray; + } + + if (defaultContainer != DockerContainer::None) { + obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer); + } + + if (!dns1.isEmpty()) { + obj[configKey::dns1] = dns1; + } + if (!dns2.isEmpty()) { + obj[configKey::dns2] = dns2; + } + + return obj; +} + +NativeServerConfig NativeServerConfig::fromJson(const QJsonObject& json) +{ + NativeServerConfig config; + + config.description = json.value(configKey::description).toString(); + config.hostName = json.value(configKey::hostName).toString(); + + QJsonArray containersArray = json.value(configKey::containers).toArray(); + for (const QJsonValue& val : containersArray) { + QJsonObject containerObj = val.toObject(); + ContainerConfig containerConfig = ContainerConfig::fromJson(containerObj); + + QString containerStr = containerObj.value(configKey::container).toString(); + DockerContainer container = ContainerUtils::containerFromString(containerStr); + + config.containers.insert(container, containerConfig); + } + + QString defaultContainerStr = json.value(configKey::defaultContainer).toString(); + config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr); + + config.dns1 = json.value(configKey::dns1).toString(); + config.dns2 = json.value(configKey::dns2).toString(); + + return config; +} + +} // namespace amnezia + diff --git a/client/core/models/selfhosted/nativeServerConfig.h b/client/core/models/selfhosted/nativeServerConfig.h new file mode 100644 index 000000000..13982eb8b --- /dev/null +++ b/client/core/models/selfhosted/nativeServerConfig.h @@ -0,0 +1,34 @@ +#ifndef NATIVESERVERCONFIG_H +#define NATIVESERVERCONFIG_H + +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/containerConfig.h" + +namespace amnezia +{ + +using namespace ContainerEnumNS; + +struct NativeServerConfig { + QString description; + QString hostName; + QMap containers; + DockerContainer defaultContainer; + QString dns1; + QString dns2; + + bool hasContainers() const; + ContainerConfig containerConfig(DockerContainer container) const; + QJsonObject toJson() const; + static NativeServerConfig fromJson(const QJsonObject& json); +}; + +} // namespace amnezia + +#endif // NATIVESERVERCONFIG_H + diff --git a/client/core/models/selfhosted/selfHostedServerConfig.cpp b/client/core/models/selfhosted/selfHostedServerConfig.cpp new file mode 100644 index 000000000..535350936 --- /dev/null +++ b/client/core/models/selfhosted/selfHostedServerConfig.cpp @@ -0,0 +1,140 @@ +#include "selfHostedServerConfig.h" + +#include +#include +#include + +#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" + +namespace amnezia +{ + +using namespace ContainerEnumNS; + +bool SelfHostedServerConfig::hasCredentials() const +{ + return userName.has_value() && password.has_value() && port.has_value(); +} + +bool SelfHostedServerConfig::isReadOnly() const +{ + return !hasCredentials(); +} + +std::optional SelfHostedServerConfig::credentials() const +{ + if (!hasCredentials()) { + return std::nullopt; + } + + ServerCredentials creds; + creds.hostName = hostName; + creds.userName = userName.value(); + creds.secretData = password.value(); + creds.port = port.value(); + + return creds; +} + +bool SelfHostedServerConfig::hasContainers() const +{ + return !containers.isEmpty(); +} + +ContainerConfig SelfHostedServerConfig::containerConfig(DockerContainer container) const +{ + if (!containers.contains(container)) { + return ContainerConfig{}; + } + return containers.value(container); +} + +QJsonObject SelfHostedServerConfig::toJson() const +{ + QJsonObject obj; + + if (!description.isEmpty()) { + obj[configKey::description] = this->description; + } + if (!hostName.isEmpty()) { + obj[configKey::hostName] = hostName; + } + + QJsonArray containersArray; + for (auto it = containers.begin(); it != containers.end(); ++it) { + QJsonObject containerObj = it.value().toJson(); + containersArray.append(containerObj); + } + if (!containersArray.isEmpty()) { + obj[configKey::containers] = containersArray; + } + + if (defaultContainer != DockerContainer::None) { + obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer); + } + + if (!dns1.isEmpty()) { + obj[configKey::dns1] = dns1; + } + if (!dns2.isEmpty()) { + obj[configKey::dns2] = dns2; + } + + if (userName.has_value()) { + obj[configKey::userName] = userName.value(); + } + if (password.has_value()) { + obj[configKey::password] = password.value(); + } + if (port.has_value()) { + obj[configKey::port] = port.value(); + } + + return obj; +} + +SelfHostedServerConfig SelfHostedServerConfig::fromJson(const QJsonObject& json) +{ + SelfHostedServerConfig config; + + config.description = json.value(configKey::description).toString(); + config.hostName = json.value(configKey::hostName).toString(); + + QJsonArray containersArray = json.value(configKey::containers).toArray(); + for (const QJsonValue& val : containersArray) { + QJsonObject containerObj = val.toObject(); + ContainerConfig containerConfig = ContainerConfig::fromJson(containerObj); + + QString containerStr = containerObj.value(configKey::container).toString(); + DockerContainer container = ContainerUtils::containerFromString(containerStr); + + config.containers.insert(container, containerConfig); + } + + QString defaultContainerStr = json.value(configKey::defaultContainer).toString(); + config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr); + + config.dns1 = json.value(configKey::dns1).toString(); + config.dns2 = json.value(configKey::dns2).toString(); + + if (json.contains(configKey::userName)) { + config.userName = json.value(configKey::userName).toString(); + } + if (json.contains(configKey::password)) { + config.password = json.value(configKey::password).toString(); + } + if (json.contains(configKey::port)) { + config.port = json.value(configKey::port).toInt(); + } + + return config; +} + +} // namespace amnezia + diff --git a/client/core/models/selfhosted/selfHostedServerConfig.h b/client/core/models/selfhosted/selfHostedServerConfig.h new file mode 100644 index 000000000..39dc88540 --- /dev/null +++ b/client/core/models/selfhosted/selfHostedServerConfig.h @@ -0,0 +1,45 @@ +#ifndef SELFHOSTEDSERVERCONFIG_H +#define SELFHOSTEDSERVERCONFIG_H + +#include +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/containerConfig.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" + +namespace amnezia +{ + +using namespace ContainerEnumNS; + +struct SelfHostedServerConfig { + QString description; + QString hostName; + QMap containers; + DockerContainer defaultContainer; + QString dns1; + QString dns2; + + std::optional userName; + std::optional password; + std::optional port; + + bool hasCredentials() const; + bool isReadOnly() const; + std::optional credentials() const; + bool hasContainers() const; + ContainerConfig containerConfig(DockerContainer container) const; + QJsonObject toJson() const; + static SelfHostedServerConfig fromJson(const QJsonObject& json); +}; + +} // namespace amnezia + +#endif // SELFHOSTEDSERVERCONFIG_H + diff --git a/client/core/models/serverConfig.cpp b/client/core/models/serverConfig.cpp new file mode 100644 index 000000000..7006fb07b --- /dev/null +++ b/client/core/models/serverConfig.cpp @@ -0,0 +1,234 @@ +#include "serverConfig.h" + +#include "core/utils/api/apiUtils.h" +#include "core/utils/networkUtilities.h" +#include "core/models/selfhosted/selfHostedServerConfig.h" +#include "core/models/selfhosted/nativeServerConfig.h" +#include "core/models/api/apiV1ServerConfig.h" +#include "core/models/api/apiV2ServerConfig.h" +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" + +namespace amnezia +{ + +using namespace ContainerEnumNS; + +QString ServerConfig::description() const +{ + return std::visit([](const auto& v) { return v.description; }, data); +} + +QString ServerConfig::hostName() const +{ + return std::visit([](const auto& v) { return v.hostName; }, data); +} + +QString ServerConfig::displayName() const +{ + if (isApiV1()) { + const auto *apiV1 = as(); + return apiV1 ? apiV1->name : description(); + } + if (isApiV2()) { + const auto *apiV2 = as(); + return apiV2 ? apiV2->name : description(); + } + QString name = description(); + return name.isEmpty() ? hostName() : name; +} + +QMap ServerConfig::containers() const +{ + return std::visit([](const auto& v) { return v.containers; }, data); +} + +DockerContainer ServerConfig::defaultContainer() const +{ + return std::visit([](const auto& v) { return v.defaultContainer; }, data); +} + +QString ServerConfig::dns1() const +{ + return std::visit([](const auto& v) { return v.dns1; }, data); +} + +QString ServerConfig::dns2() const +{ + return std::visit([](const auto& v) { return v.dns2; }, data); +} + +bool ServerConfig::hasContainers() const +{ + return std::visit([](const auto& v) { return v.hasContainers(); }, data); +} + +ContainerConfig ServerConfig::containerConfig(DockerContainer container) const +{ + return std::visit([container](const auto& v) { return v.containerConfig(container); }, data); +} + +int ServerConfig::crc() const +{ + return std::visit([](const auto& v) -> int { + using T = std::decay_t; + if constexpr (std::is_same_v || + std::is_same_v) { + return v.crc; + } + return 0; + }, data); +} + +int ServerConfig::configVersion() const +{ + return std::visit([](const auto& v) -> int { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return apiDefs::ConfigSource::Telegram; + } else if constexpr (std::is_same_v) { + return apiDefs::ConfigSource::AmneziaGateway; + } + return 0; // SelfHostedServerConfig or NativeServerConfig + }, data); +} + +bool ServerConfig::isSelfHosted() const +{ + return std::holds_alternative(data); +} + +bool ServerConfig::isNative() const +{ + return std::holds_alternative(data); +} + +bool ServerConfig::isApiV1() const +{ + return std::holds_alternative(data); +} + +bool ServerConfig::isApiV2() const +{ + return std::holds_alternative(data); +} + +bool ServerConfig::isApiConfig() const +{ + return isApiV1() || isApiV2(); +} + +QJsonObject ServerConfig::toJson() const +{ + return std::visit([](const auto& v) { return v.toJson(); }, data); +} + +ServerConfig ServerConfig::fromJson(const QJsonObject& json) +{ + apiDefs::ConfigType configType = apiUtils::getConfigType(json); + + switch (configType) { + case apiDefs::ConfigType::SelfHosted: { + bool hasThirdPartyConfig = false; + QJsonArray containersArray = json.value(configKey::containers).toArray(); + for (const QJsonValue& val : containersArray) { + QJsonObject containerObj = val.toObject(); + for (auto it = containerObj.begin(); it != containerObj.end(); ++it) { + QString key = it.key(); + if (key != configKey::container) { + QJsonObject protocolObj = it.value().toObject(); + if (protocolObj.contains(configKey::isThirdPartyConfig) && + protocolObj.value(configKey::isThirdPartyConfig).toBool()) { + hasThirdPartyConfig = true; + break; + } + } + } + if (hasThirdPartyConfig) { + break; + } + } + + if (hasThirdPartyConfig) { + return ServerConfig{NativeServerConfig::fromJson(json)}; + } else { + return ServerConfig{SelfHostedServerConfig::fromJson(json)}; + } + } + case apiDefs::ConfigType::AmneziaPremiumV1: + case apiDefs::ConfigType::AmneziaFreeV2: + return ServerConfig{ApiV1ServerConfig::fromJson(json)}; + case apiDefs::ConfigType::AmneziaPremiumV2: + case apiDefs::ConfigType::AmneziaFreeV3: + case apiDefs::ConfigType::ExternalPremium: + return ServerConfig{ApiV2ServerConfig::fromJson(json)}; + default: { + // Check if any container has isThirdPartyConfig + bool hasThirdPartyConfig = false; + QJsonArray containersArray = json.value(configKey::containers).toArray(); + for (const QJsonValue& val : containersArray) { + QJsonObject containerObj = val.toObject(); + // Check all protocol keys in the container object + for (auto it = containerObj.begin(); it != containerObj.end(); ++it) { + QString key = it.key(); + if (key != configKey::container) { + QJsonObject protocolObj = it.value().toObject(); + if (protocolObj.contains(configKey::isThirdPartyConfig) && + protocolObj.value(configKey::isThirdPartyConfig).toBool()) { + hasThirdPartyConfig = true; + break; + } + } + } + if (hasThirdPartyConfig) { + break; + } + } + + if (hasThirdPartyConfig) { + return ServerConfig{NativeServerConfig::fromJson(json)}; + } else { + return ServerConfig{SelfHostedServerConfig::fromJson(json)}; + } + } + } +} + +QPair ServerConfig::getDnsPair(bool isAmneziaDnsEnabled, + const QString &primaryDns, + const QString &secondaryDns) const +{ + QPair dns; + + QMap serverContainers = containers(); + + bool isDnsContainerInstalled = false; + for (auto it = serverContainers.begin(); it != serverContainers.end(); ++it) { + if (it.key() == DockerContainer::Dns) { + isDnsContainerInstalled = true; + break; + } + } + + dns.first = dns1(); + dns.second = dns2(); + + if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) { + if (isAmneziaDnsEnabled && isDnsContainerInstalled) { + dns.first = protocols::dns::amneziaDnsIp; + } else { + dns.first = primaryDns; + } + } + + if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) { + dns.second = secondaryDns; + } + + return dns; +} + +} // namespace amnezia + diff --git a/client/core/models/serverConfig.h b/client/core/models/serverConfig.h new file mode 100644 index 000000000..93ef1842d --- /dev/null +++ b/client/core/models/serverConfig.h @@ -0,0 +1,92 @@ +#ifndef SERVERCONFIG_H +#define SERVERCONFIG_H + +#include +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/selfhosted/selfHostedServerConfig.h" +#include "core/models/selfhosted/nativeServerConfig.h" +#include "core/models/api/apiV1ServerConfig.h" +#include "core/models/api/apiV2ServerConfig.h" +#include "core/models/containerConfig.h" + +namespace amnezia +{ + +using namespace ContainerEnumNS; + +struct ServerConfig { + using Variant = std::variant< + SelfHostedServerConfig, + NativeServerConfig, + ApiV1ServerConfig, + ApiV2ServerConfig + >; + + Variant data; + + ServerConfig() = default; + ServerConfig(const Variant& v) : data(v) {} + ServerConfig(Variant&& v) : data(std::move(v)) {} + + template>, ServerConfig>::value>> + ServerConfig(const T& v) : data(v) {} + + template>, ServerConfig>::value>> + ServerConfig(T&& v) : data(std::forward(v)) {} + + QString description() const; + QString hostName() const; + QString displayName() const; + QMap containers() const; + DockerContainer defaultContainer() const; + QString dns1() const; + QString dns2() const; + bool hasContainers() const; + ContainerConfig containerConfig(DockerContainer container) const; + + int crc() const; + int configVersion() const; + + bool isSelfHosted() const; + bool isNative() const; + bool isApiV1() const; + bool isApiV2() const; + bool isApiConfig() const; + + template + T* as() { + return std::get_if(&data); + } + + template + const T* as() const { + return std::get_if(&data); + } + + QJsonObject toJson() const; + static ServerConfig fromJson(const QJsonObject& json); + + template + auto visit(Visitor&& visitor) { + return std::visit(std::forward(visitor), data); + } + + template + auto visit(Visitor&& visitor) const { + return std::visit(std::forward(visitor), data); + } + + QPair getDnsPair(bool isAmneziaDnsEnabled, + const QString &primaryDns, + const QString &secondaryDns) const; +}; + +} // namespace amnezia + +#endif // SERVERCONFIG_H + diff --git a/client/protocols/android_vpnprotocol.cpp b/client/core/protocols/androidVpnProtocol.cpp similarity index 94% rename from client/protocols/android_vpnprotocol.cpp rename to client/core/protocols/androidVpnProtocol.cpp index 313a37499..05e2aefd0 100644 --- a/client/protocols/android_vpnprotocol.cpp +++ b/client/core/protocols/androidVpnProtocol.cpp @@ -1,4 +1,4 @@ -#include "android_vpnprotocol.h" +#include "androidVpnProtocol.h" #include "platforms/android/android_controller.h" diff --git a/client/protocols/android_vpnprotocol.h b/client/core/protocols/androidVpnProtocol.h similarity index 94% rename from client/protocols/android_vpnprotocol.h rename to client/core/protocols/androidVpnProtocol.h index fcf85de79..6552e5a81 100644 --- a/client/protocols/android_vpnprotocol.h +++ b/client/core/protocols/androidVpnProtocol.h @@ -1,7 +1,7 @@ #ifndef ANDROID_VPNPROTOCOL_H #define ANDROID_VPNPROTOCOL_H -#include "vpnprotocol.h" +#include "vpnProtocol.h" using namespace amnezia; diff --git a/client/protocols/awgprotocol.cpp b/client/core/protocols/awgProtocol.cpp similarity index 83% rename from client/protocols/awgprotocol.cpp rename to client/core/protocols/awgProtocol.cpp index 64415dbee..8f7460fbb 100644 --- a/client/protocols/awgprotocol.cpp +++ b/client/core/protocols/awgProtocol.cpp @@ -1,4 +1,4 @@ -#include "awgprotocol.h" +#include "awgProtocol.h" Awg::Awg(const QJsonObject &configuration, QObject *parent) : WireguardProtocol(configuration, parent) diff --git a/client/protocols/awgprotocol.h b/client/core/protocols/awgProtocol.h similarity index 89% rename from client/protocols/awgprotocol.h rename to client/core/protocols/awgProtocol.h index d7fc9c920..537740d30 100644 --- a/client/protocols/awgprotocol.h +++ b/client/core/protocols/awgProtocol.h @@ -3,7 +3,7 @@ #include -#include "wireguardprotocol.h" +#include "wireGuardProtocol.h" class Awg : public WireguardProtocol { diff --git a/client/protocols/ikev2_vpn_protocol_windows.cpp b/client/core/protocols/ikev2VpnProtocolWindows.cpp similarity index 96% rename from client/protocols/ikev2_vpn_protocol_windows.cpp rename to client/core/protocols/ikev2VpnProtocolWindows.cpp index 030e29cdf..94df48334 100644 --- a/client/protocols/ikev2_vpn_protocol_windows.cpp +++ b/client/core/protocols/ikev2VpnProtocolWindows.cpp @@ -8,8 +8,9 @@ #include "ipc.h" #include "logger.h" -#include "ikev2_vpn_protocol_windows.h" -#include "utilities.h" +#include "ikev2VpnProtocolWindows.h" +#include "core/utils/utilities.h" +#include "core/protocols/protocolUtils.h" static Ikev2Protocol* self = nullptr; @@ -172,12 +173,12 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) { - m_config = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject(); + m_config = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Ikev2)).toObject(); } ErrorCode Ikev2Protocol::start() { - QByteArray cert = QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()); + QByteArray cert = QByteArray::fromBase64(m_config[configKey::cert].toString().toUtf8()); setConnectionState(Vpn::ConnectionState::Connecting); QTemporaryFile * certFile = new QTemporaryFile; @@ -204,7 +205,7 @@ ErrorCode Ikev2Protocol::start() } certInstallProcess->setProgram(PermittedProcess::CertUtil); - QStringList arguments({"-f", "-importpfx", "-p", m_config[config_key::password].toString(), + QStringList arguments({"-f", "-importpfx", "-p", m_config[configKey::password].toString(), QDir::toNativeSeparators(m_filename), "NoExport" }); @@ -223,7 +224,7 @@ ErrorCode Ikev2Protocol::start() { { - if ( !create_new_vpn(tunnelName(), m_config[config_key::hostName].toString())){ + if ( !create_new_vpn(tunnelName(), m_config[configKey::hostName].toString())){ qDebug() <<"Can't create the VPN connect"; } } diff --git a/client/protocols/ikev2_vpn_protocol_windows.h b/client/core/protocols/ikev2VpnProtocolWindows.h similarity index 96% rename from client/protocols/ikev2_vpn_protocol_windows.h rename to client/core/protocols/ikev2VpnProtocolWindows.h index 179427350..f9a68e31d 100644 --- a/client/protocols/ikev2_vpn_protocol_windows.h +++ b/client/core/protocols/ikev2VpnProtocolWindows.h @@ -7,8 +7,8 @@ #include #include -#include "vpnprotocol.h" -#include "core/ipcclient.h" +#include "vpnProtocol.h" +#include "core/utils/ipcClient.h" #include #include diff --git a/client/protocols/openvpnprotocol.cpp b/client/core/protocols/openVpnProtocol.cpp similarity index 94% rename from client/protocols/openvpnprotocol.cpp rename to client/core/protocols/openVpnProtocol.cpp index 20a3416c1..660846e51 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/core/protocols/openVpnProtocol.cpp @@ -6,10 +6,11 @@ #include #include -#include "core/networkUtilities.h" +#include "core/utils/networkUtilities.h" #include "ipc.h" -#include "openvpnprotocol.h" -#include "utilities.h" +#include "openVpnProtocol.h" +#include "core/utils/utilities.h" +#include "core/protocols/protocolUtils.h" #include "version.h" OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent) @@ -98,12 +99,12 @@ void OpenVpnProtocol::killOpenVpnProcess() void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration) { - if (configuration.contains(ProtocolProps::key_proto_config_data(Proto::OpenVpn))) { + if (configuration.contains(ProtocolUtils::key_proto_config_data(Proto::OpenVpn))) { m_configData = configuration; - QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::OpenVpn)).toObject(); + QJsonObject jConfig = configuration.value(ProtocolUtils::key_proto_config_data(Proto::OpenVpn)).toObject(); m_configFile.open(); - m_configFile.write(jConfig.value(config_key::config).toString().toUtf8()); + m_configFile.write(jConfig.value(configKey::config).toString().toUtf8()); m_configFile.close(); m_configFileName = m_configFile.fileName(); qDebug().noquote() << QString("Set config data") << m_configFileName; @@ -181,7 +182,7 @@ ErrorCode OpenVpnProtocol::start() #ifdef AMNEZIA_DESKTOP const ErrorCode res = IpcClient::withInterface([&](QSharedPointer iface) { - QString ip = NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString()); + QString ip = NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString()); QRemoteObjectPendingReply reply = iface->addKillSwitchAllowedRange(QStringList(ip)); if (!reply.waitForFinished(1000) || !reply.returnValue()) { return ErrorCode::AmneziaServiceConnectionFailed; @@ -354,13 +355,13 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line) { // killSwitch toggle if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { - if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { + if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) { iface->enableKillSwitch(m_configData, netInterfaces.at(i).index()); } m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index()); m_configData.insert("vpnGateway", m_vpnGateway); m_configData.insert("vpnServer", - NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString())); + NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString())); iface->enablePeerTraffic(m_configData); } } @@ -369,9 +370,9 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line) #endif #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) // killSwitch toggle - if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { + if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) { m_configData.insert("vpnServer", - NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString())); + NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString())); IpcClient::withInterface([&](QSharedPointer iface) { QRemoteObjectPendingReply reply = iface->enableKillSwitch(m_configData, 0); if (!reply.waitForFinished(1000) || !reply.returnValue()) { diff --git a/client/protocols/openvpnprotocol.h b/client/core/protocols/openVpnProtocol.h similarity index 93% rename from client/protocols/openvpnprotocol.h rename to client/core/protocols/openVpnProtocol.h index 490fff832..45116d0e7 100644 --- a/client/protocols/openvpnprotocol.h +++ b/client/core/protocols/openVpnProtocol.h @@ -5,10 +5,10 @@ #include #include -#include "managementserver.h" -#include "vpnprotocol.h" +#include "core/utils/managementServer.h" +#include "vpnProtocol.h" -#include "core/ipcclient.h" +#include "core/utils/ipcClient.h" class OpenVpnProtocol : public VpnProtocol { diff --git a/client/protocols/protocols_defs.cpp b/client/core/protocols/protocolUtils.cpp similarity index 66% rename from client/protocols/protocols_defs.cpp rename to client/core/protocols/protocolUtils.cpp index dce8fede4..65446e0ec 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/core/protocols/protocolUtils.cpp @@ -1,40 +1,12 @@ -#include "protocols_defs.h" +#include "protocolUtils.h" #include #include +#include using namespace amnezia; -QDebug operator<<(QDebug debug, const amnezia::ProtocolEnumNS::Proto &p) -{ - QDebugStateSaver saver(debug); - debug.nospace() << ProtocolProps::protoToString(p); - - return debug; -} - -amnezia::Proto ProtocolProps::protoFromString(QString proto) -{ - QMetaEnum metaEnum = QMetaEnum::fromType(); - for (int i = 0; i < metaEnum.keyCount(); ++i) { - Proto p = static_cast(i); - if (proto == protoToString(p)) - return p; - } - return Proto::Any; -} - -QString ProtocolProps::protoToString(amnezia::Proto p) -{ - if (p == Proto::Any) - return ""; - - QMetaEnum metaEnum = QMetaEnum::fromType(); - QString protoKey = metaEnum.valueToKey(static_cast(p)); - return protoKey.toLower(); -} - -QList ProtocolProps::allProtocols() +QList ProtocolUtils::allProtocols() { QMetaEnum metaEnum = QMetaEnum::fromType(); QList all; @@ -45,7 +17,7 @@ QList ProtocolProps::allProtocols() return all; } -TransportProto ProtocolProps::transportProtoFromString(QString p) +TransportProto ProtocolUtils::transportProtoFromString(QString p) { QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { @@ -56,46 +28,61 @@ TransportProto ProtocolProps::transportProtoFromString(QString p) return TransportProto::Udp; } -QString ProtocolProps::transportProtoToString(TransportProto proto, Proto p) +QString ProtocolUtils::transportProtoToString(TransportProto proto, Proto p) { QMetaEnum metaEnum = QMetaEnum::fromType(); QString protoKey = metaEnum.valueToKey(static_cast(proto)); return protoKey.toLower(); } -QMap ProtocolProps::protocolHumanNames() +Proto ProtocolUtils::protoFromString(QString proto) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < metaEnum.keyCount(); ++i) { + Proto p = static_cast(i); + if (proto == protoToString(p)) + return p; + } + return Proto::Unknown; +} + +QString ProtocolUtils::protoToString(Proto p) +{ + if (p == Proto::Unknown) + return ""; + + QMetaEnum metaEnum = QMetaEnum::fromType(); + QString protoKey = metaEnum.valueToKey(static_cast(p)); + return protoKey.toLower(); +} + +QMap ProtocolUtils::protocolHumanNames() { return { { Proto::OpenVpn, "OpenVPN" }, - { Proto::ShadowSocks, "Shadowsocks" }, - { Proto::Cloak, "Cloak" }, { Proto::WireGuard, "WireGuard" }, { Proto::Awg, "AmneziaWG" }, { Proto::Ikev2, "IKEv2" }, - { Proto::L2tp, "L2TP" }, { Proto::Xray, "XRay" }, { Proto::SSXray, "Shadowsocks"}, - { Proto::TorWebSite, "Website in Tor network" }, { Proto::Dns, "DNS Service" }, { Proto::Sftp, QObject::tr("SFTP service") }, { Proto::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } }; } -QMap ProtocolProps::protocolDescriptions() +QMap ProtocolUtils::protocolDescriptions() { return {}; } -amnezia::ServiceType ProtocolProps::protocolService(Proto p) +ServiceType ProtocolUtils::protocolService(Proto p) { switch (p) { - case Proto::Any: return ServiceType::None; + case Proto::Unknown: return ServiceType::None; case Proto::SSXray: return ServiceType::None; case Proto::OpenVpn: return ServiceType::Vpn; - case Proto::Cloak: return ServiceType::Vpn; - case Proto::ShadowSocks: return ServiceType::Vpn; case Proto::WireGuard: return ServiceType::Vpn; case Proto::Awg: return ServiceType::Vpn; case Proto::Ikev2: return ServiceType::Vpn; @@ -109,12 +96,11 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p) } } -int ProtocolProps::getPortForInstall(Proto p) +int ProtocolUtils::getPortForInstall(Proto p) { switch (p) { case Awg: case WireGuard: - case ShadowSocks: case OpenVpn: case Socks5Proxy: return QRandomGenerator::global()->bounded(30000, 50000); @@ -123,18 +109,15 @@ int ProtocolProps::getPortForInstall(Proto p) } } -int ProtocolProps::defaultPort(Proto p) +int ProtocolUtils::defaultPort(Proto p) { switch (p) { - case Proto::Any: return -1; + case Proto::Unknown: return -1; case Proto::OpenVpn: return QString(protocols::openvpn::defaultPort).toInt(); - case Proto::Cloak: return QString(protocols::cloak::defaultPort).toInt(); - case Proto::ShadowSocks: return QString(protocols::shadowsocks::defaultPort).toInt(); case Proto::WireGuard: return QString(protocols::wireguard::defaultPort).toInt(); case Proto::Awg: return QString(protocols::awg::defaultPort).toInt(); case Proto::Xray: return QString(protocols::xray::defaultPort).toInt(); case Proto::Ikev2: return -1; - case Proto::L2tp: return -1; case Proto::TorWebSite: return -1; case Proto::Dns: return 53; @@ -144,17 +127,14 @@ int ProtocolProps::defaultPort(Proto p) } } -bool ProtocolProps::defaultPortChangeable(Proto p) +bool ProtocolUtils::defaultPortChangeable(Proto p) { switch (p) { - case Proto::Any: return false; + case Proto::Unknown: return false; case Proto::OpenVpn: return true; - case Proto::Cloak: return true; - case Proto::ShadowSocks: return true; case Proto::WireGuard: return true; case Proto::Awg: return true; case Proto::Ikev2: return false; - case Proto::L2tp: return false; case Proto::Xray: return true; case Proto::TorWebSite: return false; @@ -165,38 +145,34 @@ bool ProtocolProps::defaultPortChangeable(Proto p) } } -TransportProto ProtocolProps::defaultTransportProto(Proto p) +TransportProto ProtocolUtils::defaultTransportProto(Proto p) { switch (p) { - case Proto::Any: return TransportProto::Udp; + case Proto::Unknown: return TransportProto::Udp; case Proto::OpenVpn: return TransportProto::Udp; - case Proto::Cloak: return TransportProto::Tcp; - case Proto::ShadowSocks: return TransportProto::TcpAndUdp; case Proto::WireGuard: return TransportProto::Udp; case Proto::Awg: return TransportProto::Udp; case Proto::Ikev2: return TransportProto::Udp; - case Proto::L2tp: return TransportProto::Udp; case Proto::Xray: return TransportProto::Tcp; + case Proto::SSXray: return TransportProto::Tcp; // non-vpn case Proto::TorWebSite: return TransportProto::Tcp; case Proto::Dns: return TransportProto::Udp; case Proto::Sftp: return TransportProto::Tcp; case Proto::Socks5Proxy: return TransportProto::Tcp; + default: return TransportProto::Udp; } } -bool ProtocolProps::defaultTransportProtoChangeable(Proto p) +bool ProtocolUtils::defaultTransportProtoChangeable(Proto p) { switch (p) { - case Proto::Any: return false; + case Proto::Unknown: return false; case Proto::OpenVpn: return true; - case Proto::Cloak: return false; - case Proto::ShadowSocks: return false; case Proto::WireGuard: return false; case Proto::Awg: return false; case Proto::Ikev2: return false; - case Proto::L2tp: return false; case Proto::Xray: return false; // non-vpn @@ -209,22 +185,22 @@ bool ProtocolProps::defaultTransportProtoChangeable(Proto p) return false; } -QString ProtocolProps::key_proto_config_data(Proto p) +QString ProtocolUtils::key_proto_config_data(Proto p) { return protoToString(p) + "_config_data"; } -QString ProtocolProps::key_proto_config_path(Proto p) +QString ProtocolUtils::key_proto_config_path(Proto p) { return protoToString(p) + "_config_path"; } -QString ProtocolProps::getProtocolVersion(const QJsonObject &protocolConfig) +QString ProtocolUtils::getProtocolVersion(const QJsonObject &protocolConfig) { - return protocolConfig.value(config_key::protocolVersion).toString(); + return protocolConfig.value(configKey::protocolVersion).toString(); } -QString ProtocolProps::getProtocolVersionString(const QJsonObject &protocolConfig) +QString ProtocolUtils::getProtocolVersionString(const QJsonObject &protocolConfig) { auto version = getProtocolVersion(protocolConfig); @@ -232,3 +208,4 @@ QString ProtocolProps::getProtocolVersionString(const QJsonObject &protocolConfi if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)"); return ""; } + diff --git a/client/core/protocols/protocolUtils.h b/client/core/protocols/protocolUtils.h new file mode 100644 index 000000000..d8019e81e --- /dev/null +++ b/client/core/protocols/protocolUtils.h @@ -0,0 +1,49 @@ +#ifndef PROTOCOLUTILS_H +#define PROTOCOLUTILS_H + +#include +#include +#include +#include + +#include "core/utils/protocolEnum.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" + +namespace amnezia +{ + namespace ProtocolUtils + { + QList allProtocols(); + + // spelling may differ for various protocols - TCP for OpenVPN, tcp for others + TransportProto transportProtoFromString(QString p); + QString transportProtoToString(TransportProto proto, Proto p = Proto::Unknown); + + Proto protoFromString(QString p); + QString protoToString(Proto p); + + QMap protocolHumanNames(); + QMap protocolDescriptions(); + + ServiceType protocolService(Proto p); + + int getPortForInstall(Proto p); + + int defaultPort(Proto p); + bool defaultPortChangeable(Proto p); + + TransportProto defaultTransportProto(Proto p); + bool defaultTransportProtoChangeable(Proto p); + + QString key_proto_config_data(Proto p); + QString key_proto_config_path(Proto p); + + QString getProtocolVersion(const QJsonObject &protocolConfig); + QString getProtocolVersionString(const QJsonObject &protocolConfig); + } +} + +#endif // PROTOCOLUTILS_H + + diff --git a/client/protocols/qml_register_protocols.h b/client/core/protocols/qmlRegisterProtocols.h similarity index 90% rename from client/protocols/qml_register_protocols.h rename to client/core/protocols/qmlRegisterProtocols.h index 30b006ae5..6071e48db 100644 --- a/client/protocols/qml_register_protocols.h +++ b/client/core/protocols/qmlRegisterProtocols.h @@ -1,7 +1,7 @@ #ifndef QML_REGISTER_PROTOCOLS_H #define QML_REGISTER_PROTOCOLS_H -#include "protocols_defs.h" +#include "core/utils/protocolEnum.h" #include #include @@ -39,6 +39,4 @@ void declareQmlProtocolEnum() { } // namespace amnezia -QDebug operator<<(QDebug debug, const amnezia::Proto &p); - #endif // QML_REGISTER_PROTOCOLS_H diff --git a/client/protocols/vpnprotocol.cpp b/client/core/protocols/vpnProtocol.cpp similarity index 90% rename from client/protocols/vpnprotocol.cpp rename to client/core/protocols/vpnProtocol.cpp index 318b5d92b..82d8b6bef 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/core/protocols/vpnProtocol.cpp @@ -1,19 +1,17 @@ #include #include -#include "core/errorstrings.h" -#include "vpnprotocol.h" +#include "core/utils/errorStrings.h" +#include "vpnProtocol.h" #if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) - #include "openvpnovercloakprotocol.h" - #include "openvpnprotocol.h" - #include "shadowsocksvpnprotocol.h" - #include "wireguardprotocol.h" - #include "xrayprotocol.h" + #include "openVpnProtocol.h" + #include "wireGuardProtocol.h" + #include "xrayProtocol.h" #endif #ifdef Q_OS_WINDOWS - #include "ikev2_vpn_protocol_windows.h" + #include "ikev2VpnProtocolWindows.h" #endif VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent) @@ -116,8 +114,6 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject & #endif #if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration); - case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration); - case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration); case DockerContainer::WireGuard: return new WireguardProtocol(configuration); case DockerContainer::Awg2: return new WireguardProtocol(configuration); case DockerContainer::Awg: return new WireguardProtocol(configuration); diff --git a/client/protocols/vpnprotocol.h b/client/core/protocols/vpnProtocol.h similarity index 90% rename from client/protocols/vpnprotocol.h rename to client/core/protocols/vpnProtocol.h index 2ef9e6806..68a79ec78 100644 --- a/client/protocols/vpnprotocol.h +++ b/client/core/protocols/vpnProtocol.h @@ -4,9 +4,14 @@ #include #include #include +#include -#include "core/defs.h" -#include "containers/containers_defs.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" using namespace amnezia; diff --git a/client/protocols/wireguardprotocol.cpp b/client/core/protocols/wireGuardProtocol.cpp similarity index 88% rename from client/protocols/wireguardprotocol.cpp rename to client/core/protocols/wireGuardProtocol.cpp index 2ae7ebcba..c401147c9 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/core/protocols/wireGuardProtocol.cpp @@ -4,8 +4,8 @@ #include #include -#include "wireguardprotocol.h" -#include "core/networkUtilities.h" +#include "wireGuardProtocol.h" +#include "core/utils/networkUtilities.h" #include "mozilla/localsocketcontroller.h" @@ -58,9 +58,9 @@ ErrorCode WireguardProtocol::startMzImpl() { QString protocolName = m_rawConfig.value("protocol").toString(); QJsonObject vpnConfigData = m_rawConfig.value(protocolName + "_config_data").toObject(); - vpnConfigData[config_key::hostName] = NetworkUtilities::getIPAddress(vpnConfigData.value(config_key::hostName).toString()); + vpnConfigData[configKey::hostName] = NetworkUtilities::getIPAddress(vpnConfigData.value(configKey::hostName).toString()); m_rawConfig.insert(protocolName + "_config_data", vpnConfigData); - m_rawConfig[config_key::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[config_key::hostName].toString()); + m_rawConfig[configKey::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[configKey::hostName].toString()); m_impl->activate(m_rawConfig); return ErrorCode::NoError; diff --git a/client/protocols/wireguardprotocol.h b/client/core/protocols/wireGuardProtocol.h similarity index 96% rename from client/protocols/wireguardprotocol.h rename to client/core/protocols/wireGuardProtocol.h index 6d1a05187..a6c502c56 100644 --- a/client/protocols/wireguardprotocol.h +++ b/client/core/protocols/wireGuardProtocol.h @@ -7,7 +7,7 @@ #include #include -#include "vpnprotocol.h" +#include "vpnProtocol.h" #include "mozilla/controllerimpl.h" diff --git a/client/protocols/xrayprotocol.cpp b/client/core/protocols/xrayProtocol.cpp similarity index 90% rename from client/protocols/xrayprotocol.cpp rename to client/core/protocols/xrayProtocol.cpp index 016501ad4..d3d483334 100755 --- a/client/protocols/xrayprotocol.cpp +++ b/client/core/protocols/xrayProtocol.cpp @@ -1,13 +1,13 @@ -#include "xrayprotocol.h" +#include "xrayProtocol.h" -#include "core/ipcclient.h" -#include "core/serialization/serialization.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/ipcClient.h" +#include "core/utils/networkUtilities.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/serialization/serialization.h" #include "ipc.h" -#include "utilities.h" -#include "core/networkUtilities.h" #include -#include #include #include #include @@ -29,19 +29,19 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr; m_routeGateway = NetworkUtilities::getGatewayAndIface().first; - m_routeMode = static_cast(configuration.value(amnezia::config_key::splitTunnelType).toInt()); - m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::config_key::hostName).toString()); + m_routeMode = static_cast(configuration.value(amnezia::configKey::splitTunnelType).toInt()); + m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::configKey::hostName).toString()); - const QString primaryDns = configuration.value(amnezia::config_key::dns1).toString(); + const QString primaryDns = configuration.value(amnezia::configKey::dns1).toString(); m_dnsServers.push_back(QHostAddress(primaryDns)); if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) { - const QString secondaryDns = configuration.value(amnezia::config_key::dns2).toString(); + const QString secondaryDns = configuration.value(amnezia::configKey::dns2).toString(); m_dnsServers.push_back(QHostAddress(secondaryDns)); } - QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject(); + QJsonObject xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Xray)).toObject(); if (xrayConfiguration.isEmpty()) { - xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject(); + xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::SSXray)).toObject(); } m_xrayConfig = xrayConfiguration; } @@ -69,8 +69,14 @@ ErrorCode XrayProtocol::start() m_socksPassword = creds.password; m_socksPort = creds.port; + const QString xrayConfigStr = QJsonDocument(m_xrayConfig).toJson(QJsonDocument::Compact); + if (xrayConfigStr.isEmpty()) { + qCritical() << "Xray config is empty"; + return ErrorCode::XrayExecutableCrashed; + } + return IpcClient::withInterface([&](QSharedPointer iface) { - auto xrayStart = iface->xrayStart(QJsonDocument(m_xrayConfig).toJson()); + auto xrayStart = iface->xrayStart(xrayConfigStr); if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) { qCritical() << "Failed to start xray"; return ErrorCode::XrayExecutableCrashed; @@ -210,7 +216,7 @@ ErrorCode XrayProtocol::setupRouting() { #else static const int vpnAdapterIndex = 0; #endif - const bool killSwitchEnabled = QVariant(m_rawConfig.value(config_key::killSwitchOption).toString()).toBool(); + const bool killSwitchEnabled = QVariant(m_rawConfig.value(configKey::killSwitchOption).toString()).toBool(); if (killSwitchEnabled) { if (vpnAdapterIndex != -1) { QJsonObject config = m_rawConfig; @@ -225,7 +231,7 @@ ErrorCode XrayProtocol::setupRouting() { qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled"; } - if (m_routeMode == Settings::RouteMode::VpnAllSites) { + if (m_routeMode == amnezia::RouteMode::VpnAllSites) { static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" }; auto routeAddList = iface->routeAddList(m_vpnGateway, subnets); diff --git a/client/protocols/xrayprotocol.h b/client/core/protocols/xrayProtocol.h similarity index 73% rename from client/protocols/xrayprotocol.h rename to client/core/protocols/xrayProtocol.h index 0abd237eb..e831ab2f4 100644 --- a/client/protocols/xrayprotocol.h +++ b/client/core/protocols/xrayProtocol.h @@ -2,11 +2,15 @@ #define XRAYPROTOCOL_H #include "QProcess" - -#include "core/ipcclient.h" -#include "vpnprotocol.h" -#include "settings.h" #include +#include +#include + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/utils/ipcClient.h" +#include "vpnProtocol.h" class XrayProtocol : public VpnProtocol { @@ -22,7 +26,7 @@ private: ErrorCode startTun2Socks(); QJsonObject m_xrayConfig; - Settings::RouteMode m_routeMode; + amnezia::RouteMode m_routeMode; QList m_dnsServers; QString m_remoteAddress; diff --git a/client/core/repositories/secureAppSettingsRepository.cpp b/client/core/repositories/secureAppSettingsRepository.cpp new file mode 100644 index 000000000..3bc579c74 --- /dev/null +++ b/client/core/repositories/secureAppSettingsRepository.cpp @@ -0,0 +1,453 @@ +#include "secureAppSettingsRepository.h" + +#include +#include +#include + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" +#include "core/models/serverConfig.h" +#include "core/utils/networkUtilities.h" + +using namespace amnezia; + +namespace { + constexpr char gatewayEndpoint[] = "http://gw.amnezia.org:80/"; +} + +SecureAppSettingsRepository::SecureAppSettingsRepository(SecureQSettings* settings, QObject *parent) + : QObject(parent), m_settings(settings) +{ + QString storedEndpoint = value("Conf/gatewayEndpoint", gatewayEndpoint).toString(); + m_gatewayEndpoint = storedEndpoint.isEmpty() ? gatewayEndpoint : storedEndpoint; +} + +QVariant SecureAppSettingsRepository::value(const QString &key, const QVariant &defaultValue) const +{ + return m_settings->value(key, defaultValue); +} + +void SecureAppSettingsRepository::setValue(const QString &key, const QVariant &value) +{ + m_settings->setValue(key, value); +} + +QLocale SecureAppSettingsRepository::getAppLanguage() const +{ + QString localeStr = value("Conf/appLanguage", QLocale::system().name()).toString(); + return QLocale(localeStr); +} + +void SecureAppSettingsRepository::setAppLanguage(QLocale locale) +{ + setValue("Conf/appLanguage", locale.name()); + emit appLanguageChanged(locale); +} + +bool SecureAppSettingsRepository::useAmneziaDns() const +{ + return value("Conf/useAmneziaDns", true).toBool(); +} + +void SecureAppSettingsRepository::setUseAmneziaDns(bool enabled) +{ + setValue("Conf/useAmneziaDns", enabled); + emit useAmneziaDnsChanged(enabled); +} + +QStringList SecureAppSettingsRepository::getAllowedDnsServers() const +{ + return value("Conf/allowedDnsServers").toStringList(); +} + +void SecureAppSettingsRepository::setAllowedDnsServers(const QStringList &servers) +{ + setValue("Conf/allowedDnsServers", servers); + emit allowedDnsServersChanged(servers); +} + +QString SecureAppSettingsRepository::primaryDns() const +{ + constexpr char cloudFlareNs1[] = "1.1.1.1"; + return value("Conf/primaryDns", cloudFlareNs1).toString(); +} + +void SecureAppSettingsRepository::setPrimaryDns(const QString &dns) +{ + setValue("Conf/primaryDns", dns); +} + +QString SecureAppSettingsRepository::secondaryDns() const +{ + constexpr char cloudFlareNs2[] = "1.0.0.1"; + return value("Conf/secondaryDns", cloudFlareNs2).toString(); +} + +void SecureAppSettingsRepository::setSecondaryDns(const QString &dns) +{ + setValue("Conf/secondaryDns", dns); +} + +namespace { + QString routeModeString(RouteMode mode) { + switch (mode) { + case RouteMode::VpnAllSites: return "AllSites"; + case RouteMode::VpnOnlyForwardSites: return "ForwardSites"; + case RouteMode::VpnAllExceptSites: return "ExceptSites"; + } + return QString(); + } +} + +RouteMode SecureAppSettingsRepository::routeMode() const +{ + return static_cast(value("Conf/routeMode", 0).toInt()); +} + +void SecureAppSettingsRepository::setRouteMode(RouteMode mode) +{ + setValue("Conf/routeMode", static_cast(mode)); + emit routeModeChanged(mode); +} + +QVariantMap SecureAppSettingsRepository::vpnSites(RouteMode mode) const +{ + return value("Conf/" + routeModeString(mode)).toMap(); +} + +void SecureAppSettingsRepository::setVpnSites(RouteMode mode, const QVariantMap &sites) +{ + setValue("Conf/" + routeModeString(mode), sites); +} + +bool SecureAppSettingsRepository::addVpnSite(RouteMode mode, const QString &site, const QString &ip) +{ + QVariantMap sites = vpnSites(mode); + if (sites.contains(site) && ip.isEmpty()) + return false; + + sites.insert(site, ip); + setVpnSites(mode, sites); + emit sitesChanged(mode); + return true; +} + +void SecureAppSettingsRepository::addVpnSites(RouteMode mode, const QMap &sites) +{ + QVariantMap allSites = vpnSites(mode); + for (auto i = sites.constBegin(); i != sites.constEnd(); ++i) { + const QString &site = i.key(); + const QString &ip = i.value(); + + if (allSites.contains(site) && allSites.value(site) == ip) + continue; + + allSites.insert(site, ip); + } + + setVpnSites(mode, allSites); + emit sitesChanged(mode); +} + +void SecureAppSettingsRepository::removeVpnSite(RouteMode mode, const QString &site) +{ + QVariantMap sites = vpnSites(mode); + if (!sites.contains(site)) + return; + + sites.remove(site); + setVpnSites(mode, sites); + emit sitesChanged(mode); +} + +void SecureAppSettingsRepository::removeAllVpnSites(RouteMode mode) +{ + setVpnSites(mode, QVariantMap()); + emit sitesChanged(mode); +} + +bool SecureAppSettingsRepository::isSitesSplitTunnelingEnabled() const +{ + return value("Conf/sitesSplitTunnelingEnabled", false).toBool(); +} + +void SecureAppSettingsRepository::setSitesSplitTunnelingEnabled(bool enabled) +{ + setValue("Conf/sitesSplitTunnelingEnabled", enabled); + emit sitesSplitTunnelingEnabledChanged(enabled); +} + +namespace { + QString appsRouteModeString(AppsRouteMode mode) { + switch (mode) { + case AppsRouteMode::VpnAllApps: return "AllApps"; + case AppsRouteMode::VpnOnlyForwardApps: return "ForwardApps"; + case AppsRouteMode::VpnAllExceptApps: return "ExceptApps"; + } + return QString(); + } +} + +AppsRouteMode SecureAppSettingsRepository::appsRouteMode() const +{ + return static_cast(value("Conf/appsRouteMode", 0).toInt()); +} + +void SecureAppSettingsRepository::setAppsRouteMode(AppsRouteMode mode) +{ + setValue("Conf/appsRouteMode", static_cast(mode)); + emit appsRouteModeChanged(mode); +} + +QVector SecureAppSettingsRepository::vpnApps(AppsRouteMode mode) const +{ + QVector apps; + auto appsArray = value("Conf/" + appsRouteModeString(mode)).toJsonArray(); + for (const auto &app : appsArray) { + InstalledAppInfo appInfo; + appInfo.appName = app.toObject().value("appName").toString(); + appInfo.packageName = app.toObject().value("packageName").toString(); + appInfo.appPath = app.toObject().value("appPath").toString(); + + apps.push_back(appInfo); + } + return apps; +} + +void SecureAppSettingsRepository::setVpnApps(AppsRouteMode mode, const QVector &apps) +{ + QJsonArray appsArray; + for (const auto &app : apps) { + QJsonObject appInfo; + appInfo.insert("appName", app.appName); + appInfo.insert("packageName", app.packageName); + appInfo.insert("appPath", app.appPath); + appsArray.push_back(appInfo); + } + setValue("Conf/" + appsRouteModeString(mode), appsArray); + emit appsChanged(mode); +} + +bool SecureAppSettingsRepository::isAppsSplitTunnelingEnabled() const +{ + return value("Conf/appsSplitTunnelingEnabled", false).toBool(); +} + +void SecureAppSettingsRepository::setAppsSplitTunnelingEnabled(bool enabled) +{ + setValue("Conf/appsSplitTunnelingEnabled", enabled); + emit appsSplitTunnelingEnabledChanged(enabled); +} + +QString SecureAppSettingsRepository::getGatewayEndpoint(bool isTestPurchase) const +{ + if (isTestPurchase) { + return QString(DEV_AGW_ENDPOINT); + } + return m_gatewayEndpoint; +} + +void SecureAppSettingsRepository::setGatewayEndpoint(const QString &endpoint) +{ + m_gatewayEndpoint = endpoint; + setValue("Conf/gatewayEndpoint", endpoint); +} + +void SecureAppSettingsRepository::resetGatewayEndpoint() +{ + m_gatewayEndpoint = gatewayEndpoint; + setValue("Conf/gatewayEndpoint", gatewayEndpoint); +} + +void SecureAppSettingsRepository::setDevGatewayEndpoint() +{ + m_gatewayEndpoint = QString(DEV_AGW_ENDPOINT); + setValue("Conf/gatewayEndpoint", DEV_AGW_ENDPOINT); +} + +bool SecureAppSettingsRepository::isDevGatewayEnv(bool isTestPurchase) const +{ + return isTestPurchase ? true : value("Conf/devGatewayEnv", false).toBool(); +} + +void SecureAppSettingsRepository::toggleDevGatewayEnv(bool enabled) +{ + setValue("Conf/devGatewayEnv", enabled); +} + +bool SecureAppSettingsRepository::isKillSwitchEnabled() const +{ + return value("Conf/killSwitchEnabled", true).toBool(); +} + +void SecureAppSettingsRepository::setKillSwitchEnabled(bool enabled) +{ + setValue("Conf/killSwitchEnabled", enabled); +} + +bool SecureAppSettingsRepository::isStrictKillSwitchEnabled() const +{ + return value("Conf/strictKillSwitchEnabled", false).toBool(); +} + +void SecureAppSettingsRepository::setStrictKillSwitchEnabled(bool enabled) +{ + setValue("Conf/strictKillSwitchEnabled", enabled); +} + +bool SecureAppSettingsRepository::isAutoConnect() const +{ + return value("Conf/autoConnect", false).toBool(); +} + +void SecureAppSettingsRepository::setAutoConnect(bool enabled) +{ + setValue("Conf/autoConnect", enabled); +} + +bool SecureAppSettingsRepository::isStartMinimized() const +{ + return value("Conf/startMinimized", false).toBool(); +} + +void SecureAppSettingsRepository::setStartMinimized(bool enabled) +{ + setValue("Conf/startMinimized", enabled); +} + +bool SecureAppSettingsRepository::isScreenshotsEnabled() const +{ + return value("Conf/screenshotsEnabled", true).toBool(); +} + +void SecureAppSettingsRepository::setScreenshotsEnabled(bool enabled) +{ + setValue("Conf/screenshotsEnabled", enabled); + emit screenshotsEnabledChanged(enabled); +} + +bool SecureAppSettingsRepository::isNewsNotifications() const +{ + return value("Conf/newsNotifications", true).toBool(); +} + +void SecureAppSettingsRepository::setNewsNotifications(bool enabled) +{ + setValue("Conf/newsNotifications", enabled); +} + +bool SecureAppSettingsRepository::isSaveLogs() const +{ + return value("Conf/saveLogs", false).toBool(); +} + +void SecureAppSettingsRepository::setSaveLogs(bool enabled) +{ + setValue("Conf/saveLogs", enabled); + emit saveLogsChanged(enabled); +} + +QDateTime SecureAppSettingsRepository::getLogEnableDate() const +{ + return value("Conf/logEnableDate").toDateTime(); +} + +void SecureAppSettingsRepository::setLogEnableDate(const QDateTime &date) +{ + setValue("Conf/logEnableDate", date); +} + +QString SecureAppSettingsRepository::getInstallationUuid(bool createIfNotExists) const +{ + auto uuid = value("Conf/installationUuid", "").toString(); + if (createIfNotExists && uuid.isEmpty()) { + uuid = QUuid::createUuid().toString(); + uuid.remove(0, 1); + uuid.chop(1); + const_cast(this)->setValue("Conf/installationUuid", uuid); + } else if (uuid.contains("{") && uuid.contains("}")) { + uuid.remove(0, 1); + uuid.chop(1); + const_cast(this)->setValue("Conf/installationUuid", uuid); + } + return uuid; +} + +QStringList SecureAppSettingsRepository::getReadNewsIds() const +{ + return value("News/readIds").toStringList(); +} + +void SecureAppSettingsRepository::setReadNewsIds(const QStringList &ids) +{ + setValue("News/readIds", ids); +} + +bool SecureAppSettingsRepository::isHomeAdLabelVisible() const +{ + return value("Conf/homeAdLabelVisible", true).toBool(); +} + +void SecureAppSettingsRepository::disableHomeAdLabel() +{ + setValue("Conf/homeAdLabelVisible", false); +} + +bool SecureAppSettingsRepository::isPremV1MigrationReminderActive() const +{ + return value("Conf/premV1MigrationReminderActive", true).toBool(); +} + +void SecureAppSettingsRepository::disablePremV1MigrationReminder() +{ + setValue("Conf/premV1MigrationReminderActive", false); +} + +QByteArray SecureAppSettingsRepository::backupAppConfig() const +{ + return m_settings->backupAppConfig(); +} + +bool SecureAppSettingsRepository::restoreAppConfig(const QByteArray &cfg) +{ + return m_settings->restoreAppConfig(cfg); +} + +void SecureAppSettingsRepository::clearSettings() +{ + auto uuid = getInstallationUuid(false); + m_settings->clearSettings(); + m_settings->setValue("Conf/installationUuid", uuid); + 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); +} + + diff --git a/client/core/repositories/secureAppSettingsRepository.h b/client/core/repositories/secureAppSettingsRepository.h new file mode 100644 index 000000000..54ee0cd07 --- /dev/null +++ b/client/core/repositories/secureAppSettingsRepository.h @@ -0,0 +1,121 @@ +#ifndef SECUREAPPSETTINGSREPOSITORY_H +#define SECUREAPPSETTINGSREPOSITORY_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "secureQSettings.h" + +using namespace amnezia; + +class SecureAppSettingsRepository : public QObject +{ + Q_OBJECT + +public: + explicit SecureAppSettingsRepository(SecureQSettings* settings, QObject *parent = nullptr); + + QLocale getAppLanguage() const; + void setAppLanguage(QLocale locale); + + bool useAmneziaDns() const; + void setUseAmneziaDns(bool enabled); + QStringList getAllowedDnsServers() const; + void setAllowedDnsServers(const QStringList &servers); + QString primaryDns() const; + void setPrimaryDns(const QString &dns); + QString secondaryDns() const; + void setSecondaryDns(const QString &dns); + + RouteMode routeMode() const; + void setRouteMode(RouteMode mode); + bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); + void addVpnSites(RouteMode mode, const QMap &sites); + void removeVpnSite(RouteMode mode, const QString &site); + void removeAllVpnSites(RouteMode mode); + QVariantMap vpnSites(RouteMode mode) const; + bool isSitesSplitTunnelingEnabled() const; + void setSitesSplitTunnelingEnabled(bool enabled); + + AppsRouteMode appsRouteMode() const; + void setAppsRouteMode(AppsRouteMode mode); + void setVpnApps(AppsRouteMode mode, const QVector &apps); + QVector vpnApps(AppsRouteMode mode) const; + bool isAppsSplitTunnelingEnabled() const; + void setAppsSplitTunnelingEnabled(bool enabled); + + QString getGatewayEndpoint(bool isTestPurchase = false) const; + void setGatewayEndpoint(const QString &endpoint); + void resetGatewayEndpoint(); + void setDevGatewayEndpoint(); + bool isDevGatewayEnv(bool isTestPurchase = false) const; + void toggleDevGatewayEnv(bool enabled); + + bool isKillSwitchEnabled() const; + void setKillSwitchEnabled(bool enabled); + bool isStrictKillSwitchEnabled() const; + void setStrictKillSwitchEnabled(bool enabled); + + bool isAutoConnect() const; + void setAutoConnect(bool enabled); + bool isStartMinimized() const; + void setStartMinimized(bool enabled); + bool isScreenshotsEnabled() const; + void setScreenshotsEnabled(bool enabled); + bool isNewsNotifications() const; + void setNewsNotifications(bool enabled); + bool isSaveLogs() const; + void setSaveLogs(bool enabled); + QDateTime getLogEnableDate() const; + void setLogEnableDate(const QDateTime &date); + + QString getInstallationUuid(bool createIfNotExists) const; + QStringList getReadNewsIds() const; + void setReadNewsIds(const QStringList &ids); + + bool isHomeAdLabelVisible() const; + void disableHomeAdLabel(); + bool isPremV1MigrationReminderActive() const; + void disablePremV1MigrationReminder(); + QByteArray backupAppConfig() const; + bool restoreAppConfig(const QByteArray &cfg); + void clearSettings(); + + QString nextAvailableServerName() const; + +signals: + void appLanguageChanged(QLocale locale); + void allowedDnsServersChanged(const QStringList &servers); + void sitesChanged(RouteMode mode); + void appsChanged(AppsRouteMode mode); + void routeModeChanged(RouteMode mode); + void appsRouteModeChanged(AppsRouteMode mode); + void sitesSplitTunnelingEnabledChanged(bool enabled); + void appsSplitTunnelingEnabledChanged(bool enabled); + void useAmneziaDnsChanged(bool enabled); + void saveLogsChanged(bool enabled); + void screenshotsEnabledChanged(bool enabled); + void settingsCleared(); + +private: + void setVpnSites(RouteMode mode, const QVariantMap &sites); + void setInstallationUuid(const QString &uuid); + + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + void setValue(const QString &key, const QVariant &value); + + SecureQSettings* m_settings; + QString m_gatewayEndpoint; +}; + +#endif // SECUREAPPSETTINGSREPOSITORY_H + diff --git a/client/core/repositories/secureServersRepository.cpp b/client/core/repositories/secureServersRepository.cpp new file mode 100644 index 000000000..7107b8aa5 --- /dev/null +++ b/client/core/repositories/secureServersRepository.cpp @@ -0,0 +1,248 @@ +#include "secureServersRepository.h" + +#include +#include + +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" +#include "core/models/serverConfig.h" +#include "core/models/containerConfig.h" +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" + +SecureServersRepository::SecureServersRepository(SecureQSettings* settings, QObject *parent) + : QObject(parent), m_settings(settings) +{ + QJsonArray arr = QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array(); + for (const QJsonValue &val : arr) { + m_servers.append(ServerConfig::fromJson(val.toObject())); + } + m_defaultServerIndex = value("Servers/defaultServerIndex", 0).toInt(); +} + +QVariant SecureServersRepository::value(const QString &key, const QVariant &defaultValue) const +{ + return m_settings->value(key, defaultValue); +} + +void SecureServersRepository::setValue(const QString &key, const QVariant &value) +{ + m_settings->setValue(key, value); +} + +void SecureServersRepository::syncToStorage() +{ + QJsonArray arr; + for (const ServerConfig &cfg : m_servers) { + arr.append(cfg.toJson()); + } + setValue("Servers/serversList", QJsonDocument(arr).toJson()); +} + +void SecureServersRepository::invalidateCache() +{ + m_servers.clear(); + QJsonArray arr = QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array(); + for (const QJsonValue &val : arr) { + m_servers.append(ServerConfig::fromJson(val.toObject())); + } + m_defaultServerIndex = value("Servers/defaultServerIndex", 0).toInt(); +} + +void SecureServersRepository::setServersArray(const QJsonArray &servers) +{ + m_servers.clear(); + for (const QJsonValue &val : servers) { + m_servers.append(ServerConfig::fromJson(val.toObject())); + } + syncToStorage(); +} + +void SecureServersRepository::addServer(const ServerConfig &server) +{ + m_servers.append(server); + syncToStorage(); + emit serverAdded(server); +} + +void SecureServersRepository::editServer(int index, const ServerConfig &server) +{ + if (index < 0 || index >= m_servers.size()) { + return; + } + m_servers.replace(index, server); + syncToStorage(); + emit serverEdited(index, server); +} + +void SecureServersRepository::removeServer(int index) +{ + if (index < 0 || index >= m_servers.size()) { + return; + } + int defaultIndex = m_defaultServerIndex; + m_servers.removeAt(index); + + if (defaultIndex == index) { + setDefaultServer(0); + } else if (defaultIndex > index) { + setDefaultServer(defaultIndex - 1); + } + + if (m_servers.isEmpty()) { + setDefaultServer(0); + } + + syncToStorage(); + emit serverRemoved(index); +} + +ServerConfig SecureServersRepository::server(int index) const +{ + if (index < 0 || index >= m_servers.size()) { + return SelfHostedServerConfig{}; + } + return m_servers.at(index); +} + +QVector SecureServersRepository::servers() const +{ + return m_servers; +} + +int SecureServersRepository::serversCount() const +{ + return m_servers.size(); +} + +int SecureServersRepository::defaultServerIndex() const +{ + return m_defaultServerIndex; +} + +void SecureServersRepository::setDefaultServer(int index) +{ + if (index < 0) { + return; + } + if (m_servers.size() > 0 && index >= m_servers.size()) { + return; + } + if (m_servers.isEmpty() && index != 0) { + return; + } + if (m_defaultServerIndex == index) { + return; + } + m_defaultServerIndex = index; + setValue("Servers/defaultServerIndex", index); + emit defaultServerChanged(index); +} + +void SecureServersRepository::setDefaultContainer(int serverIndex, DockerContainer container) +{ + ServerConfig config = server(serverIndex); + config.visit([container](auto& arg) { + arg.defaultContainer = container; + }); + editServer(serverIndex, config); +} + +ContainerConfig SecureServersRepository::containerConfig(int serverIndex, DockerContainer container) const +{ + ServerConfig config = server(serverIndex); + return config.containerConfig(container); +} + +void SecureServersRepository::setContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config) +{ + ServerConfig serverConfig = server(serverIndex); + serverConfig.visit([container, &config](auto& arg) { + arg.containers[container] = config; + }); + editServer(serverIndex, serverConfig); +} + +void SecureServersRepository::clearLastConnectionConfig(int serverIndex, DockerContainer container) +{ + ServerConfig serverConfig = server(serverIndex); + ContainerConfig containerCfg = serverConfig.containerConfig(container); + + containerCfg.protocolConfig.clearClientConfig(); + + setContainerConfig(serverIndex, container, containerCfg); +} + +ServerCredentials SecureServersRepository::serverCredentials(int index) const +{ + ServerConfig config = server(index); + + if (config.isSelfHosted()) { + const SelfHostedServerConfig* selfHosted = config.as(); + if (!selfHosted) return ServerCredentials(); + auto creds = selfHosted->credentials(); + if (creds.has_value()) { + return creds.value(); + } + } + + return ServerCredentials{}; +} + +bool SecureServersRepository::hasServerWithVpnKey(const QString &vpnKey) const +{ + QString normalizedInput = vpnKey.trimmed(); + if (normalizedInput.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) { + normalizedInput = normalizedInput.mid(QStringLiteral("vpn://").size()); + } + if (normalizedInput.isEmpty()) { + return false; + } + + QVector serversList = servers(); + for (const ServerConfig& serverConfig : serversList) { + if (serverConfig.isApiV1()) { + const ApiV1ServerConfig* apiV1 = serverConfig.as(); + if (!apiV1) continue; + QString storedKey = apiV1->vpnKey(); + if (storedKey.isEmpty()) { + continue; + } + QString normalizedStored = storedKey.trimmed(); + if (normalizedStored.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) { + normalizedStored = normalizedStored.mid(QStringLiteral("vpn://").size()); + } + if (normalizedInput == normalizedStored) { + return true; + } + } else if (serverConfig.isApiV2()) { + const ApiV2ServerConfig* apiV2 = serverConfig.as(); + if (!apiV2) continue; + QString storedKey = apiV2->vpnKey(); + if (storedKey.isEmpty()) { + continue; + } + QString normalizedStored = storedKey.trimmed(); + if (normalizedStored.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) { + normalizedStored = normalizedStored.mid(QStringLiteral("vpn://").size()); + } + if (normalizedInput == normalizedStored) { + return true; + } + } + } + return false; +} + +bool SecureServersRepository::hasServerWithCrc(quint16 crc) const +{ + for (const ServerConfig& serverConfig : m_servers) { + if (static_cast(serverConfig.crc()) == crc) { + return true; + } + } + return false; +} diff --git a/client/core/repositories/secureServersRepository.h b/client/core/repositories/secureServersRepository.h new file mode 100644 index 000000000..03c876a71 --- /dev/null +++ b/client/core/repositories/secureServersRepository.h @@ -0,0 +1,64 @@ +#ifndef SECURESERVERSREPOSITORY_H +#define SECURESERVERSREPOSITORY_H + +#include +#include +#include +#include +#include + +#include "core/models/serverConfig.h" +#include "core/models/containerConfig.h" +#include "secureQSettings.h" + +using namespace amnezia; + +class SecureServersRepository : public QObject +{ + Q_OBJECT + +public: + explicit SecureServersRepository(SecureQSettings* settings, QObject *parent = nullptr); + + void addServer(const ServerConfig &server); + void editServer(int index, const ServerConfig &server); + void removeServer(int index); + ServerConfig server(int index) const; + QVector servers() const; + int serversCount() const; + + int defaultServerIndex() const; + void setDefaultServer(int index); + + void setDefaultContainer(int serverIndex, DockerContainer container); + ContainerConfig containerConfig(int serverIndex, DockerContainer container) const; + void setContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config); + void clearLastConnectionConfig(int serverIndex, DockerContainer container); + + ServerCredentials serverCredentials(int index) const; + bool hasServerWithVpnKey(const QString &vpnKey) const; + bool hasServerWithCrc(quint16 crc) const; + + void setServersArray(const QJsonArray &servers); + + void invalidateCache(); + +signals: + void serverAdded(ServerConfig config); + void serverEdited(int index, ServerConfig config); + void serverRemoved(int index); + void defaultServerChanged(int index); + +private: + void syncToStorage(); + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + void setValue(const QString &key, const QVariant &value); + + SecureQSettings* m_settings; + + QVector m_servers; + int m_defaultServerIndex = 0; +}; + +#endif // SECURESERVERSREPOSITORY_H + diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp deleted file mode 100644 index 92447c9ab..000000000 --- a/client/core/scripts_registry.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "scripts_registry.h" - -#include -#include -#include - -QString amnezia::scriptFolder(amnezia::DockerContainer container) -{ - switch (container) { - case DockerContainer::OpenVpn: return QLatin1String("openvpn"); - case DockerContainer::Cloak: return QLatin1String("openvpn_cloak"); - case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks"); - case DockerContainer::WireGuard: return QLatin1String("wireguard"); - case DockerContainer::Awg2: return QLatin1String("awg"); - case DockerContainer::Awg: return QLatin1String("awg_legacy"); - case DockerContainer::Ipsec: return QLatin1String("ipsec"); - case DockerContainer::Xray: return QLatin1String("xray"); - - case DockerContainer::TorWebSite: return QLatin1String("website_tor"); - case DockerContainer::Dns: return QLatin1String("dns"); - case DockerContainer::Sftp: return QLatin1String("sftp"); - case DockerContainer::Socks5Proxy: return QLatin1String("socks5_proxy"); - default: return QString(); - } -} - -QString amnezia::scriptName(SharedScriptType type) -{ - switch (type) { - case SharedScriptType::prepare_host: return QLatin1String("prepare_host.sh"); - case SharedScriptType::install_docker: return QLatin1String("install_docker.sh"); - case SharedScriptType::build_container: return QLatin1String("build_container.sh"); - case SharedScriptType::remove_container: return QLatin1String("remove_container.sh"); - case SharedScriptType::remove_all_containers: return QLatin1String("remove_all_containers.sh"); - case SharedScriptType::setup_host_firewall: return QLatin1String("setup_host_firewall.sh"); - case SharedScriptType::check_connection: return QLatin1String("check_connection.sh"); - case SharedScriptType::check_server_is_busy: return QLatin1String("check_server_is_busy.sh"); - case SharedScriptType::check_user_in_sudo: return QLatin1String("check_user_in_sudo.sh"); - default: return QString(); - } -} - -QString amnezia::scriptName(ProtocolScriptType type) -{ - switch (type) { - case ProtocolScriptType::dockerfile: return QLatin1String("Dockerfile"); - case ProtocolScriptType::run_container: return QLatin1String("run_container.sh"); - case ProtocolScriptType::configure_container: return QLatin1String("configure_container.sh"); - case ProtocolScriptType::container_startup: return QLatin1String("start.sh"); - case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn"); - case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf"); - case ProtocolScriptType::awg_template: return QLatin1String("template.conf"); - case ProtocolScriptType::xray_template: return QLatin1String("template.json"); - default: return QString(); - } -} - -QString amnezia::scriptData(amnezia::SharedScriptType type) -{ - QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type)); - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Warning: script missing" << fileName; - return ""; - } - QByteArray ba = file.readAll(); - if (ba.isEmpty()) { - qDebug() << "Warning: script is empty" << fileName; - } - return ba; -} - -QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer container) -{ - QString fileName = QString(":/server_scripts/%1/%2").arg(amnezia::scriptFolder(container), amnezia::scriptName(type)); - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Warning: script missing" << fileName; - return ""; - } - QByteArray data = file.readAll(); - data.replace("\r", ""); - return data; -} diff --git a/client/core/scripts_registry.h b/client/core/scripts_registry.h deleted file mode 100644 index d952dafb0..000000000 --- a/client/core/scripts_registry.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef SCRIPTS_REGISTRY_H -#define SCRIPTS_REGISTRY_H - -#include -#include "core/defs.h" -#include "containers/containers_defs.h" - -namespace amnezia { - -enum SharedScriptType { - // General scripts - prepare_host, - install_docker, - build_container, - remove_container, - remove_all_containers, - setup_host_firewall, - check_connection, - check_server_is_busy, - check_user_in_sudo -}; -enum ProtocolScriptType { - // Protocol scripts - dockerfile, - run_container, - configure_container, - container_startup, - openvpn_template, - wireguard_template, - awg_template, - xray_template -}; - - -QString scriptFolder(DockerContainer container); - -QString scriptName(SharedScriptType type); -QString scriptName(ProtocolScriptType type); - -QString scriptData(SharedScriptType type); -QString scriptData(ProtocolScriptType type, DockerContainer container); -} - -#endif // SCRIPTS_REGISTRY_H diff --git a/client/core/server_defs.cpp b/client/core/server_defs.cpp deleted file mode 100644 index 2e360d5d7..000000000 --- a/client/core/server_defs.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "server_defs.h" - -//QString amnezia::containerToString(amnezia::DockerContainer container) -//{ -// switch (container) { -// case(DockerContainer::OpenVpn): return "amnezia-openvpn"; -// case(DockerContainer::OpenVpnOverCloak): return "amnezia-openvpn-cloak"; -// case(DockerContainer::OpenVpnOverShadowSocks): return "amnezia-shadowsocks"; -// default: return ""; -// } -//} - -QString amnezia::server::getDockerfileFolder(amnezia::DockerContainer container) -{ - return "/opt/amnezia/" + ContainerProps::containerToString(container); -} diff --git a/client/core/server_defs.h b/client/core/server_defs.h deleted file mode 100644 index e61ca72ad..000000000 --- a/client/core/server_defs.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef SERVER_DEFS_H -#define SERVER_DEFS_H - -#include -#include "containers/containers_defs.h" - -namespace amnezia { -namespace server { -//QString getContainerName(amnezia::DockerContainer container); -QString getDockerfileFolder(amnezia::DockerContainer container); - -} -} - -#endif // SERVER_DEFS_H diff --git a/client/core/utils/api/apiEnums.h b/client/core/utils/api/apiEnums.h new file mode 100644 index 000000000..c52ce98a0 --- /dev/null +++ b/client/core/utils/api/apiEnums.h @@ -0,0 +1,25 @@ +#ifndef APIENUMS_H +#define APIENUMS_H + +namespace apiDefs +{ + enum ConfigType { + AmneziaFreeV2 = 0, + AmneziaFreeV3, + AmneziaPremiumV1, + AmneziaPremiumV2, + AmneziaTrialV2, + SelfHosted, + ExternalPremium, + ExternalTrial + }; + + enum ConfigSource { + Telegram = 1, + AmneziaGateway + }; +} + +#endif // APIENUMS_H + + diff --git a/client/core/api/apiUtils.cpp b/client/core/utils/api/apiUtils.cpp similarity index 90% rename from client/core/api/apiUtils.cpp rename to client/core/utils/api/apiUtils.cpp index 5ed46d1f1..eca4689fb 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/utils/api/apiUtils.cpp @@ -1,10 +1,13 @@ #include "apiUtils.h" +#include "core/utils/constants/configKeys.h" #include #include #include #include +using namespace amnezia; + namespace { const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff"); @@ -74,7 +77,7 @@ bool apiUtils::isSubscriptionExpiringSoon(const QString &subscriptionEndDate, in bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject) { - auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt(); + auto configVersion = serverConfigObject.value(configKey::configVersion).toInt(); switch (configVersion) { case apiDefs::ConfigSource::Telegram: return true; case apiDefs::ConfigSource::AmneziaGateway: return true; @@ -84,7 +87,7 @@ bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject) apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject) { - auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt(); + auto configVersion = serverConfigObject.value(configKey::configVersion).toInt(); switch (configVersion) { case apiDefs::ConfigSource::Telegram: { @@ -126,7 +129,7 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigObject) { - return static_cast(serverConfigObject.value(apiDefs::key::configVersion).toInt()); + return static_cast(serverConfigObject.value(configKey::configVersion).toInt()); } amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList &sslErrors, const QString &replyErrorString, @@ -206,9 +209,9 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject) } QList> orderedFields; - orderedFields.append(qMakePair(apiDefs::key::name, serverConfigObject[apiDefs::key::name].toString())); - orderedFields.append(qMakePair(apiDefs::key::description, serverConfigObject[apiDefs::key::description].toString())); - orderedFields.append(qMakePair(apiDefs::key::configVersion, serverConfigObject[apiDefs::key::configVersion].toDouble())); + orderedFields.append(qMakePair(configKey::name, serverConfigObject[configKey::name].toString())); + orderedFields.append(qMakePair(configKey::description, serverConfigObject[configKey::description].toString())); + orderedFields.append(qMakePair(configKey::configVersion, serverConfigObject[configKey::configVersion].toDouble())); orderedFields.append(qMakePair(apiDefs::key::protocol, serverConfigObject[apiDefs::key::protocol].toString())); orderedFields.append(qMakePair(apiDefs::key::apiEndpoint, serverConfigObject[apiDefs::key::apiEndpoint].toString())); orderedFields.append(qMakePair(apiDefs::key::apiKey, serverConfigObject[apiDefs::key::apiKey].toString())); @@ -250,9 +253,9 @@ QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject) auto apiConfig = serverConfigObject.value(apiDefs::key::apiConfig).toObject(); auto authData = serverConfigObject.value(QLatin1String("auth_data")).toObject(); - const QString name = serverConfigObject.value(apiDefs::key::name).toString(); - const QString description = serverConfigObject.value(apiDefs::key::description).toString(); - const double configVersion = serverConfigObject.value(apiDefs::key::configVersion).toDouble(); + const QString name = serverConfigObject.value(configKey::name).toString(); + const QString description = serverConfigObject.value(configKey::description).toString(); + const double configVersion = serverConfigObject.value(configKey::configVersion).toDouble(); const QString serviceType = apiConfig.value(apiDefs::key::serviceType).toString(); const QString serviceProtocol = apiConfig.value(QLatin1String("service_protocol")).toString(); @@ -261,9 +264,9 @@ QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject) const QString apiKey = authData.value(apiDefs::key::apiKey).toString(); QString vpnKeyStr = "{"; - vpnKeyStr += "\"" + QString(apiDefs::key::name) + "\": \"" + name + "\", "; - vpnKeyStr += "\"" + QString(apiDefs::key::description) + "\": \"" + description + "\", "; - vpnKeyStr += "\"" + QString(apiDefs::key::configVersion) + "\": " + QString::number(static_cast(configVersion)) + ", "; + vpnKeyStr += "\"" + QString(configKey::name) + "\": \"" + name + "\", "; + vpnKeyStr += "\"" + QString(configKey::description) + "\": \"" + description + "\", "; + vpnKeyStr += "\"" + QString(configKey::configVersion) + "\": " + QString::number(static_cast(configVersion)) + ", "; vpnKeyStr += "\"" + QString(apiDefs::key::apiConfig) + "\": {"; vpnKeyStr += "\"" + QString(apiDefs::key::serviceType) + "\": \"" + serviceType + "\", "; diff --git a/client/core/api/apiUtils.h b/client/core/utils/api/apiUtils.h similarity index 81% rename from client/core/api/apiUtils.h rename to client/core/utils/api/apiUtils.h index ba2a0103b..be770defa 100644 --- a/client/core/api/apiUtils.h +++ b/client/core/utils/api/apiUtils.h @@ -4,8 +4,12 @@ #include #include -#include "apiDefs.h" -#include "core/defs.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" namespace apiUtils { diff --git a/client/core/utils/commonStructs.h b/client/core/utils/commonStructs.h new file mode 100644 index 000000000..e9e699441 --- /dev/null +++ b/client/core/utils/commonStructs.h @@ -0,0 +1,63 @@ +#ifndef COMMONSTRUCTS_H +#define COMMONSTRUCTS_H + +#include +#include "core/utils/routeModes.h" + +namespace amnezia +{ + struct ServerCredentials + { + QString hostName; + QString userName; + QString secretData; + int port = 22; + + bool isValid() const + { + return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; + } + }; + + struct InstalledAppInfo { + QString appName; + QString packageName; + QString appPath; + + bool operator==(const InstalledAppInfo& other) const { + if (!packageName.isEmpty()) { + return packageName == other.packageName; + } else { + return appPath == other.appPath; + } + } + }; + + struct DnsSettings + { + QString primaryDns; + QString secondaryDns; + }; + + struct SplitTunnelingSettings + { + bool isSitesSplitTunnelingEnabled; + RouteMode routeMode; + }; + + struct ConnectionSettings + { + DnsSettings dns; + bool isApiConfig; + SplitTunnelingSettings splitTunneling; + }; + + struct ExportSettings + { + DnsSettings dns; + }; +} + +#endif // COMMONSTRUCTS_H + + diff --git a/client/constants.h b/client/core/utils/constants.h similarity index 100% rename from client/constants.h rename to client/core/utils/constants.h diff --git a/client/core/utils/constants/apiConstants.h b/client/core/utils/constants/apiConstants.h new file mode 100644 index 000000000..b9895bc82 --- /dev/null +++ b/client/core/utils/constants/apiConstants.h @@ -0,0 +1,11 @@ +#ifndef APICONSTANTS_H +#define APICONSTANTS_H + +namespace apiDefs +{ + const int requestTimeoutMsecs = 12 * 1000; // 12 secs +} + +#endif // APICONSTANTS_H + + diff --git a/client/core/api/apiDefs.h b/client/core/utils/constants/apiKeys.h similarity index 76% rename from client/core/api/apiDefs.h rename to client/core/utils/constants/apiKeys.h index 2c2fd00f6..2e037bca6 100644 --- a/client/core/api/apiDefs.h +++ b/client/core/utils/constants/apiKeys.h @@ -1,57 +1,47 @@ -#ifndef APIDEFS_H -#define APIDEFS_H +#ifndef APIKEYS_H +#define APIKEYS_H -#include +#include +#include "core/utils/api/apiEnums.h" namespace apiDefs { - enum ConfigType { - AmneziaFreeV2 = 0, - AmneziaFreeV3, - AmneziaPremiumV1, - AmneziaPremiumV2, - SelfHosted, - ExternalPremium, - ExternalTrial - }; - - enum ConfigSource { - Telegram = 1, - AmneziaGateway - }; - namespace key { - constexpr QLatin1String configVersion("config_version"); constexpr QLatin1String apiEndpoint("api_endpoint"); constexpr QLatin1String apiKey("api_key"); - constexpr QLatin1String description("description"); - constexpr QLatin1String name("name"); constexpr QLatin1String protocol("protocol"); - constexpr QLatin1String apiConfig("api_config"); - constexpr QLatin1String stackType("stack_type"); constexpr QLatin1String serviceType("service_type"); + constexpr QLatin1String serviceInfo("service_info"); + constexpr QLatin1String serviceProtocol("service_protocol"); + constexpr QLatin1String vpnKey("vpn_key"); + constexpr QLatin1String stackType("stack_type"); constexpr QLatin1String cliVersion("cli_version"); constexpr QLatin1String cliName("cli_name"); constexpr QLatin1String supportedProtocols("supported_protocols"); - - constexpr QLatin1String vpnKey("vpn_key"); - constexpr QLatin1String config("config"); - constexpr QLatin1String configs("configs"); - + constexpr QLatin1String availableCountries("available_countries"); constexpr QLatin1String installationUuid("installation_uuid"); + constexpr QLatin1String uuid("installation_uuid"); + constexpr QLatin1String osVersion("os_version"); + constexpr QLatin1String userCountryCode("user_country_code"); + constexpr QLatin1String serverCountryCode("server_country_code"); + constexpr QLatin1String serverCountryName("server_country_name"); + constexpr QLatin1String appVersion("app_version"); + constexpr QLatin1String authData("auth_data"); + + constexpr QLatin1String aesKey("aes_key"); + constexpr QLatin1String aesIv("aes_iv"); + constexpr QLatin1String aesSalt("aes_salt"); + constexpr QLatin1String apiPayload("api_payload"); + constexpr QLatin1String keyPayload("key_payload"); + + constexpr QLatin1String services("services"); constexpr QLatin1String workerLastUpdated("worker_last_updated"); constexpr QLatin1String lastDownloaded("last_downloaded"); constexpr QLatin1String sourceType("source_type"); - - constexpr QLatin1String serverCountryCode("server_country_code"); - constexpr QLatin1String serverCountryName("server_country_name"); - - constexpr QLatin1String osVersion("os_version"); constexpr QLatin1String appLanguage("app_language"); - constexpr QLatin1String availableCountries("available_countries"); constexpr QLatin1String activeDeviceCount("active_device_count"); constexpr QLatin1String maxDeviceCount("max_device_count"); constexpr QLatin1String subscriptionEndDate("subscription_end_date"); @@ -74,22 +64,25 @@ namespace apiDefs constexpr QLatin1String id("id"); constexpr QLatin1String orderId("order_id"); constexpr QLatin1String migrationCode("migration_code"); - constexpr QLatin1String transactionId("transaction_id"); constexpr QLatin1String isTestPurchase("is_test_purchase"); constexpr QLatin1String isInAppPurchase("is_in_app_purchase"); + constexpr QLatin1String config("config"); - constexpr QLatin1String userCountryCode("user_country_code"); - - constexpr QLatin1String serviceInfo("service_info"); constexpr QLatin1String isAdVisible("is_ad_visible"); constexpr QLatin1String isRenewalAvailable("is_renewal_available"); constexpr QLatin1String adHeader("ad_header"); constexpr QLatin1String adDescription("ad_description"); constexpr QLatin1String adEndpoint("ad_endpoint"); - } - const int requestTimeoutMsecs = 12 * 1000; // 12 secs + constexpr QLatin1String configs("configs"); + + constexpr QLatin1String publicKeyInfo("public_key"); + constexpr QLatin1String publicKey("public_key"); + constexpr QLatin1String expiresAt("expires_at"); + constexpr QLatin1String isConnectEvent("is_connect_event"); + constexpr QLatin1String certificate("certificate"); + } } -#endif // APIDEFS_H +#endif // APIKEYS_H diff --git a/client/core/utils/constants/configKeys.h b/client/core/utils/constants/configKeys.h new file mode 100644 index 000000000..40bc842b1 --- /dev/null +++ b/client/core/utils/constants/configKeys.h @@ -0,0 +1,127 @@ +#ifndef CONFIGKEYS_H +#define CONFIGKEYS_H + +#include + +namespace amnezia +{ + namespace configKey + { + constexpr QLatin1String hostName("hostName"); + constexpr QLatin1String userName("userName"); + constexpr QLatin1String password("password"); + constexpr QLatin1String port("port"); + constexpr QLatin1String localPort("local_port"); + + constexpr QLatin1String dns1("dns1"); + constexpr QLatin1String dns2("dns2"); + + constexpr QLatin1String serverIndex("serverIndex"); + constexpr QLatin1String description("description"); + constexpr QLatin1String name("name"); + constexpr QLatin1String cert("cert"); + constexpr QLatin1String accessToken("api_key"); + constexpr QLatin1String config("config"); + constexpr QLatin1String configVersion("config_version"); + + constexpr QLatin1String containers("containers"); + constexpr QLatin1String container("container"); + constexpr QLatin1String defaultContainer("defaultContainer"); + + constexpr QLatin1String vpnProto("protocol"); + constexpr QLatin1String protocol("protocol"); + constexpr QLatin1String protocols("protocols"); + + constexpr QLatin1String remote("remote"); + constexpr QLatin1String transportProto("transport_proto"); + constexpr QLatin1String cipher("cipher"); + constexpr QLatin1String hash("hash"); + constexpr QLatin1String ncpDisable("ncp_disable"); + constexpr QLatin1String tlsAuth("tls_auth"); + + constexpr QLatin1String clientPrivKey("client_priv_key"); + constexpr QLatin1String clientPubKey("client_pub_key"); + constexpr QLatin1String serverPrivKey("server_priv_key"); + constexpr QLatin1String serverPubKey("server_pub_key"); + constexpr QLatin1String pskKey("psk_key"); + constexpr QLatin1String mtu("mtu"); + constexpr QLatin1String allowedIps("allowed_ips"); + constexpr QLatin1String persistentKeepAlive("persistent_keep_alive"); + + constexpr QLatin1String clientIp("client_ip"); + + constexpr QLatin1String site("site"); + constexpr QLatin1String blockOutsideDns("block_outside_dns"); + + constexpr QLatin1String subnetAddress("subnet_address"); + constexpr QLatin1String subnetMask("subnet_mask"); + constexpr QLatin1String subnetCidr("subnet_cidr"); + + constexpr QLatin1String additionalClientConfig("additional_client_config"); + constexpr QLatin1String additionalServerConfig("additional_server_config"); + + constexpr QLatin1String lastConfig("last_config"); + + constexpr QLatin1String isThirdPartyConfig("isThirdPartyConfig"); + constexpr QLatin1String isObfuscationEnabled("isObfuscationEnabled"); + + constexpr QLatin1String junkPacketCount("Jc"); + constexpr QLatin1String junkPacketMinSize("Jmin"); + constexpr QLatin1String junkPacketMaxSize("Jmax"); + constexpr QLatin1String initPacketJunkSize("S1"); + constexpr QLatin1String responsePacketJunkSize("S2"); + constexpr QLatin1String cookieReplyPacketJunkSize("S3"); + constexpr QLatin1String transportPacketJunkSize("S4"); + constexpr QLatin1String initPacketMagicHeader("H1"); + constexpr QLatin1String responsePacketMagicHeader("H2"); + constexpr QLatin1String underloadPacketMagicHeader("H3"); + constexpr QLatin1String transportPacketMagicHeader("H4"); + constexpr QLatin1String specialJunk1("I1"); + constexpr QLatin1String specialJunk2("I2"); + constexpr QLatin1String specialJunk3("I3"); + constexpr QLatin1String specialJunk4("I4"); + constexpr QLatin1String specialJunk5("I5"); + + constexpr QLatin1String protocolVersion("protocol_version"); + + constexpr QLatin1String openvpn("openvpn"); + constexpr QLatin1String wireguard("wireguard"); + constexpr QLatin1String sftp("sftp"); + constexpr QLatin1String awg("awg"); + constexpr QLatin1String vless("vless"); + constexpr QLatin1String xray("xray"); + constexpr QLatin1String ssxray("ssxray"); + constexpr QLatin1String socks5proxy("socks5proxy"); + + constexpr QLatin1String splitTunnelSites("splitTunnelSites"); + constexpr QLatin1String splitTunnelType("splitTunnelType"); + + constexpr QLatin1String splitTunnelApps("splitTunnelApps"); + constexpr QLatin1String appSplitTunnelType("appSplitTunnelType"); + + constexpr QLatin1String allowedDnsServers("allowedDnsServers"); + + constexpr QLatin1String killSwitchOption("killSwitchOption"); + + constexpr QLatin1String crc("crc"); + + constexpr QLatin1String clientId("clientId"); + + constexpr QLatin1String nameOverriddenByUser("nameOverriddenByUser"); + + constexpr QLatin1String amneziaOpenvpn("amnezia-openvpn"); + constexpr QLatin1String amneziaWireguard("amnezia-wireguard"); + constexpr QLatin1String amneziaAwg("amnezia-awg"); + constexpr QLatin1String amneziaXray("amnezia-xray"); + constexpr QLatin1String amneziaSsxray("amnezia-ssxray"); + + constexpr QLatin1String clientName("clientName"); + constexpr QLatin1String userData("userData"); + constexpr QLatin1String creationDate("creationDate"); + constexpr QLatin1String latestHandshake("latestHandshake"); + constexpr QLatin1String dataReceived("dataReceived"); + constexpr QLatin1String dataSent("dataSent"); + } +} + +#endif diff --git a/client/core/utils/constants/protocolConstants.h b/client/core/utils/constants/protocolConstants.h new file mode 100644 index 000000000..01e2a151a --- /dev/null +++ b/client/core/utils/constants/protocolConstants.h @@ -0,0 +1,182 @@ +#ifndef PROTOCOLCONSTANTS_H +#define PROTOCOLCONSTANTS_H + +namespace amnezia +{ + namespace protocols + { + + namespace dns + { + constexpr char amneziaDnsIp[] = "172.29.172.254"; + } + + namespace openvpn + { + constexpr char defaultSubnetAddress[] = "10.8.0.0"; + constexpr char defaultSubnetMask[] = "255.255.255.0"; + constexpr char defaultSubnetCidr[] = "24"; + constexpr char defaultMtu[] = "1500"; + + constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf"; + constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt"; + constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued"; + constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key"; + constexpr char clientsDirPath[] = "/opt/amnezia/openvpn/clients"; + constexpr char defaultPort[] = "1194"; + constexpr char defaultTransportProto[] = "udp"; + constexpr char defaultCipher[] = "AES-256-GCM"; + constexpr char defaultHash[] = "SHA512"; + constexpr bool defaultBlockOutsideDns = true; + constexpr bool defaultNcpDisable = false; + constexpr bool defaultTlsAuth = true; + constexpr char ncpDisableString[] = "ncp-disable"; + constexpr char tlsAuthString[] = "tls-auth /opt/amnezia/openvpn/ta.key 0"; + + constexpr char defaultAdditionalClientConfig[] = ""; + constexpr char defaultAdditionalServerConfig[] = ""; + } + + namespace shadowsocks + { + constexpr char ssKeyPath[] = "/opt/amnezia/shadowsocks/shadowsocks.key"; + constexpr char defaultPort[] = "6789"; + constexpr char defaultLocalProxyPort[] = "8585"; + constexpr char defaultCipher[] = "chacha20-ietf-poly1305"; + } + + namespace xray + { + constexpr char serverConfigPath[] = "/opt/amnezia/xray/server.json"; + constexpr char uuidPath[] = "/opt/amnezia/xray/xray_uuid.key"; + constexpr char PublicKeyPath[] = "/opt/amnezia/xray/xray_public.key"; + constexpr char PrivateKeyPath[] = "/opt/amnezia/xray/xray_private.key"; + constexpr char shortidPath[] = "/opt/amnezia/xray/xray_short_id.key"; + constexpr char defaultSite[] = "www.googletagmanager.com"; + + constexpr char defaultPort[] = "443"; + constexpr char defaultLocalProxyPort[] = "10808"; + constexpr char defaultLocalAddr[] = "10.33.0.2"; + + constexpr char outbounds[] = "outbounds"; + constexpr char inbounds[] = "inbounds"; + constexpr char settings[] = "settings"; + constexpr char streamSettings[] = "streamSettings"; + constexpr char vnext[] = "vnext"; + constexpr char users[] = "users"; + constexpr char servers[] = "servers"; + constexpr char clients[] = "clients"; + constexpr char id[] = "id"; + constexpr char port[] = "port"; + constexpr char address[] = "address"; + constexpr char flow[] = "flow"; + constexpr char encryption[] = "encryption"; + constexpr char network[] = "network"; + constexpr char security[] = "security"; + constexpr char realitySettings[] = "realitySettings"; + constexpr char serverNames[] = "serverNames"; + constexpr char serverName[] = "serverName"; + constexpr char publicKey[] = "publicKey"; + constexpr char shortId[] = "shortId"; + constexpr char fingerprint[] = "fingerprint"; + constexpr char spiderX[] = "spiderX"; + constexpr char user[] = "user"; + constexpr char pass[] = "pass"; + } + + namespace cloak + { + constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key"; + constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key"; + constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key"; + constexpr char defaultPort[] = "443"; + constexpr char defaultRedirSite[] = "tile.openstreetmap.org"; + constexpr char defaultCipher[] = "chacha20-poly1305"; + } + + namespace wireguard + { + // Config file keys ([Interface] / [Peer] sections) - case-sensitive + constexpr char PrivateKey[] = "PrivateKey"; + constexpr char Address[] = "Address"; + constexpr char PublicKey[] = "PublicKey"; + constexpr char PresharedKey[] = "PresharedKey"; + constexpr char PreSharedKey[] = "PreSharedKey"; + constexpr char AllowedIPs[] = "AllowedIPs"; + constexpr char Endpoint[] = "Endpoint"; + constexpr char PersistentKeepalive[] = "PersistentKeepalive"; + constexpr char MTU[] = "MTU"; + + constexpr char defaultSubnetAddress[] = "10.8.1.0"; + constexpr char defaultSubnetMask[] = "255.255.255.0"; + constexpr char defaultSubnetCidr[] = "24"; + + constexpr char defaultPort[] = "51820"; + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) + constexpr char defaultMtu[] = "1280"; +#else + constexpr char defaultMtu[] = "1376"; +#endif + constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf"; + constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key"; + constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key"; + + } + + namespace sftp + { + constexpr char defaultUserName[] = "sftp_user"; + + } // namespace sftp + + namespace awg + { + constexpr char defaultPort[] = "55424"; +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) + constexpr char defaultMtu[] = "1280"; +#else + constexpr char defaultMtu[] = "1376"; +#endif + + constexpr char serverConfigPath[] = "/opt/amnezia/awg/awg0.conf"; + constexpr char serverLegacyConfigPath[] = "/opt/amnezia/awg/wg0.conf"; + constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key"; + constexpr char serverPskKeyPath[] = "/opt/amnezia/awg/wireguard_psk.key"; + + constexpr char defaultJunkPacketCount[] = "3"; + constexpr char defaultJunkPacketMinSize[] = "10"; + constexpr char defaultJunkPacketMaxSize[] = "30"; + constexpr char defaultInitPacketJunkSize[] = "15"; + constexpr char defaultResponsePacketJunkSize[] = "18"; + constexpr char defaultCookieReplyPacketJunkSize[] = "20"; + constexpr char defaultTransportPacketJunkSize[] = "23"; + + constexpr char defaultInitPacketMagicHeader[] = "1020325451"; + constexpr char defaultResponsePacketMagicHeader[] = "3288052141"; + constexpr char defaultTransportPacketMagicHeader[] = "2528465083"; + constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858"; + constexpr char defaultSpecialJunk1[] = ""; + constexpr char defaultSpecialJunk2[] = ""; + constexpr char defaultSpecialJunk3[] = ""; + constexpr char defaultSpecialJunk4[] = ""; + constexpr char defaultSpecialJunk5[] = ""; + + constexpr char awgV1_5[] = "1.5"; + constexpr char awgV2[] = "2"; + } + + namespace socks5Proxy + { + constexpr char defaultUserName[] = "proxy_user"; + constexpr char defaultPort[] = "38080"; + + constexpr char proxyConfigPath[] = "/usr/local/3proxy/conf/3proxy.cfg"; + } + + } // namespace protocols +} + +#endif // PROTOCOLCONSTANTS_H + + diff --git a/client/core/utils/containerEnum.h b/client/core/utils/containerEnum.h new file mode 100644 index 000000000..97398b783 --- /dev/null +++ b/client/core/utils/containerEnum.h @@ -0,0 +1,36 @@ +#ifndef CONTAINERENUM_H +#define CONTAINERENUM_H + +#include +#include + +namespace amnezia +{ + namespace ContainerEnumNS + { + Q_NAMESPACE + enum DockerContainer { + None = 0, + Awg, + Awg2, + WireGuard, + OpenVpn, + Ipsec, + Xray, + SSXray, + + // non-vpn + TorWebSite, + Dns, + Sftp, + Socks5Proxy + }; + Q_ENUM_NS(DockerContainer) + } // namespace ContainerEnumNS + + using namespace ContainerEnumNS; +} + +#endif // CONTAINERENUM_H + + diff --git a/client/containers/containers_defs.cpp b/client/core/utils/containers/containerUtils.cpp similarity index 68% rename from client/containers/containers_defs.cpp rename to client/core/utils/containers/containerUtils.cpp index d14ace052..b028dcf5f 100644 --- a/client/containers/containers_defs.cpp +++ b/client/core/utils/containers/containerUtils.cpp @@ -1,17 +1,12 @@ -#include "containers_defs.h" +#include "containerUtils.h" -#include "QJsonObject" -#include "QJsonDocument" +#include +#include +#include -QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c) -{ - QDebugStateSaver saver(debug); - debug.nospace() << ContainerProps::containerToString(c); +using namespace amnezia; - return debug; -} - -amnezia::DockerContainer ContainerProps::containerFromString(const QString &container) +DockerContainer ContainerUtils::containerFromString(const QString &container) { QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { @@ -22,12 +17,10 @@ amnezia::DockerContainer ContainerProps::containerFromString(const QString &cont return DockerContainer::None; } -QString ContainerProps::containerToString(amnezia::DockerContainer c) +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) @@ -38,7 +31,7 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c) return "amnezia-" + containerKey.toLower(); } -QString ContainerProps::containerTypeToString(amnezia::DockerContainer c) +QString ContainerUtils::containerTypeToString(DockerContainer c) { if (c == DockerContainer::None) return "none"; @@ -54,36 +47,7 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c) return containerKey.toLower(); } -QVector ContainerProps::protocolsForContainer(amnezia::DockerContainer container) -{ - switch (container) { - case DockerContainer::None: return {}; - - case DockerContainer::OpenVpn: return { Proto::OpenVpn }; - - case DockerContainer::ShadowSocks: return { Proto::OpenVpn, Proto::ShadowSocks }; - - case DockerContainer::Cloak: return { Proto::OpenVpn, Proto::ShadowSocks, Proto::Cloak }; - - case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ }; - - case DockerContainer::Xray: return { Proto::Xray }; - - case DockerContainer::SSXray: return { Proto::SSXray }; - - case DockerContainer::Dns: return { Proto::Dns }; - - case DockerContainer::Sftp: return { Proto::Sftp }; - - case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy }; - - case DockerContainer::Awg: return { Proto::Awg }; - case DockerContainer::Awg2: return { Proto::Awg }; - default: return { defaultProtocol(container) }; - } -} - -QList ContainerProps::allContainers() +QList ContainerUtils::allContainers() { QMetaEnum metaEnum = QMetaEnum::fromType(); QList all; @@ -94,12 +58,10 @@ QList ContainerProps::allContainers() return all; } -QMap ContainerProps::containerHumanNames() +QMap 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" }, @@ -113,16 +75,11 @@ QMap ContainerProps::containerHumanNames() { DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } }; } -QMap ContainerProps::containerDescriptions() +QMap ContainerUtils::containerDescriptions() { - return { { DockerContainer::OpenVpn, + 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("Shadowsocks masks VPN traffic, making it resemble normal web traffic, but it may still be detected by certain analysis systems.") }, - { DockerContainer::Cloak, - QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against " - "active-probing detection. It is very resistant to detection, but offers low speed.") }, { DockerContainer::WireGuard, QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power " "consumption.") }, @@ -148,7 +105,7 @@ QMap ContainerProps::containerDescriptions() QObject::tr("") } }; } -QMap ContainerProps::containerDetailedDescriptions() +QMap ContainerUtils::containerDetailedDescriptions() { return { { DockerContainer::OpenVpn, @@ -162,28 +119,6 @@ QMap ContainerProps::containerDetailedDescriptions() "* Normal battery consumption on mobile devices\n" "* Flexible customization for various devices and OS\n" "* Operates over both TCP and UDP protocols") }, - { DockerContainer::ShadowSocks, - QObject::tr("Shadowsocks is based on the SOCKS5 protocol and encrypts connections using AEAD cipher. " - "Although designed to be discreet, it doesn't mimic a standard HTTPS connection and can be detected by some DPI systems. " - "Due to limited support in Amnezia, we recommend using the AmneziaWG protocol.\n" - "\nFeatures:\n" - "* Available in AmneziaVPN only on desktop platforms\n" - "* Customizable encryption protocol\n" - "* Detectable by some DPI systems\n" - "* Operates over TCP protocol\n") }, - { DockerContainer::Cloak, - QObject::tr("This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking.\n" - "\nOpenVPN securely encrypts all internet traffic between your device and the server.\n" - "\nThe Cloak plugin further protects the connection from DPI detection. " - "It modifies traffic metadata to disguise VPN traffic as regular web traffic and prevents detection through active probing. " - "If an incoming connection fails authentication, Cloak serves a fake website, making your VPN invisible to traffic analysis systems.\n" - "\nIn regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection.\n" - "\nFeatures:\n" - "* Available on all AmneziaVPN platforms\n" - "* High power consumption on mobile devices\n" - "* Flexible configuration options\n" - "* Undetectable by DPI systems\n" - "* Operates over TCP protocol on port 443") }, { DockerContainer::WireGuard, QObject::tr("WireGuard is a modern, streamlined VPN protocol offering stable connectivity and excellent performance across all devices. " "It uses fixed encryption settings, delivering lower latency and higher data transfer speeds compared to OpenVPN. " @@ -241,18 +176,16 @@ QMap ContainerProps::containerDetailedDescriptions() }; } -amnezia::ServiceType ContainerProps::containerService(DockerContainer c) +ServiceType ContainerUtils::containerService(DockerContainer c) { - return ProtocolProps::protocolService(defaultProtocol(c)); + return ProtocolUtils::protocolService(defaultProtocol(c)); } -Proto ContainerProps::defaultProtocol(DockerContainer c) +Proto ContainerUtils::defaultProtocol(DockerContainer c) { switch (c) { - case DockerContainer::None: return Proto::Any; + case DockerContainer::None: return Proto::Unknown; case DockerContainer::OpenVpn: return Proto::OpenVpn; - case DockerContainer::Cloak: return Proto::Cloak; - case DockerContainer::ShadowSocks: return Proto::ShadowSocks; case DockerContainer::WireGuard: return Proto::WireGuard; case DockerContainer::Awg2: return Proto::Awg; case DockerContainer::Awg: return Proto::Awg; @@ -264,20 +197,20 @@ Proto ContainerProps::defaultProtocol(DockerContainer c) case DockerContainer::Dns: return Proto::Dns; case DockerContainer::Sftp: return Proto::Sftp; case DockerContainer::Socks5Proxy: return Proto::Socks5Proxy; - default: return Proto::Any; + default: return Proto::Unknown; } } -QString ContainerProps::containerTypeToProtocolString(DockerContainer c) +QString ContainerUtils::containerTypeToProtocolString(DockerContainer c) { if (c == DockerContainer::None) return "none"; Proto p = defaultProtocol(c); - return ProtocolProps::protoToString(p); + return ProtocolUtils::protoToString(p); } -bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) +bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c) { #ifdef Q_OS_WINDOWS return true; @@ -290,9 +223,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) case DockerContainer::Awg2: return true; case DockerContainer::Awg: return true; case DockerContainer::Xray: return true; - case DockerContainer::Cloak: return true; case DockerContainer::SSXray: return true; - // case DockerContainer::ShadowSocks: return true; default: return false; } @@ -306,8 +237,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) case DockerContainer::Awg: return true; case DockerContainer::Xray: return true; case DockerContainer::SSXray: return true; - case DockerContainer::Cloak: - case DockerContainer::ShadowSocks: return false; default: return false; @@ -323,10 +252,8 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; - case DockerContainer::ShadowSocks: return false; case DockerContainer::Awg2: return true; case DockerContainer::Awg: return true; - case DockerContainer::Cloak: return true; case DockerContainer::Xray: return true; case DockerContainer::SSXray: return true; default: return false; @@ -343,7 +270,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) #endif } -QStringList ContainerProps::fixedPortsForContainer(DockerContainer c) +QStringList ContainerUtils::fixedPortsForContainer(DockerContainer c) { switch (c) { case DockerContainer::Ipsec: return QStringList { "500", "4500" }; @@ -351,7 +278,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c) } } -bool ContainerProps::isEasySetupContainer(DockerContainer container) +bool ContainerUtils::isEasySetupContainer(DockerContainer container) { switch (container) { case DockerContainer::Awg2: return true; @@ -359,24 +286,24 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container) } } -QString ContainerProps::easySetupHeader(DockerContainer container) +QString ContainerUtils::easySetupHeader(DockerContainer container) { switch (container) { - case DockerContainer::Awg2: return tr("Automatic"); + case DockerContainer::Awg2: return QObject::tr("Automatic"); default: return ""; } } -QString ContainerProps::easySetupDescription(DockerContainer container) +QString ContainerUtils::easySetupDescription(DockerContainer container) { switch (container) { - case DockerContainer::Awg2: return tr("AmneziaWG protocol will be installed. " + case DockerContainer::Awg2: return QObject::tr("AmneziaWG protocol will be installed. " "It provides high connection speed and ensures stable operation even in the most challenging network conditions."); default: return ""; } } -int ContainerProps::easySetupOrder(DockerContainer container) +int ContainerUtils::easySetupOrder(DockerContainer container) { switch (container) { case DockerContainer::Awg2: return 1; @@ -384,7 +311,7 @@ int ContainerProps::easySetupOrder(DockerContainer container) } } -bool ContainerProps::isShareable(DockerContainer container) +bool ContainerUtils::isShareable(DockerContainer container) { switch (container) { case DockerContainer::TorWebSite: return false; @@ -395,28 +322,25 @@ bool ContainerProps::isShareable(DockerContainer container) } } -bool ContainerProps::isAwgContainer(DockerContainer container) +bool ContainerUtils::isAwgContainer(DockerContainer container) { return container == DockerContainer::Awg || container == DockerContainer::Awg2; } - -QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig) +QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig) { - QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol)) + QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol)) .toObject() - .value(config_key::last_config) + .value(configKey::lastConfig) .toString(); return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); } -int ContainerProps::installPageOrder(DockerContainer container) +int ContainerUtils::installPageOrder(DockerContainer container) { switch (container) { case DockerContainer::OpenVpn: return 4; - case DockerContainer::Cloak: return 5; - case DockerContainer::ShadowSocks: return 6; case DockerContainer::WireGuard: return 2; case DockerContainer::Awg2: return 1; case DockerContainer::Xray: return 3; @@ -425,3 +349,5 @@ int ContainerProps::installPageOrder(DockerContainer container) default: return 0; } } + + diff --git a/client/core/utils/containers/containerUtils.h b/client/core/utils/containers/containerUtils.h new file mode 100644 index 000000000..5dd2d4ea2 --- /dev/null +++ b/client/core/utils/containers/containerUtils.h @@ -0,0 +1,56 @@ +#ifndef CONTAINERUTILS_H +#define CONTAINERUTILS_H + +#include +#include +#include +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/constants/configKeys.h" + +namespace amnezia +{ + namespace ContainerUtils + { + DockerContainer containerFromString(const QString &container); + QString containerToString(DockerContainer container); + QString containerTypeToString(DockerContainer c); + QString containerTypeToProtocolString(DockerContainer c); + + QList allContainers(); + + QMap containerHumanNames(); + QMap containerDescriptions(); + QMap containerDetailedDescriptions(); + + ServiceType containerService(DockerContainer c); + + // binding between Docker container and main protocol of given container + // it may be changed fot future containers :) + Proto defaultProtocol(DockerContainer c); + + bool isSupportedByCurrentPlatform(DockerContainer c); + QStringList fixedPortsForContainer(DockerContainer c); + + bool isEasySetupContainer(DockerContainer container); + QString easySetupHeader(DockerContainer container); + QString easySetupDescription(DockerContainer container); + int easySetupOrder(DockerContainer container); + + bool isShareable(DockerContainer container); + + bool isAwgContainer(DockerContainer container); + + QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig); + + int installPageOrder(DockerContainer container); + } +} + +#endif // CONTAINERUTILS_H + + diff --git a/client/core/defs.h b/client/core/utils/errorCodes.h similarity index 78% rename from client/core/defs.h rename to client/core/utils/errorCodes.h index 731af38ed..00e8c6b20 100644 --- a/client/core/defs.h +++ b/client/core/utils/errorCodes.h @@ -1,38 +1,11 @@ -#ifndef DEFS_H -#define DEFS_H +#ifndef ERRORCODES_H +#define ERRORCODES_H #include #include namespace amnezia { - struct ServerCredentials - { - QString hostName; - QString userName; - QString secretData; - int port = 22; - - bool isValid() const - { - return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; - } - }; - - struct InstalledAppInfo { - QString appName; - QString packageName; - QString appPath; - - bool operator==(const InstalledAppInfo& other) const { - if (!packageName.isEmpty()) { - return packageName == other.packageName; - } else { - return appPath == other.appPath; - } - } - }; - namespace error_code_ns { Q_NAMESPACE @@ -80,8 +53,6 @@ namespace amnezia // Distro errors OpenVpnExecutableMissing = 600, - ShadowSocksExecutableMissing = 601, - CloakExecutableMissing = 602, AmneziaServiceConnectionFailed = 603, ExecutableMissing = 604, XrayExecutableMissing = 605, @@ -95,13 +66,13 @@ namespace amnezia // 3rd party utils errors OpenSslFailed = 800, - ShadowSocksExecutableCrashed = 801, - CloakExecutableCrashed = 802, XrayExecutableCrashed = 803, Tun2SockExecutableCrashed = 804, // import and install errors ImportInvalidConfigError = 900, + ImportBackupFileUseRestoreInstead = 903, + RestoreBackupInvalidError = 904, ImportOpenConfigError = 901, NoInstalledContainersError = 902, @@ -139,9 +110,10 @@ namespace amnezia } using ErrorCode = error_code_ns::ErrorCode; - -} // namespace amnezia +} Q_DECLARE_METATYPE(amnezia::ErrorCode) -#endif // DEFS_H +#endif // ERRORCODES_H + + diff --git a/client/core/errorstrings.cpp b/client/core/utils/errorStrings.cpp similarity index 94% rename from client/core/errorstrings.cpp rename to client/core/utils/errorStrings.cpp index 4f5262cde..591716ec9 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/utils/errorStrings.cpp @@ -1,4 +1,4 @@ -#include "errorstrings.h" +#include "errorStrings.h" using namespace amnezia; @@ -48,8 +48,6 @@ QString errorString(ErrorCode code) { // Distro errors case (ErrorCode::OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break; - case (ErrorCode::ShadowSocksExecutableMissing): errorMessage = QObject::tr("Shadowsocks (ss-local) executable missing"); break; - case (ErrorCode::CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break; case (ErrorCode::AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break; case (ErrorCode::OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break; @@ -59,8 +57,10 @@ QString errorString(ErrorCode code) { case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break; case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break; + 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::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; + case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break; // Android errors case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break; diff --git a/client/core/errorstrings.h b/client/core/utils/errorStrings.h similarity index 85% rename from client/core/errorstrings.h rename to client/core/utils/errorStrings.h index a3f817cb5..83726474d 100644 --- a/client/core/errorstrings.h +++ b/client/core/utils/errorStrings.h @@ -3,7 +3,7 @@ #include -#include "defs.h" +#include "core/utils/errorCodes.h" using namespace amnezia; diff --git a/client/core/installedAppsImageProvider.cpp b/client/core/utils/installedAppsImageProvider.cpp similarity index 100% rename from client/core/installedAppsImageProvider.cpp rename to client/core/utils/installedAppsImageProvider.cpp diff --git a/client/core/installedAppsImageProvider.h b/client/core/utils/installedAppsImageProvider.h similarity index 100% rename from client/core/installedAppsImageProvider.h rename to client/core/utils/installedAppsImageProvider.h diff --git a/client/core/ipcclient.cpp b/client/core/utils/ipcClient.cpp similarity index 99% rename from client/core/ipcclient.cpp rename to client/core/utils/ipcClient.cpp index d5ee3d7d3..607c2f392 100644 --- a/client/core/ipcclient.cpp +++ b/client/core/utils/ipcClient.cpp @@ -1,4 +1,4 @@ -#include "ipcclient.h" +#include "ipcClient.h" #include "ipc.h" #include #include diff --git a/client/core/ipcclient.h b/client/core/utils/ipcClient.h similarity index 100% rename from client/core/ipcclient.h rename to client/core/utils/ipcClient.h diff --git a/client/managementserver.cpp b/client/core/utils/managementServer.cpp similarity index 98% rename from client/managementserver.cpp rename to client/core/utils/managementServer.cpp index bb2e03839..36b70f2f1 100644 --- a/client/managementserver.cpp +++ b/client/core/utils/managementServer.cpp @@ -2,7 +2,7 @@ #include #include -#include "managementserver.h" +#include "core/utils/managementServer.h" ManagementServer::ManagementServer(QObject *parent) : QObject(parent), m_tcpServer(nullptr) diff --git a/client/managementserver.h b/client/core/utils/managementServer.h similarity index 100% rename from client/managementserver.h rename to client/core/utils/managementServer.h diff --git a/client/migrations.cpp b/client/core/utils/migrations.cpp similarity index 98% rename from client/migrations.cpp rename to client/core/utils/migrations.cpp index 8086d1db4..170a4fb11 100644 --- a/client/migrations.cpp +++ b/client/core/utils/migrations.cpp @@ -1,4 +1,4 @@ -#include "migrations.h" +#include "core/utils/migrations.h" #include #include diff --git a/client/migrations.h b/client/core/utils/migrations.h similarity index 100% rename from client/migrations.h rename to client/core/utils/migrations.h diff --git a/client/core/networkUtilities.cpp b/client/core/utils/networkUtilities.cpp similarity index 98% rename from client/core/networkUtilities.cpp rename to client/core/utils/networkUtilities.cpp index 1a52a974a..96b9131be 100644 --- a/client/core/networkUtilities.cpp +++ b/client/core/utils/networkUtilities.cpp @@ -50,12 +50,6 @@ QRegularExpression NetworkUtilities::ipAddressRegExp() return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); } -QRegularExpression NetworkUtilities::ipAddressPortRegExp() -{ - return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); -} - QRegExp NetworkUtilities::ipAddressWithSubnetRegExp() { return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" diff --git a/client/core/networkUtilities.h b/client/core/utils/networkUtilities.h similarity index 95% rename from client/core/networkUtilities.h rename to client/core/utils/networkUtilities.h index 102097568..ebd6aba0e 100644 --- a/client/core/networkUtilities.h +++ b/client/core/utils/networkUtilities.h @@ -22,7 +22,6 @@ public: static int AdapterIndexTo(const QHostAddress& dst); static QRegularExpression ipAddressRegExp(); - static QRegularExpression ipAddressPortRegExp(); static QRegExp ipAddressWithSubnetRegExp(); static QRegExp ipNetwork24RegExp(); static QRegExp ipPortRegExp(); diff --git a/client/core/osSignalHandler.cpp b/client/core/utils/osSignalHandler.cpp similarity index 99% rename from client/core/osSignalHandler.cpp rename to client/core/utils/osSignalHandler.cpp index e304f2aed..229ca8002 100644 --- a/client/core/osSignalHandler.cpp +++ b/client/core/utils/osSignalHandler.cpp @@ -4,7 +4,7 @@ #include #include -#include "../amnezia_application.h" +#include "../amneziaApplication.h" #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) #include diff --git a/client/core/osSignalHandler.h b/client/core/utils/osSignalHandler.h similarity index 100% rename from client/core/osSignalHandler.h rename to client/core/utils/osSignalHandler.h diff --git a/client/core/utils/protocolEnum.h b/client/core/utils/protocolEnum.h new file mode 100644 index 000000000..2c5d9663b --- /dev/null +++ b/client/core/utils/protocolEnum.h @@ -0,0 +1,50 @@ +#ifndef PROTOCOLENUM_H +#define PROTOCOLENUM_H + +#include +#include + +namespace amnezia +{ + namespace ProtocolEnumNS + { + Q_NAMESPACE + + enum TransportProto { + Udp, + Tcp, + TcpAndUdp + }; + Q_ENUM_NS(TransportProto) + + enum Proto { + Unknown = 0, + OpenVpn, + WireGuard, + Awg, + Ikev2, + Xray, + SSXray, + + // non-vpn + TorWebSite, + Dns, + Sftp, + Socks5Proxy + }; + Q_ENUM_NS(Proto) + + enum ServiceType { + None = 0, + Vpn, + Other + }; + Q_ENUM_NS(ServiceType) + } // namespace ProtocolEnumNS + + using namespace ProtocolEnumNS; +} + +#endif // PROTOCOLENUM_H + + diff --git a/client/core/qrCodeUtils.cpp b/client/core/utils/qrCodeUtils.cpp similarity index 100% rename from client/core/qrCodeUtils.cpp rename to client/core/utils/qrCodeUtils.cpp diff --git a/client/core/qrCodeUtils.h b/client/core/utils/qrCodeUtils.h similarity index 100% rename from client/core/qrCodeUtils.h rename to client/core/utils/qrCodeUtils.h diff --git a/client/core/utils/routeModes.h b/client/core/utils/routeModes.h new file mode 100644 index 000000000..e3d4de27a --- /dev/null +++ b/client/core/utils/routeModes.h @@ -0,0 +1,38 @@ +#ifndef ROUTEMODES_H +#define ROUTEMODES_H + +#include +#include + +namespace amnezia +{ + namespace route_mode_ns + { + Q_NAMESPACE + enum RouteMode { + VpnAllSites, + VpnOnlyForwardSites, + VpnAllExceptSites + }; + Q_ENUM_NS(RouteMode) + } + + using RouteMode = route_mode_ns::RouteMode; + + namespace apps_route_mode_ns + { + Q_NAMESPACE + enum AppsRouteMode { + VpnAllApps, + VpnOnlyForwardApps, + VpnAllExceptApps + }; + Q_ENUM_NS(AppsRouteMode) + } + + using AppsRouteMode = apps_route_mode_ns::AppsRouteMode; +} + +#endif // ROUTEMODES_H + + diff --git a/client/core/utils/selfhosted/scriptsRegistry.cpp b/client/core/utils/selfhosted/scriptsRegistry.cpp new file mode 100644 index 000000000..03d329ee4 --- /dev/null +++ b/client/core/utils/selfhosted/scriptsRegistry.cpp @@ -0,0 +1,292 @@ +#include "scriptsRegistry.h" + +#include +#include +#include +#include +#include +#include "core/utils/networkUtilities.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/models/containerConfig.h" +#include "core/models/protocols/openVpnProtocolConfig.h" +#include "core/models/protocols/wireGuardProtocolConfig.h" +#include "core/models/protocols/awgProtocolConfig.h" +#include "core/models/protocols/xrayProtocolConfig.h" +#include "core/models/protocols/sftpProtocolConfig.h" +#include "core/models/protocols/socks5ProxyProtocolConfig.h" + +using namespace amnezia; +using namespace ProtocolUtils; + +QString amnezia::scriptFolder(amnezia::DockerContainer container) +{ + switch (container) { + case DockerContainer::OpenVpn: return QLatin1String("openvpn"); + case DockerContainer::WireGuard: return QLatin1String("wireguard"); + case DockerContainer::Awg2: return QLatin1String("awg"); + case DockerContainer::Awg: return QLatin1String("awg_legacy"); + case DockerContainer::Ipsec: return QLatin1String("ipsec"); + case DockerContainer::Xray: return QLatin1String("xray"); + + case DockerContainer::TorWebSite: return QLatin1String("website_tor"); + case DockerContainer::Dns: return QLatin1String("dns"); + case DockerContainer::Sftp: return QLatin1String("sftp"); + case DockerContainer::Socks5Proxy: return QLatin1String("socks5_proxy"); + default: return QString(); + } +} + +QString amnezia::scriptName(SharedScriptType type) +{ + switch (type) { + case SharedScriptType::prepare_host: return QLatin1String("prepare_host.sh"); + case SharedScriptType::install_docker: return QLatin1String("install_docker.sh"); + case SharedScriptType::build_container: return QLatin1String("build_container.sh"); + case SharedScriptType::remove_container: return QLatin1String("remove_container.sh"); + case SharedScriptType::remove_all_containers: return QLatin1String("remove_all_containers.sh"); + case SharedScriptType::setup_host_firewall: return QLatin1String("setup_host_firewall.sh"); + case SharedScriptType::check_connection: return QLatin1String("check_connection.sh"); + case SharedScriptType::check_server_is_busy: return QLatin1String("check_server_is_busy.sh"); + case SharedScriptType::check_user_in_sudo: return QLatin1String("check_user_in_sudo.sh"); + default: return QString(); + } +} + +QString amnezia::scriptName(ProtocolScriptType type) +{ + switch (type) { + case ProtocolScriptType::dockerfile: return QLatin1String("Dockerfile"); + case ProtocolScriptType::run_container: return QLatin1String("run_container.sh"); + case ProtocolScriptType::configure_container: return QLatin1String("configure_container.sh"); + case ProtocolScriptType::container_startup: return QLatin1String("start.sh"); + case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn"); + case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf"); + case ProtocolScriptType::awg_template: return QLatin1String("template.conf"); + case ProtocolScriptType::xray_template: return QLatin1String("template.json"); + default: return QString(); + } +} + +QString amnezia::scriptData(amnezia::SharedScriptType type) +{ + QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type)); + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "Warning: script missing" << fileName; + return ""; + } + QByteArray ba = file.readAll(); + if (ba.isEmpty()) { + qDebug() << "Warning: script is empty" << fileName; + } + return ba; +} + +QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer container) +{ + QString fileName = QString(":/server_scripts/%1/%2").arg(amnezia::scriptFolder(container), amnezia::scriptName(type)); + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "Warning: script missing" << fileName; + return ""; + } + QByteArray data = file.readAll(); + data.replace("\r", ""); + return data; +} + +amnezia::ScriptVars amnezia::genBaseVars(const ServerCredentials &credentials, + DockerContainer container, + const QString &primaryDns, + const QString &secondaryDns) +{ + ScriptVars vars; + + vars.append({ { "$REMOTE_HOST", credentials.hostName } }); + vars.append({ { "$CONTAINER_NAME", ContainerUtils::containerToString(container) } }); + vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerUtils::containerToString(container) } }); + + QString serverIp = (!ContainerUtils::isAwgContainer(container) && container != DockerContainer::WireGuard && container != DockerContainer::Xray) + ? NetworkUtilities::getIPAddress(credentials.hostName) + : credentials.hostName; + if (!serverIp.isEmpty()) { + vars.append({ { "$SERVER_IP_ADDRESS", serverIp } }); + } else { + qWarning() << "amnezia::genBaseVars unable to resolve address for credentials.hostName"; + } + + QString dns1 = primaryDns.isEmpty() ? QString("8.8.8.8") : primaryDns; + QString dns2 = secondaryDns.isEmpty() ? QString("8.8.4.4") : secondaryDns; + vars.append({ { "$PRIMARY_SERVER_DNS", dns1 } }); + vars.append({ { "$SECONDARY_SERVER_DNS", dns2 } }); + + // IPsec vars (constants) + vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } }); + vars.append({ { "$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250" } }); + vars.append({ { "$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1" } }); + vars.append({ { "$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24" } }); + vars.append({ { "$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250" } }); + vars.append({ { "$IPSEC_VPN_SHA2_TRUNCBUG", "yes" } }); + vars.append({ { "$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_IKEV2", "no" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_L2TP", "no" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_XAUTH", "no" } }); + vars.append({ { "$IPSEC_VPN_C2C_TRAFFIC", "no" } }); + + return vars; +} + +amnezia::ScriptVars amnezia::genOpenVpnVars(const ContainerConfig &containerConfig) +{ + ScriptVars vars; + + if (auto* openVpnProtocolConfig = containerConfig.getOpenVpnProtocolConfig()) { + const OpenVpnServerConfig& config = openVpnProtocolConfig->serverConfig; + + vars.append({ { "$OPENVPN_SUBNET_IP", config.subnetAddress.isEmpty() ? protocols::openvpn::defaultSubnetAddress : config.subnetAddress } }); + vars.append({ { "$OPENVPN_SUBNET_CIDR", config.subnetCidr.isEmpty() ? protocols::openvpn::defaultSubnetCidr : config.subnetCidr } }); + vars.append({ { "$OPENVPN_SUBNET_MASK", config.subnetMask.isEmpty() ? protocols::openvpn::defaultSubnetMask : config.subnetMask } }); + vars.append({ { "$OPENVPN_PORT", config.port.isEmpty() ? protocols::openvpn::defaultPort : config.port } }); + vars.append({ { "$OPENVPN_TRANSPORT_PROTO", config.transportProto.isEmpty() ? protocols::openvpn::defaultTransportProto : config.transportProto } }); + + vars.append({ { "$OPENVPN_NCP_DISABLE", config.ncpDisable ? protocols::openvpn::ncpDisableString : "" } }); + vars.append({ { "$OPENVPN_CIPHER", config.cipher.isEmpty() ? protocols::openvpn::defaultCipher : config.cipher } }); + vars.append({ { "$OPENVPN_HASH", config.hash.isEmpty() ? protocols::openvpn::defaultHash : config.hash } }); + + vars.append({ { "$OPENVPN_TLS_AUTH", config.tlsAuth ? protocols::openvpn::tlsAuthString : "" } }); + if (!config.tlsAuth) { + vars.append({ { "$OPENVPN_TA_KEY", "" } }); + } + + vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG", config.additionalClientConfig.isEmpty() ? protocols::openvpn::defaultAdditionalClientConfig : config.additionalClientConfig } }); + vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG", config.additionalServerConfig.isEmpty() ? protocols::openvpn::defaultAdditionalServerConfig : config.additionalServerConfig } }); + } + + return vars; +} + +amnezia::ScriptVars amnezia::genXrayVars(const ContainerConfig &containerConfig) +{ + ScriptVars vars; + + if (auto* xrayProtocolConfig = containerConfig.getXrayProtocolConfig()) { + const XrayServerConfig& config = xrayProtocolConfig->serverConfig; + + vars.append({ { "$XRAY_SITE_NAME", config.site.isEmpty() ? protocols::xray::defaultSite : config.site } }); + vars.append({ { "$XRAY_SERVER_PORT", config.port.isEmpty() ? protocols::xray::defaultPort : config.port } }); + } + + return vars; +} + +amnezia::ScriptVars amnezia::genWireGuardVars(const ContainerConfig &containerConfig) +{ + ScriptVars vars; + + if (auto* wireGuardProtocolConfig = containerConfig.getWireGuardProtocolConfig()) { + const WireGuardServerConfig& config = wireGuardProtocolConfig->serverConfig; + + vars.append({ { "$WIREGUARD_SUBNET_IP", config.subnetAddress.isEmpty() ? protocols::wireguard::defaultSubnetAddress : config.subnetAddress } }); + vars.append({ { "$WIREGUARD_SUBNET_CIDR", config.subnetCidr.isEmpty() ? protocols::wireguard::defaultSubnetCidr : config.subnetCidr } }); + vars.append({ { "$WIREGUARD_SERVER_PORT", config.port.isEmpty() ? protocols::wireguard::defaultPort : config.port } }); + } + + return vars; +} + +amnezia::ScriptVars amnezia::genAwgVars(const ContainerConfig &containerConfig) +{ + ScriptVars vars; + + if (auto* awgProtocolConfig = containerConfig.getAwgProtocolConfig()) { + const AwgServerConfig& config = awgProtocolConfig->serverConfig; + + vars.append({ { "$AWG_SUBNET_IP", config.subnetAddress.isEmpty() ? protocols::wireguard::defaultSubnetAddress : config.subnetAddress } }); + vars.append({ { "$WIREGUARD_SUBNET_CIDR", config.subnetCidr.isEmpty() ? protocols::wireguard::defaultSubnetCidr : config.subnetCidr } }); + vars.append({ { "$AWG_SERVER_PORT", config.port.isEmpty() ? protocols::awg::defaultPort : config.port } }); + vars.append({ { "$JUNK_PACKET_COUNT", config.junkPacketCount } }); + vars.append({ { "$JUNK_PACKET_MIN_SIZE", config.junkPacketMinSize } }); + vars.append({ { "$JUNK_PACKET_MAX_SIZE", config.junkPacketMaxSize } }); + vars.append({ { "$INIT_PACKET_JUNK_SIZE", config.initPacketJunkSize } }); + vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", config.responsePacketJunkSize } }); + vars.append({ { "$INIT_PACKET_MAGIC_HEADER", config.initPacketMagicHeader } }); + vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", config.responsePacketMagicHeader } }); + vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", config.underloadPacketMagicHeader } }); + vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", config.transportPacketMagicHeader } }); + vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", config.cookieReplyPacketJunkSize } }); + vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", config.transportPacketJunkSize } }); + vars.append({ { "$SPECIAL_JUNK_1", config.specialJunk1 } }); + vars.append({ { "$SPECIAL_JUNK_2", config.specialJunk2 } }); + vars.append({ { "$SPECIAL_JUNK_3", config.specialJunk3 } }); + vars.append({ { "$SPECIAL_JUNK_4", config.specialJunk4 } }); + vars.append({ { "$SPECIAL_JUNK_5", config.specialJunk5 } }); + } + + return vars; +} + +amnezia::ScriptVars amnezia::genSftpVars(const ContainerConfig &containerConfig) +{ + ScriptVars vars; + + if (auto* sftpProtocolConfig = containerConfig.getSftpProtocolConfig()) { + vars.append({ { "$SFTP_PORT", sftpProtocolConfig->port.isEmpty() ? QString::number(ProtocolUtils::defaultPort(Proto::Sftp)) : sftpProtocolConfig->port } }); + vars.append({ { "$SFTP_USER", sftpProtocolConfig->userName } }); + vars.append({ { "$SFTP_PASSWORD", sftpProtocolConfig->password } }); + } + + return vars; +} + +amnezia::ScriptVars amnezia::genSocks5ProxyVars(const ContainerConfig &containerConfig) +{ + ScriptVars vars; + + if (auto* socks5ProxyProtocolConfig = containerConfig.getSocks5ProxyProtocolConfig()) { + vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyProtocolConfig->port.isEmpty() ? protocols::socks5Proxy::defaultPort : socks5ProxyProtocolConfig->port } }); + QString socks5user = (!socks5ProxyProtocolConfig->userName.isEmpty() && !socks5ProxyProtocolConfig->password.isEmpty()) + ? QString("users %1:CL:%2").arg(socks5ProxyProtocolConfig->userName, socks5ProxyProtocolConfig->password) + : ""; + vars.append({ { "$SOCKS5_USER", socks5user } }); + vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } }); + } + + return vars; +} + +amnezia::ScriptVars amnezia::genProtocolVarsForContainer(DockerContainer container, const ContainerConfig &containerConfig) +{ + ScriptVars vars; + Proto protocol = ContainerUtils::defaultProtocol(container); + + switch (protocol) { + case Proto::OpenVpn: + vars.append(genOpenVpnVars(containerConfig)); + break; + case Proto::Xray: + vars.append(genXrayVars(containerConfig)); + break; + case Proto::WireGuard: + vars.append(genWireGuardVars(containerConfig)); + break; + case Proto::Awg: + vars.append(genAwgVars(containerConfig)); + break; + case Proto::Sftp: + vars.append(genSftpVars(containerConfig)); + break; + case Proto::Socks5Proxy: + vars.append(genSocks5ProxyVars(containerConfig)); + break; + default: + break; + } + + return vars; +} diff --git a/client/core/utils/selfhosted/scriptsRegistry.h b/client/core/utils/selfhosted/scriptsRegistry.h new file mode 100644 index 000000000..e10862777 --- /dev/null +++ b/client/core/utils/selfhosted/scriptsRegistry.h @@ -0,0 +1,68 @@ +#ifndef SCRIPTSREGISTRY_H +#define SCRIPTSREGISTRY_H + +#include +#include +#include +#include +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/containerConfig.h" + +namespace amnezia { + +typedef QList> ScriptVars; + +enum SharedScriptType { + // General scripts + prepare_host, + install_docker, + build_container, + remove_container, + remove_all_containers, + setup_host_firewall, + check_connection, + check_server_is_busy, + check_user_in_sudo +}; +enum ProtocolScriptType { + // Protocol scripts + dockerfile, + run_container, + configure_container, + container_startup, + openvpn_template, + wireguard_template, + awg_template, + xray_template +}; + + +QString scriptFolder(DockerContainer container); + +QString scriptName(SharedScriptType type); +QString scriptName(ProtocolScriptType type); + +QString scriptData(SharedScriptType type); +QString scriptData(ProtocolScriptType type, DockerContainer container); + +ScriptVars genBaseVars(const ServerCredentials &credentials, + DockerContainer container, + const QString &primaryDns, + const QString &secondaryDns); + +ScriptVars genOpenVpnVars(const ContainerConfig &containerConfig); +ScriptVars genXrayVars(const ContainerConfig &containerConfig); +ScriptVars genWireGuardVars(const ContainerConfig &containerConfig); +ScriptVars genAwgVars(const ContainerConfig &containerConfig); +ScriptVars genSftpVars(const ContainerConfig &containerConfig); +ScriptVars genSocks5ProxyVars(const ContainerConfig &containerConfig); + +ScriptVars genProtocolVarsForContainer(DockerContainer container, const ContainerConfig &containerConfig); +} + +#endif // SCRIPTSREGISTRY_H diff --git a/client/core/sshclient.cpp b/client/core/utils/selfhosted/sshClient.cpp similarity index 99% rename from client/core/sshclient.cpp rename to client/core/utils/selfhosted/sshClient.cpp index 30322bb56..e8847a484 100644 --- a/client/core/sshclient.cpp +++ b/client/core/utils/selfhosted/sshClient.cpp @@ -1,4 +1,4 @@ -#include "sshclient.h" +#include "sshClient.h" #include #include diff --git a/client/core/sshclient.h b/client/core/utils/selfhosted/sshClient.h similarity index 94% rename from client/core/sshclient.h rename to client/core/utils/selfhosted/sshClient.h index 2ef26fb1a..7e40b6e6e 100644 --- a/client/core/sshclient.h +++ b/client/core/utils/selfhosted/sshClient.h @@ -8,7 +8,9 @@ #include -#include "defs.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" using namespace amnezia; diff --git a/client/core/utils/selfhosted/sshSession.cpp b/client/core/utils/selfhosted/sshSession.cpp new file mode 100644 index 000000000..5821ad232 --- /dev/null +++ b/client/core/utils/selfhosted/sshSession.cpp @@ -0,0 +1,243 @@ +#include "sshSession.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/networkUtilities.h" +#include "core/utils/selfhosted/scriptsRegistry.h" +#include "logger.h" +#include "core/utils/utilities.h" + +namespace +{ + Logger logger("SshSession"); +} + +SshSession::SshSession(QObject *parent) : QObject(parent) +{ +} + +SshSession::~SshSession() +{ + m_sshClient.disconnectFromHost(); +} + +ErrorCode SshSession::runScript(const ServerCredentials &credentials, QString script, + const std::function &cbReadStdOut, + const std::function &cbReadStdErr) +{ + + auto error = m_sshClient.connectToHost(credentials); + if (error != ErrorCode::NoError) { + return error; + } + + script.replace("\r", ""); + + qDebug() << "SshSession::Run script"; + + QString totalLine; + const QStringList &lines = script.split("\n", Qt::SkipEmptyParts); + for (int i = 0; i < lines.count(); i++) { + QString currentLine = lines.at(i); + + if (totalLine.isEmpty()) { + totalLine = currentLine; + } else { + totalLine = totalLine + "\n" + currentLine; + } + + QString lineToExec; + if (currentLine.endsWith("\\")) { + continue; + } else { + lineToExec = totalLine; + totalLine.clear(); + } + + if (lineToExec.startsWith("#")) { + continue; + } + + qDebug().noquote() << lineToExec; + + error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr); + if (error != ErrorCode::NoError) { + return error; + } + } + + qDebug().noquote() << "SshSession::runScript finished\n"; + return ErrorCode::NoError; +} + +ErrorCode SshSession::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, + const std::function &cbReadStdOut, + const std::function &cbReadStdErr) +{ + QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh"; + + ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName); + if (e) + return e; + + QString runner = + QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash")); + e = runScript(credentials, replaceVars(runner, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr); + + QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName); + runScript(credentials, replaceVars(remover, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr); + + return e; +} + +ErrorCode SshSession::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file, + const QString &path, libssh::ScpOverwriteMode overwriteMode) +{ + ErrorCode e = ErrorCode::NoError; + QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16)); + e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName); + if (e) + return e; + + QString stdOut; + auto cbReadStd = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + // mkdir + QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path); + + e = runScript(credentials, replaceVars(mkdir, amnezia::genBaseVars(credentials, container, QString(), QString()))); + if (e) + return e; + + if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) { + e = runScript(credentials, + replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path), + amnezia::genBaseVars(credentials, container, QString(), QString())), + cbReadStd, cbReadStd); + + if (e) + return e; + } else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) { + e = runScript(credentials, + replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName), + amnezia::genBaseVars(credentials, container, QString(), QString())), + cbReadStd, cbReadStd); + + if (e) + return e; + + e = runScript(credentials, + replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path), + amnezia::genBaseVars(credentials, container, QString(), QString())), + cbReadStd, cbReadStd); + + if (e) + return e; + } else + return ErrorCode::NotImplementedError; + + if (stdOut.contains("Error") && stdOut.contains("No such container")) { + return ErrorCode::ServerContainerMissingError; + } + + runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), amnezia::genBaseVars(credentials, container, QString(), QString()))); + return e; +} + +QByteArray SshSession::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, + ErrorCode &errorCode) +{ + + errorCode = ErrorCode::NoError; + + QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2'\"").arg(ContainerUtils::containerToString(container), path); + + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data; + return ErrorCode::NoError; + }; + + errorCode = runScript(credentials, script, cbReadStdOut); + return QByteArray::fromHex(stdOut.toUtf8()); +} + +ErrorCode SshSession::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, + libssh::ScpOverwriteMode overwriteMode) +{ + auto error = m_sshClient.connectToHost(credentials); + if (error != ErrorCode::NoError) { + return error; + } + + QTemporaryFile localFile; + localFile.open(); + localFile.write(data); + localFile.close(); + + error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc"); + + if (error != ErrorCode::NoError) { + return error; + } + return ErrorCode::NoError; +} + +QString SshSession::checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode) +{ + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; + + errorCode = runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); + + return stdOut; +} + +QString SshSession::replaceVars(const QString &script, const Vars &vars) +{ + QString s = script; + for (const QPair &var : vars) { + s.replace(var.first, var.second); + } + return s; +} + +ErrorCode SshSession::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, + const std::function &callback) +{ + auto error = m_sshClient.getDecryptedPrivateKey(credentials, decryptedPrivateKey, callback); + return error; +} diff --git a/client/core/utils/selfhosted/sshSession.h b/client/core/utils/selfhosted/sshSession.h new file mode 100644 index 000000000..2a738cb95 --- /dev/null +++ b/client/core/utils/selfhosted/sshSession.h @@ -0,0 +1,54 @@ +#ifndef SSHSESSION_H +#define SSHSESSION_H + +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/utils/selfhosted/sshClient.h" + +using namespace amnezia; + +class SshSession : public QObject +{ + Q_OBJECT +public: + SshSession(QObject *parent = nullptr); + ~SshSession(); + + typedef QList> Vars; + + ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file, + const QString &path, + libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting); + QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, + ErrorCode &errorCode); + + static QString replaceVars(const QString &script, const Vars &vars); + + ErrorCode runScript(const ServerCredentials &credentials, QString script, + const std::function &cbReadStdOut = nullptr, + const std::function &cbReadStdErr = nullptr); + + ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, + const std::function &cbReadStdOut = nullptr, + const std::function &cbReadStdErr = nullptr); + + QString checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode); + + ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, + const std::function &callback); + + ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, + libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting); + +private: + libssh::Client m_sshClient; +}; + +#endif // SSHSESSION_H diff --git a/client/core/serialization/inbound.cpp b/client/core/utils/serialization/inbound.cpp similarity index 100% rename from client/core/serialization/inbound.cpp rename to client/core/utils/serialization/inbound.cpp diff --git a/client/core/serialization/outbound.cpp b/client/core/utils/serialization/outbound.cpp similarity index 100% rename from client/core/serialization/outbound.cpp rename to client/core/utils/serialization/outbound.cpp diff --git a/client/core/serialization/serialization.h b/client/core/utils/serialization/serialization.h similarity index 100% rename from client/core/serialization/serialization.h rename to client/core/utils/serialization/serialization.h diff --git a/client/core/serialization/ss.cpp b/client/core/utils/serialization/ss.cpp similarity index 99% rename from client/core/serialization/ss.cpp rename to client/core/utils/serialization/ss.cpp index 84c9f3c33..5a100285c 100644 --- a/client/core/serialization/ss.cpp +++ b/client/core/utils/serialization/ss.cpp @@ -32,7 +32,7 @@ #include "3rd/QJsonStruct/QJsonIO.hpp" #include "3rd/QJsonStruct/QJsonStruct.hpp" -#include "utilities.h" +#include "core/utils/utilities.h" #include "serialization.h" #define OUTBOUND_TAG_PROXY "PROXY" diff --git a/client/core/serialization/ssd.cpp b/client/core/utils/serialization/ssd.cpp similarity index 99% rename from client/core/serialization/ssd.cpp rename to client/core/utils/serialization/ssd.cpp index 2d8adccd3..d9f58a9e4 100644 --- a/client/core/serialization/ssd.cpp +++ b/client/core/utils/serialization/ssd.cpp @@ -39,7 +39,7 @@ #include "3rd/QJsonStruct/QJsonIO.hpp" #include "3rd/QJsonStruct/QJsonStruct.hpp" -#include "utilities.h" +#include "core/utils/utilities.h" #include "serialization.h" const inline QString QV2RAY_SSD_DEFAULT_NAME_PATTERN = "%1 - %2 (rate %3)"; diff --git a/client/core/serialization/transfer.h b/client/core/utils/serialization/transfer.h similarity index 100% rename from client/core/serialization/transfer.h rename to client/core/utils/serialization/transfer.h diff --git a/client/core/serialization/trojan.cpp b/client/core/utils/serialization/trojan.cpp similarity index 100% rename from client/core/serialization/trojan.cpp rename to client/core/utils/serialization/trojan.cpp diff --git a/client/core/serialization/vless.cpp b/client/core/utils/serialization/vless.cpp similarity index 100% rename from client/core/serialization/vless.cpp rename to client/core/utils/serialization/vless.cpp diff --git a/client/core/serialization/vmess.cpp b/client/core/utils/serialization/vmess.cpp similarity index 99% rename from client/core/serialization/vmess.cpp rename to client/core/utils/serialization/vmess.cpp index c5258c8fc..240eec07c 100644 --- a/client/core/serialization/vmess.cpp +++ b/client/core/utils/serialization/vmess.cpp @@ -33,7 +33,7 @@ #include "3rd/QJsonStruct/QJsonStruct.hpp" #include #include "transfer.h" -#include "utilities.h" +#include "core/utils/utilities.h" #include "serialization.h" #define nothing diff --git a/client/core/serialization/vmess_new.cpp b/client/core/utils/serialization/vmess_new.cpp similarity index 100% rename from client/core/serialization/vmess_new.cpp rename to client/core/utils/serialization/vmess_new.cpp diff --git a/client/utilities.cpp b/client/core/utils/utilities.cpp similarity index 99% rename from client/utilities.cpp rename to client/core/utils/utilities.cpp index 51a9885a9..476fb27ed 100755 --- a/client/utilities.cpp +++ b/client/core/utils/utilities.cpp @@ -9,7 +9,7 @@ #include #include -#include "utilities.h" +#include "core/utils/utilities.h" #ifdef Q_OS_WINDOWS QString printErrorMessage(DWORD errorCode) { diff --git a/client/utilities.h b/client/core/utils/utilities.h similarity index 100% rename from client/utilities.h rename to client/core/utils/utilities.h diff --git a/client/images/flagKit.qrc b/client/images/flagKit.qrc new file mode 100644 index 000000000..9a3a69111 --- /dev/null +++ b/client/images/flagKit.qrc @@ -0,0 +1,253 @@ + + + flagKit/ZW.svg + flagKit/ZM.svg + flagKit/ZA.svg + flagKit/YT.svg + flagKit/YE.svg + flagKit/XK.svg + flagKit/WS.svg + flagKit/WF.svg + flagKit/VU.svg + flagKit/VN.svg + flagKit/VI.svg + flagKit/VG.svg + flagKit/VE.svg + flagKit/VC.svg + flagKit/VA.svg + flagKit/UZ.svg + flagKit/UY.svg + flagKit/US.svg + flagKit/UM.svg + flagKit/UG.svg + flagKit/UA.svg + flagKit/TZ.svg + flagKit/TW.svg + flagKit/TV.svg + flagKit/TT.svg + flagKit/TR.svg + flagKit/TO.svg + flagKit/TN.svg + flagKit/TM.svg + flagKit/TL.svg + flagKit/TK.svg + flagKit/TJ.svg + flagKit/TH.svg + flagKit/TG.svg + flagKit/TF.svg + flagKit/TD.svg + flagKit/TC.svg + flagKit/SZ.svg + flagKit/SY.svg + flagKit/SX.svg + flagKit/SV.svg + flagKit/ST.svg + flagKit/SS.svg + flagKit/SR.svg + flagKit/SO.svg + flagKit/SN.svg + flagKit/SM.svg + flagKit/SL.svg + flagKit/SK.svg + flagKit/SJ.svg + flagKit/SI.svg + flagKit/SH.svg + flagKit/SG.svg + flagKit/SE.svg + flagKit/SD.svg + flagKit/SC.svg + flagKit/SB.svg + flagKit/SA.svg + flagKit/RW.svg + flagKit/RU.svg + flagKit/RS.svg + flagKit/RO.svg + flagKit/RE.svg + flagKit/QA.svg + flagKit/PY.svg + flagKit/PW.svg + flagKit/PT.svg + flagKit/PS.svg + flagKit/PR.svg + flagKit/PN.svg + flagKit/PM.svg + flagKit/PL.svg + flagKit/PK.svg + flagKit/PH.svg + flagKit/PG.svg + flagKit/PF.svg + flagKit/PE.svg + flagKit/PA.svg + flagKit/OM.svg + flagKit/NZ.svg + flagKit/NU.svg + flagKit/NR.svg + flagKit/NP.svg + flagKit/NO.svg + flagKit/NL.svg + flagKit/NI.svg + flagKit/NG.svg + flagKit/NF.svg + flagKit/NE.svg + flagKit/NC.svg + flagKit/NA.svg + flagKit/MZ.svg + flagKit/MY.svg + flagKit/MX.svg + flagKit/MW.svg + flagKit/MV.svg + flagKit/MU.svg + flagKit/MT.svg + flagKit/MS.svg + flagKit/MR.svg + flagKit/MQ.svg + flagKit/MP.svg + flagKit/MO.svg + flagKit/MN.svg + flagKit/MM.svg + flagKit/ML.svg + flagKit/MK.svg + flagKit/MH.svg + flagKit/MG.svg + flagKit/MF.svg + flagKit/ME.svg + flagKit/MD.svg + flagKit/MC.svg + flagKit/MA.svg + flagKit/LY.svg + flagKit/LV.svg + flagKit/LU.svg + flagKit/LT.svg + flagKit/LS.svg + flagKit/LR.svg + flagKit/LK.svg + flagKit/LI.svg + flagKit/LC.svg + flagKit/LB.svg + flagKit/LA.svg + flagKit/KZ.svg + flagKit/KY.svg + flagKit/KW.svg + flagKit/KR.svg + flagKit/KP.svg + flagKit/KN.svg + flagKit/KM.svg + flagKit/KI.svg + flagKit/KH.svg + flagKit/KG.svg + flagKit/KE.svg + flagKit/JP.svg + flagKit/JO.svg + flagKit/JM.svg + flagKit/JE.svg + flagKit/IT.svg + flagKit/IS.svg + flagKit/IR.svg + flagKit/IQ.svg + flagKit/IO.svg + flagKit/IN.svg + flagKit/IM.svg + flagKit/IL.svg + flagKit/IE.svg + flagKit/ID.svg + flagKit/HU.svg + flagKit/HT.svg + flagKit/HR.svg + flagKit/HN.svg + flagKit/HM.svg + flagKit/HK.svg + flagKit/GY.svg + flagKit/GW.svg + flagKit/GU.svg + flagKit/GT.svg + flagKit/GS.svg + flagKit/GR.svg + flagKit/GQ.svg + flagKit/GP.svg + flagKit/GN.svg + flagKit/GM.svg + flagKit/GL.svg + flagKit/GI.svg + flagKit/GH.svg + flagKit/GG.svg + flagKit/GF.svg + flagKit/GE.svg + flagKit/GD.svg + flagKit/GB.svg + flagKit/GA.svg + flagKit/FR.svg + flagKit/FO.svg + flagKit/FM.svg + flagKit/FK.svg + flagKit/FJ.svg + flagKit/FI.svg + flagKit/EU.svg + flagKit/ET.svg + flagKit/ES.svg + flagKit/ER.svg + flagKit/EG.svg + flagKit/EE.svg + flagKit/EC.svg + flagKit/DZ.svg + flagKit/DO.svg + flagKit/DM.svg + flagKit/DK.svg + flagKit/DJ.svg + flagKit/DE.svg + flagKit/CZ.svg + flagKit/CY.svg + flagKit/CX.svg + flagKit/CW.svg + flagKit/CV.svg + flagKit/CU.svg + flagKit/CR.svg + flagKit/CO.svg + flagKit/CN.svg + flagKit/CM.svg + flagKit/CL.svg + flagKit/CK.svg + flagKit/CI.svg + flagKit/CH.svg + flagKit/CG.svg + flagKit/CF.svg + flagKit/CD.svg + flagKit/CC.svg + flagKit/CA.svg + flagKit/BZ.svg + flagKit/BY.svg + flagKit/BW.svg + flagKit/BV.svg + flagKit/BT.svg + flagKit/BS.svg + flagKit/BR.svg + flagKit/BO.svg + flagKit/BN.svg + flagKit/BM.svg + flagKit/BL.svg + flagKit/BJ.svg + flagKit/BI.svg + flagKit/BH.svg + flagKit/BG.svg + flagKit/BF.svg + flagKit/BE.svg + flagKit/BD.svg + flagKit/BB.svg + flagKit/BA.svg + flagKit/AZ.svg + flagKit/AX.svg + flagKit/AW.svg + flagKit/AU.svg + flagKit/AT.svg + flagKit/AS.svg + flagKit/AR.svg + flagKit/AO.svg + flagKit/AM.svg + flagKit/AL.svg + flagKit/AI.svg + flagKit/AG.svg + flagKit/AF.svg + flagKit/AE.svg + flagKit/AD.svg + + + diff --git a/client/images/images.qrc b/client/images/images.qrc new file mode 100644 index 000000000..6bbfb6308 --- /dev/null +++ b/client/images/images.qrc @@ -0,0 +1,74 @@ + + + ../fonts/pt-root-ui_vf.ttf + + + amneziaBigLogo.png + AmneziaVPN.png + controls/alert-circle.svg + controls/amnezia.svg + controls/app.svg + controls/archive-restore.svg + controls/arrow-left.svg + controls/arrow-right.svg + controls/bug.svg + controls/check.svg + controls/chevron-down.svg + controls/chevron-right.svg + controls/chevron-up.svg + controls/close.svg + controls/copy.svg + controls/delete.svg + controls/download.svg + controls/edit-3.svg + controls/eye-off.svg + controls/eye.svg + controls/external-link.svg + controls/file-check-2.svg + controls/file-cog-2.svg + controls/folder-open.svg + controls/folder-search-2.svg + controls/gauge.svg + controls/globe-2.svg + controls/github.svg + controls/help-circle.svg + controls/history.svg + controls/home.svg + controls/infinity.svg + controls/info.svg + controls/mail.svg + controls/map-pin.svg + controls/more-vertical.svg + controls/news.svg + controls/news-unread.svg + controls/unread-dot.svg + controls/plus.svg + controls/qr-code.svg + controls/radio-button-inner-circle-pressed.png + controls/radio-button-inner-circle.png + controls/radio-button-pressed.svg + controls/radio-button.svg + controls/radio.svg + controls/refresh-cw.svg + controls/save.svg + controls/scan-line.svg + controls/search.svg + controls/server.svg + controls/settings-2.svg + controls/settings.svg + controls/settings-news.svg + controls/share-2.svg + controls/split-tunneling.svg + controls/smartphone.svg + controls/tag.svg + controls/telegram.svg + controls/text-cursor.svg + controls/trash.svg + controls/x-circle.svg + tray/active.png + tray/default.png + tray/error.png + controls/monitor.svg + + + diff --git a/client/main.cpp b/client/main.cpp index 23d39fc54..53a1fd574 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -1,9 +1,9 @@ #include #include -#include "amnezia_application.h" -#include "core/osSignalHandler.h" -#include "migrations.h" +#include "amneziaApplication.h" +#include "core/utils/osSignalHandler.h" +#include "core/utils/migrations.h" #include "version.h" #include diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 17bf5a5e9..f1f9ca693 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -24,7 +24,10 @@ #include "logger.h" #include "daemon/daemonerrors.h" -#include "protocols/protocols_defs.h" +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" // How many times do we try to reconnect. constexpr int MAX_CONNECTION_RETRY = 10; @@ -124,18 +127,18 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { int splitTunnelType = rawConfig.value("splitTunnelType").toInt(); QJsonArray splitTunnelSites = rawConfig.value("splitTunnelSites").toArray(); - int appSplitTunnelType = rawConfig.value(amnezia::config_key::appSplitTunnelType).toInt(); - QJsonArray splitTunnelApps = rawConfig.value(amnezia::config_key::splitTunnelApps).toArray(); - QJsonArray allowedDns = rawConfig.value(amnezia::config_key::allowedDnsServers).toArray(); + int appSplitTunnelType = rawConfig.value(amnezia::configKey::appSplitTunnelType).toInt(); + QJsonArray splitTunnelApps = rawConfig.value(amnezia::configKey::splitTunnelApps).toArray(); + QJsonArray allowedDns = rawConfig.value(amnezia::configKey::allowedDnsServers).toArray(); QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject(); QJsonObject json; json.insert("type", "activate"); // json.insert("hopindex", QJsonValue((double)hop.m_hopindex)); - json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key)); - json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip)); - m_deviceIpv4 = wgConfig.value(amnezia::config_key::client_ip).toString(); + json.insert("privateKey", wgConfig.value(amnezia::configKey::clientPrivKey)); + json.insert("deviceIpv4Address", wgConfig.value(amnezia::configKey::clientIp)); + m_deviceIpv4 = wgConfig.value(amnezia::configKey::clientIp).toString(); // set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable. // this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4. @@ -146,27 +149,27 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { // simply "dead::1" is globally-routable, don't use it json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); - json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key)); - json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key)); - json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName)); + json.insert("serverPublicKey", wgConfig.value(amnezia::configKey::serverPubKey)); + json.insert("serverPskKey", wgConfig.value(amnezia::configKey::pskKey)); + json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::configKey::hostName)); // json.insert("serverIpv6AddrIn", QJsonValue(hop.m_server.ipv6AddrIn())); - json.insert("deviceMTU", wgConfig.value(amnezia::config_key::mtu)); + json.insert("deviceMTU", wgConfig.value(amnezia::configKey::mtu)); - json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt()); - json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName)); + json.insert("serverPort", wgConfig.value(amnezia::configKey::port).toInt()); + json.insert("serverIpv4Gateway", wgConfig.value(amnezia::configKey::hostName)); // json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway())); - json.insert("primaryDnsServer", rawConfig.value(amnezia::config_key::dns1)); + json.insert("primaryDnsServer", rawConfig.value(amnezia::configKey::dns1)); // We don't use secondary DNS if primary DNS is AmneziaDNS - if (!rawConfig.value(amnezia::config_key::dns1).toString(). + if (!rawConfig.value(amnezia::configKey::dns1).toString(). contains(amnezia::protocols::dns::amneziaDnsIp)) { - json.insert("secondaryDnsServer", rawConfig.value(amnezia::config_key::dns2)); + json.insert("secondaryDnsServer", rawConfig.value(amnezia::configKey::dns2)); } QJsonArray jsAllowedIPAddesses; - QJsonArray plainAllowedIP = wgConfig.value(amnezia::config_key::allowed_ips).toArray(); + QJsonArray plainAllowedIP = wgConfig.value(amnezia::configKey::allowedIps).toArray(); QJsonArray defaultAllowedIP = { "0.0.0.0/0", "::/0" }; if (plainAllowedIP != defaultAllowedIP && !plainAllowedIP.isEmpty()) { @@ -227,7 +230,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { json.insert("allowedIPAddressRanges", jsAllowedIPAddesses); QJsonArray jsExcludedAddresses; - jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName)); + jsExcludedAddresses.append(wgConfig.value(amnezia::configKey::hostName)); if (splitTunnelType == 2) { for (auto v : splitTunnelSites) { QString ipRange = v.toString(); @@ -241,52 +244,52 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { json.insert("allowedDnsServers", allowedDns); - json.insert(amnezia::config_key::killSwitchOption, rawConfig.value(amnezia::config_key::killSwitchOption)); + json.insert(amnezia::configKey::killSwitchOption, rawConfig.value(amnezia::configKey::killSwitchOption)); - if (protocolName == amnezia::config_key::awg) { - json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); - json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize)); - json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); - json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize)); - json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize)); - json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize)); - json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize)); - json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader)); - json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); - json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader)); - json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); - json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1)); - json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2)); - json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3)); - json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4)); - json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5)); - } else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined() - && !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined() - && !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined() - && !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined() - && !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined() - && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined() - && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined() - && !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined() - && !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined() - && !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined() - && !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()) { - json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); - json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize)); - json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); - json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize)); - json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize)); - json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize)); - json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize)); - json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader)); - json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); - json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader)); - json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); - json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1)); - json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2)); - json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3)); - json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4)); - json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5)); + if (protocolName == amnezia::configKey::awg) { + json.insert(amnezia::configKey::junkPacketCount, wgConfig.value(amnezia::configKey::junkPacketCount)); + json.insert(amnezia::configKey::junkPacketMinSize, wgConfig.value(amnezia::configKey::junkPacketMinSize)); + json.insert(amnezia::configKey::junkPacketMaxSize, wgConfig.value(amnezia::configKey::junkPacketMaxSize)); + json.insert(amnezia::configKey::initPacketJunkSize, wgConfig.value(amnezia::configKey::initPacketJunkSize)); + json.insert(amnezia::configKey::responsePacketJunkSize, wgConfig.value(amnezia::configKey::responsePacketJunkSize)); + json.insert(amnezia::configKey::cookieReplyPacketJunkSize, wgConfig.value(amnezia::configKey::cookieReplyPacketJunkSize)); + json.insert(amnezia::configKey::transportPacketJunkSize, wgConfig.value(amnezia::configKey::transportPacketJunkSize)); + json.insert(amnezia::configKey::initPacketMagicHeader, wgConfig.value(amnezia::configKey::initPacketMagicHeader)); + json.insert(amnezia::configKey::responsePacketMagicHeader, wgConfig.value(amnezia::configKey::responsePacketMagicHeader)); + json.insert(amnezia::configKey::underloadPacketMagicHeader, wgConfig.value(amnezia::configKey::underloadPacketMagicHeader)); + json.insert(amnezia::configKey::transportPacketMagicHeader, wgConfig.value(amnezia::configKey::transportPacketMagicHeader)); + json.insert(amnezia::configKey::specialJunk1, wgConfig.value(amnezia::configKey::specialJunk1)); + json.insert(amnezia::configKey::specialJunk2, wgConfig.value(amnezia::configKey::specialJunk2)); + json.insert(amnezia::configKey::specialJunk3, wgConfig.value(amnezia::configKey::specialJunk3)); + json.insert(amnezia::configKey::specialJunk4, wgConfig.value(amnezia::configKey::specialJunk4)); + json.insert(amnezia::configKey::specialJunk5, wgConfig.value(amnezia::configKey::specialJunk5)); + } else if (!wgConfig.value(amnezia::configKey::junkPacketCount).isUndefined() + && !wgConfig.value(amnezia::configKey::junkPacketMinSize).isUndefined() + && !wgConfig.value(amnezia::configKey::junkPacketMaxSize).isUndefined() + && !wgConfig.value(amnezia::configKey::initPacketJunkSize).isUndefined() + && !wgConfig.value(amnezia::configKey::responsePacketJunkSize).isUndefined() + && !wgConfig.value(amnezia::configKey::cookieReplyPacketJunkSize).isUndefined() + && !wgConfig.value(amnezia::configKey::transportPacketJunkSize).isUndefined() + && !wgConfig.value(amnezia::configKey::initPacketMagicHeader).isUndefined() + && !wgConfig.value(amnezia::configKey::responsePacketMagicHeader).isUndefined() + && !wgConfig.value(amnezia::configKey::underloadPacketMagicHeader).isUndefined() + && !wgConfig.value(amnezia::configKey::transportPacketMagicHeader).isUndefined()) { + json.insert(amnezia::configKey::junkPacketCount, wgConfig.value(amnezia::configKey::junkPacketCount)); + json.insert(amnezia::configKey::junkPacketMinSize, wgConfig.value(amnezia::configKey::junkPacketMinSize)); + json.insert(amnezia::configKey::junkPacketMaxSize, wgConfig.value(amnezia::configKey::junkPacketMaxSize)); + json.insert(amnezia::configKey::initPacketJunkSize, wgConfig.value(amnezia::configKey::initPacketJunkSize)); + json.insert(amnezia::configKey::responsePacketJunkSize, wgConfig.value(amnezia::configKey::responsePacketJunkSize)); + json.insert(amnezia::configKey::cookieReplyPacketJunkSize, wgConfig.value(amnezia::configKey::cookieReplyPacketJunkSize)); + json.insert(amnezia::configKey::transportPacketJunkSize, wgConfig.value(amnezia::configKey::transportPacketJunkSize)); + json.insert(amnezia::configKey::initPacketMagicHeader, wgConfig.value(amnezia::configKey::initPacketMagicHeader)); + json.insert(amnezia::configKey::responsePacketMagicHeader, wgConfig.value(amnezia::configKey::responsePacketMagicHeader)); + json.insert(amnezia::configKey::underloadPacketMagicHeader, wgConfig.value(amnezia::configKey::underloadPacketMagicHeader)); + json.insert(amnezia::configKey::transportPacketMagicHeader, wgConfig.value(amnezia::configKey::transportPacketMagicHeader)); + json.insert(amnezia::configKey::specialJunk1, wgConfig.value(amnezia::configKey::specialJunk1)); + json.insert(amnezia::configKey::specialJunk2, wgConfig.value(amnezia::configKey::specialJunk2)); + json.insert(amnezia::configKey::specialJunk3, wgConfig.value(amnezia::configKey::specialJunk3)); + json.insert(amnezia::configKey::specialJunk4, wgConfig.value(amnezia::configKey::specialJunk4)); + json.insert(amnezia::configKey::specialJunk5, wgConfig.value(amnezia::configKey::specialJunk5)); } write(json); diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index c6f538bd9..c7f972132 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -8,7 +8,7 @@ #include "android_controller.h" #include "android_utils.h" -#include "ui/controllers/importController.h" +#include "ui/controllers/importUiController.h" namespace { @@ -538,7 +538,7 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data) { Q_UNUSED(thiz); - return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data)); + return ImportUiController::decodeQrCode(AndroidUtils::convertJString(env, data)); } // static void AndroidController::onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp) diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 49360f02c..2294cc78b 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -4,7 +4,7 @@ #include #include -#include "protocols/vpnprotocol.h" +#include "core/protocols/vpnProtocol.h" using namespace amnezia; diff --git a/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift b/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift index 3983a96f9..b43f5a2bb 100644 --- a/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift +++ b/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift @@ -87,7 +87,6 @@ extension PacketTunnelProvider { } private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data, - withShadowSocks viaSS: Bool = false, completionHandler: @escaping (Error?) -> Void) { ovpnLog(.info, message: "Setup and launch") @@ -204,9 +203,6 @@ extension PacketTunnelProvider { let peerInfoSummary = peerInfo.keys.sorted().map { "\($0)=\(peerInfo[$0] ?? "")" }.joined(separator: " ") ovpnLog(.info, title: "PeerInfoOverride", message: peerInfoSummary) } - if configString.contains("cloak") { - configuration.setPTCloak() - } let evaluation: OpenVPNConfigurationEvaluation? do { diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index 898c4ce6d..cbb9ea8f5 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -1,7 +1,7 @@ #ifndef IOS_CONTROLLER_H #define IOS_CONTROLLER_H -#include "protocols/vpnprotocol.h" +#include "core/protocols/vpnProtocol.h" #include #include #include @@ -94,7 +94,6 @@ private: explicit IosController(); bool setupOpenVPN(); - bool setupCloak(); bool setupWireGuard(); bool setupAwg(); bool setupXray(); diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index d99752f45..73aa02484 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -8,7 +8,7 @@ #include #include -#include "../protocols/vpnprotocol.h" +#include "../core/protocols/vpnProtocol.h" #import "ios_controller_wrapper.h" #import "StoreKitController.h" @@ -28,6 +28,8 @@ const char* MessageKey::isOnDemand = "is-on-demand"; const char* MessageKey::SplitTunnelType = "SplitTunnelType"; const char* MessageKey::SplitTunnelSites = "SplitTunnelSites"; +using namespace ProtocolUtils; + #if !MACOS_NE static UIViewController* getViewController() { UIApplication *application = [UIApplication sharedApplication]; @@ -216,16 +218,16 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur { m_proto = proto; m_rawConfig = configuration; - m_serverAddress = configuration.value(config_key::hostName).toString().toNSString(); + m_serverAddress = configuration.value(configKey::hostName).toString().toNSString(); const QString serverDescription = configuration.value(config_key::description).toString().trimmed(); QString tunnelName; if (serverDescription.isEmpty()) { - tunnelName = ProtocolProps::protoToString(proto); + tunnelName = ProtocolUtils::protoToString(proto); } else { tunnelName = QString("%1 %2") .arg(serverDescription) - .arg(ProtocolProps::protoToString(proto)); + .arg(ProtocolUtils::protoToString(proto)); } qDebug() << "IosController::connectVpn" << tunnelName; @@ -296,9 +298,6 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur if (proto == amnezia::Proto::OpenVpn) { return setupOpenVPN(); } - if (proto == amnezia::Proto::Cloak) { - return setupCloak(); - } if (proto == amnezia::Proto::WireGuard) { return setupWireGuard(); } @@ -525,96 +524,27 @@ void IosController::vpnConfigurationDidChange(void *pNotification) bool IosController::setupOpenVPN() { - QJsonObject ovpn = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject(); - QString ovpnConfig = ovpn[config_key::config].toString(); + QJsonObject ovpn = m_rawConfig[ProtocolUtils::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject(); + QString ovpnConfig = ovpn[configKey::config].toString(); QJsonObject openVPNConfig {}; - openVPNConfig.insert(config_key::config, ovpnConfig); + openVPNConfig.insert(configKey::config, ovpnConfig); - if (ovpn.contains(config_key::mtu)) { - openVPNConfig.insert(config_key::mtu, ovpn[config_key::mtu]); + if (ovpn.contains(configKey::mtu)) { + openVPNConfig.insert(configKey::mtu, ovpn[configKey::mtu]); } else { - openVPNConfig.insert(config_key::mtu, protocols::openvpn::defaultMtu); + openVPNConfig.insert(configKey::mtu, protocols::openvpn::defaultMtu); } - openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]); + openVPNConfig.insert(configKey::splitTunnelType, m_rawConfig[configKey::splitTunnelType]); - QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray(); + QJsonArray splitTunnelSites = m_rawConfig[configKey::splitTunnelSites].toArray(); for(int index = 0; index < splitTunnelSites.count(); index++) { splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" "); } - openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites); - - QJsonDocument openVPNConfigDoc(openVPNConfig); - QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact)); - QString openVPNConfigPreview = openVPNConfigStr.left(512); - QString ovpnPreview = ovpnConfig.left(512); - - qDebug().noquote() << "IosController::setupOpenVPN payload" - << "jsonBytes=" << openVPNConfigStr.toUtf8().size() - << "ovpnChars=" << ovpnConfig.size() - << "splitTunnelType=" << m_rawConfig[config_key::splitTunnelType].toInt() - << "splitTunnelSites=" << splitTunnelSites; - qDebug().noquote() << "IosController::setupOpenVPN payload jsonPreview=" << openVPNConfigPreview; - qDebug().noquote() << "IosController::setupOpenVPN payload ovpnPreview=" << ovpnPreview; - - return startOpenVPN(openVPNConfigStr); -} - -bool IosController::setupCloak() -{ - m_serverAddress = @"127.0.0.1"; - QJsonObject ovpn = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject(); - QString ovpnConfig = ovpn[config_key::config].toString(); - - QJsonObject cloak = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Cloak)].toObject(); - - cloak["NumConn"] = 1; - if (cloak.contains("remote")) { - cloak["RemoteHost"] = cloak["remote"].toString(); - } - if (cloak.contains("port")) { - cloak["RemotePort"] = cloak["port"].toString(); - } - cloak.remove("remote"); - cloak.remove("port"); - cloak.remove("transport_proto"); - - QJsonObject jsonObject {}; - foreach(const QString& key, cloak.keys()) { - if(key == "NumConn" or key == "StreamTimeout"){ - jsonObject.insert(key, cloak.value(key).toInt()); - }else{ - jsonObject.insert(key, cloak.value(key).toString()); - } - } - QJsonDocument doc(jsonObject); - QString strJson(doc.toJson(QJsonDocument::Compact)); - QString cloakBase64 = strJson.toUtf8().toBase64(); - ovpnConfig.append("\n\n"); - ovpnConfig.append(cloakBase64); - ovpnConfig.append("\n\n"); - - QJsonObject openVPNConfig {}; - openVPNConfig.insert(config_key::config, ovpnConfig); - - if (ovpn.contains(config_key::mtu)) { - openVPNConfig.insert(config_key::mtu, ovpn[config_key::mtu]); - } else { - openVPNConfig.insert(config_key::mtu, protocols::openvpn::defaultMtu); - } - - openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]); - - QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray(); - - for(int index = 0; index < splitTunnelSites.count(); index++) { - splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" "); - } - - openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites); + openVPNConfig.insert(configKey::splitTunnelSites, splitTunnelSites); QJsonDocument openVPNConfigDoc(openVPNConfig); QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact)); @@ -624,61 +554,61 @@ bool IosController::setupCloak() bool IosController::setupWireGuard() { - QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::WireGuard)].toObject(); + QJsonObject config = m_rawConfig[ProtocolUtils::key_proto_config_data(amnezia::Proto::WireGuard)].toObject(); QJsonObject wgConfig {}; - wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]); - wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]); + wgConfig.insert(configKey::dns1, m_rawConfig[configKey::dns1]); + wgConfig.insert(configKey::dns2, m_rawConfig[configKey::dns2]); - if (config.contains(config_key::mtu)) { - wgConfig.insert(config_key::mtu, config[config_key::mtu]); + if (config.contains(configKey::mtu)) { + wgConfig.insert(configKey::mtu, config[configKey::mtu]); } else { - wgConfig.insert(config_key::mtu, protocols::wireguard::defaultMtu); + wgConfig.insert(configKey::mtu, protocols::wireguard::defaultMtu); } - wgConfig.insert(config_key::hostName, config[config_key::hostName]); - wgConfig.insert(config_key::port, config[config_key::port]); - wgConfig.insert(config_key::client_ip, config[config_key::client_ip]); - wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]); - wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]); - wgConfig.insert(config_key::psk_key, config[config_key::psk_key]); - wgConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]); + wgConfig.insert(configKey::hostName, config[configKey::hostName]); + wgConfig.insert(configKey::port, config[configKey::port]); + wgConfig.insert(configKey::clientIp, config[configKey::clientIp]); + wgConfig.insert(configKey::clientPrivKey, config[configKey::clientPrivKey]); + wgConfig.insert(configKey::serverPubKey, config[configKey::serverPubKey]); + wgConfig.insert(configKey::pskKey, config[configKey::pskKey]); + wgConfig.insert(configKey::splitTunnelType, m_rawConfig[configKey::splitTunnelType]); - QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray(); + QJsonArray splitTunnelSites = m_rawConfig[configKey::splitTunnelSites].toArray(); for(int index = 0; index < splitTunnelSites.count(); index++) { splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" "); } - wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites); + wgConfig.insert(configKey::splitTunnelSites, splitTunnelSites); - if (config.contains(config_key::allowed_ips) && config[config_key::allowed_ips].isArray()) { - wgConfig.insert(config_key::allowed_ips, config[config_key::allowed_ips]); + if (config.contains(configKey::allowedIps) && config[configKey::allowedIps].isArray()) { + wgConfig.insert(configKey::allowedIps, config[configKey::allowedIps]); } else { QJsonArray allowed_ips { "0.0.0.0/0", "::/0" }; - wgConfig.insert(config_key::allowed_ips, allowed_ips); + wgConfig.insert(configKey::allowedIps, allowed_ips); } - if (config.contains(config_key::persistent_keep_alive)) { - wgConfig.insert(config_key::persistent_keep_alive, config[config_key::persistent_keep_alive]); + if (config.contains(configKey::persistentKeepAlive)) { + wgConfig.insert(configKey::persistentKeepAlive, config[configKey::persistentKeepAlive]); } else { - wgConfig.insert(config_key::persistent_keep_alive, "25"); + wgConfig.insert(configKey::persistentKeepAlive, "25"); } - if (config.contains(config_key::isObfuscationEnabled) && config.value(config_key::isObfuscationEnabled).toBool()) { - wgConfig.insert(config_key::initPacketMagicHeader, config[config_key::initPacketMagicHeader]); - wgConfig.insert(config_key::responsePacketMagicHeader, config[config_key::responsePacketMagicHeader]); - wgConfig.insert(config_key::underloadPacketMagicHeader, config[config_key::underloadPacketMagicHeader]); - wgConfig.insert(config_key::transportPacketMagicHeader, config[config_key::transportPacketMagicHeader]); + if (config.contains(configKey::isObfuscationEnabled) && config.value(configKey::isObfuscationEnabled).toBool()) { + wgConfig.insert(configKey::initPacketMagicHeader, config[configKey::initPacketMagicHeader]); + wgConfig.insert(configKey::responsePacketMagicHeader, config[configKey::responsePacketMagicHeader]); + wgConfig.insert(configKey::underloadPacketMagicHeader, config[configKey::underloadPacketMagicHeader]); + wgConfig.insert(configKey::transportPacketMagicHeader, config[configKey::transportPacketMagicHeader]); - wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]); - wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]); - wgConfig.insert(config_key::cookieReplyPacketJunkSize, config[config_key::cookieReplyPacketJunkSize]); - wgConfig.insert(config_key::transportPacketJunkSize, config[config_key::transportPacketJunkSize]); + wgConfig.insert(configKey::initPacketJunkSize, config[configKey::initPacketJunkSize]); + wgConfig.insert(configKey::responsePacketJunkSize, config[configKey::responsePacketJunkSize]); + wgConfig.insert(configKey::cookieReplyPacketJunkSize, config[configKey::cookieReplyPacketJunkSize]); + wgConfig.insert(configKey::transportPacketJunkSize, config[configKey::transportPacketJunkSize]); - wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]); - wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]); - wgConfig.insert(config_key::junkPacketMaxSize, config[config_key::junkPacketMaxSize]); + wgConfig.insert(configKey::junkPacketCount, config[configKey::junkPacketCount]); + wgConfig.insert(configKey::junkPacketMinSize, config[configKey::junkPacketMinSize]); + wgConfig.insert(configKey::junkPacketMaxSize, config[configKey::junkPacketMaxSize]); } QJsonDocument wgConfigDoc(wgConfig); @@ -689,24 +619,22 @@ bool IosController::setupWireGuard() bool IosController::setupXray() { - QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Xray)].toObject(); - QJsonDocument xrayConfigDoc(config); - - QString xrayConfigStr(xrayConfigDoc.toJson(QJsonDocument::Compact)); + QJsonObject config = m_rawConfig[ProtocolUtils::key_proto_config_data(amnezia::Proto::Xray)].toObject(); + QString xrayConfigStr = config.value(configKey::config).toString(); QJsonObject finalConfig; - finalConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1].toString()); - finalConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2].toString()); - finalConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]); + finalConfig.insert(configKey::dns1, m_rawConfig[configKey::dns1].toString()); + finalConfig.insert(configKey::dns2, m_rawConfig[configKey::dns2].toString()); + finalConfig.insert(configKey::splitTunnelType, m_rawConfig[configKey::splitTunnelType]); - QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray(); + QJsonArray splitTunnelSites = m_rawConfig[configKey::splitTunnelSites].toArray(); - for(int index = 0; index < splitTunnelSites.count(); index++) { + for (int index = 0; index < splitTunnelSites.count(); index++) { splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" "); } - finalConfig.insert(config_key::splitTunnelSites, splitTunnelSites); - finalConfig.insert(config_key::config, xrayConfigStr); + finalConfig.insert(configKey::splitTunnelSites, splitTunnelSites); + finalConfig.insert(configKey::config, xrayConfigStr); QJsonDocument finalConfigDoc(finalConfig); QString finalConfigStr(finalConfigDoc.toJson(QJsonDocument::Compact)); @@ -716,15 +644,13 @@ bool IosController::setupXray() bool IosController::setupSSXray() { - QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::SSXray)].toObject(); - QJsonDocument ssXrayConfigDoc(config); - - QString ssXrayConfigStr(ssXrayConfigDoc.toJson(QJsonDocument::Compact)); + QJsonObject config = m_rawConfig[ProtocolUtils::key_proto_config_data(amnezia::Proto::SSXray)].toObject(); + QString ssXrayConfigStr = config.value(configKey::config).toString(); QJsonObject finalConfig; - finalConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]); - finalConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]); - finalConfig.insert(config_key::config, ssXrayConfigStr); + finalConfig.insert(configKey::dns1, m_rawConfig[configKey::dns1]); + finalConfig.insert(configKey::dns2, m_rawConfig[configKey::dns2]); + finalConfig.insert(configKey::config, ssXrayConfigStr); QJsonDocument finalConfigDoc(finalConfig); QString finalConfigStr(finalConfigDoc.toJson(QJsonDocument::Compact)); @@ -734,66 +660,66 @@ bool IosController::setupSSXray() bool IosController::setupAwg() { - QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject(); + QJsonObject config = m_rawConfig[ProtocolUtils::key_proto_config_data(amnezia::Proto::Awg)].toObject(); QJsonObject wgConfig {}; - wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]); - wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]); + wgConfig.insert(configKey::dns1, m_rawConfig[configKey::dns1]); + wgConfig.insert(configKey::dns2, m_rawConfig[configKey::dns2]); - if (config.contains(config_key::mtu)) { - wgConfig.insert(config_key::mtu, config[config_key::mtu]); + if (config.contains(configKey::mtu)) { + wgConfig.insert(configKey::mtu, config[configKey::mtu]); } else { - wgConfig.insert(config_key::mtu, protocols::awg::defaultMtu); + wgConfig.insert(configKey::mtu, protocols::awg::defaultMtu); } - wgConfig.insert(config_key::hostName, config[config_key::hostName]); - wgConfig.insert(config_key::port, config[config_key::port]); - wgConfig.insert(config_key::client_ip, config[config_key::client_ip]); - wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]); - wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]); - wgConfig.insert(config_key::psk_key, config[config_key::psk_key]); - wgConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]); + wgConfig.insert(configKey::hostName, config[configKey::hostName]); + wgConfig.insert(configKey::port, config[configKey::port]); + wgConfig.insert(configKey::clientIp, config[configKey::clientIp]); + wgConfig.insert(configKey::clientPrivKey, config[configKey::clientPrivKey]); + wgConfig.insert(configKey::serverPubKey, config[configKey::serverPubKey]); + wgConfig.insert(configKey::pskKey, config[configKey::pskKey]); + wgConfig.insert(configKey::splitTunnelType, m_rawConfig[configKey::splitTunnelType]); - QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray(); + QJsonArray splitTunnelSites = m_rawConfig[configKey::splitTunnelSites].toArray(); for(int index = 0; index < splitTunnelSites.count(); index++) { splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" "); } - wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites); + wgConfig.insert(configKey::splitTunnelSites, splitTunnelSites); - if (config.contains(config_key::allowed_ips) && config[config_key::allowed_ips].isArray()) { - wgConfig.insert(config_key::allowed_ips, config[config_key::allowed_ips]); + if (config.contains(configKey::allowedIps) && config[configKey::allowedIps].isArray()) { + wgConfig.insert(configKey::allowedIps, config[configKey::allowedIps]); } else { QJsonArray allowed_ips { "0.0.0.0/0", "::/0" }; - wgConfig.insert(config_key::allowed_ips, allowed_ips); + wgConfig.insert(configKey::allowedIps, allowed_ips); } - if (config.contains(config_key::persistent_keep_alive)) { - wgConfig.insert(config_key::persistent_keep_alive, config[config_key::persistent_keep_alive]); + if (config.contains(configKey::persistentKeepAlive)) { + wgConfig.insert(configKey::persistentKeepAlive, config[configKey::persistentKeepAlive]); } else { - wgConfig.insert(config_key::persistent_keep_alive, "25"); + wgConfig.insert(configKey::persistentKeepAlive, "25"); } - wgConfig.insert(config_key::initPacketMagicHeader, config[config_key::initPacketMagicHeader]); - wgConfig.insert(config_key::responsePacketMagicHeader, config[config_key::responsePacketMagicHeader]); - wgConfig.insert(config_key::underloadPacketMagicHeader, config[config_key::underloadPacketMagicHeader]); - wgConfig.insert(config_key::transportPacketMagicHeader, config[config_key::transportPacketMagicHeader]); + wgConfig.insert(configKey::initPacketMagicHeader, config[configKey::initPacketMagicHeader]); + wgConfig.insert(configKey::responsePacketMagicHeader, config[configKey::responsePacketMagicHeader]); + wgConfig.insert(configKey::underloadPacketMagicHeader, config[configKey::underloadPacketMagicHeader]); + wgConfig.insert(configKey::transportPacketMagicHeader, config[configKey::transportPacketMagicHeader]); - wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]); - wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]); - wgConfig.insert(config_key::cookieReplyPacketJunkSize, config[config_key::cookieReplyPacketJunkSize]); - wgConfig.insert(config_key::transportPacketJunkSize, config[config_key::transportPacketJunkSize]); + wgConfig.insert(configKey::initPacketJunkSize, config[configKey::initPacketJunkSize]); + wgConfig.insert(configKey::responsePacketJunkSize, config[configKey::responsePacketJunkSize]); + wgConfig.insert(configKey::cookieReplyPacketJunkSize, config[configKey::cookieReplyPacketJunkSize]); + wgConfig.insert(configKey::transportPacketJunkSize, config[configKey::transportPacketJunkSize]); - wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]); - wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]); - wgConfig.insert(config_key::junkPacketMaxSize, config[config_key::junkPacketMaxSize]); + wgConfig.insert(configKey::junkPacketCount, config[configKey::junkPacketCount]); + wgConfig.insert(configKey::junkPacketMinSize, config[configKey::junkPacketMinSize]); + wgConfig.insert(configKey::junkPacketMaxSize, config[configKey::junkPacketMaxSize]); - wgConfig.insert(config_key::specialJunk1, config[config_key::specialJunk1]); - wgConfig.insert(config_key::specialJunk2, config[config_key::specialJunk2]); - wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]); - wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]); - wgConfig.insert(config_key::specialJunk5, config[config_key::specialJunk5]); + wgConfig.insert(configKey::specialJunk1, config[configKey::specialJunk1]); + wgConfig.insert(configKey::specialJunk2, config[configKey::specialJunk2]); + wgConfig.insert(configKey::specialJunk3, config[configKey::specialJunk3]); + wgConfig.insert(configKey::specialJunk4, config[configKey::specialJunk4]); + wgConfig.insert(configKey::specialJunk5, config[configKey::specialJunk5]); QJsonDocument wgConfigDoc(wgConfig); QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact)); @@ -817,7 +743,7 @@ bool IosController::startOpenVPN(const QString &config) QJsonDocument doc = QJsonDocument::fromJson(config.toUtf8(), &parseError); if (parseError.error == QJsonParseError::NoError && doc.isObject()) { QJsonObject obj = doc.object(); - splitTunnelType = obj.value(config_key::splitTunnelType).toInt(0); + splitTunnelType = obj.value(configKey::splitTunnelType).toInt(0); } #if defined(MACOS_NE) // On macOS NE use route-based full tunnel. includeAllNetworks enables diff --git a/client/platforms/ios/iosnotificationhandler.h b/client/platforms/ios/iosnotificationhandler.h index 2845016aa..58f15ac8e 100644 --- a/client/platforms/ios/iosnotificationhandler.h +++ b/client/platforms/ios/iosnotificationhandler.h @@ -5,7 +5,7 @@ #ifndef IOSNOTIFICATIONHANDLER_H #define IOSNOTIFICATIONHANDLER_H -#include "ui/notificationhandler.h" +#include "ui/utils/notificationHandler.h" #include diff --git a/client/platforms/linux/daemon/linuxroutemonitor.cpp b/client/platforms/linux/daemon/linuxroutemonitor.cpp index eaf5bcd85..f943524df 100644 --- a/client/platforms/linux/daemon/linuxroutemonitor.cpp +++ b/client/platforms/linux/daemon/linuxroutemonitor.cpp @@ -19,10 +19,10 @@ #include #include -#include "../utilities.h" +#include "core/utils/utilities.h" #include "leakdetector.h" #include "logger.h" -#include "core/networkUtilities.h" +#include "core/utils/networkUtilities.h" namespace { Logger logger("LinuxRouteMonitor"); diff --git a/client/platforms/windows/daemon/windowsdaemon.cpp b/client/platforms/windows/daemon/windowsdaemon.cpp index a0b5e18c3..d8060fca7 100644 --- a/client/platforms/windows/daemon/windowsdaemon.cpp +++ b/client/platforms/windows/daemon/windowsdaemon.cpp @@ -24,7 +24,7 @@ #include "platforms/windows/daemon/windowssplittunnel.h" #include "windowsfirewall.h" -#include "core/networkUtilities.h" +#include "core/utils/networkUtilities.h" namespace { Logger logger("WindowsDaemon"); diff --git a/client/platforms/windows/daemon/windowsdaemontunnel.cpp b/client/platforms/windows/daemon/windowsdaemontunnel.cpp index c2adeb478..b8a8b5b60 100644 --- a/client/platforms/windows/daemon/windowsdaemontunnel.cpp +++ b/client/platforms/windows/daemon/windowsdaemontunnel.cpp @@ -9,7 +9,7 @@ #include //#include "commandlineparser.h" -#include "constants.h" +#include "core/utils/constants.h" #include "leakdetector.h" #include "logger.h" #include "platforms/windows/daemon/wireguardutilswindows.h" diff --git a/client/protocols/openvpnovercloakprotocol.cpp b/client/protocols/openvpnovercloakprotocol.cpp deleted file mode 100644 index 706e651a8..000000000 --- a/client/protocols/openvpnovercloakprotocol.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "openvpnovercloakprotocol.h" - -#include "utilities.h" -#include "containers/containers_defs.h" - -#include -#include -#include -#include - -OpenVpnOverCloakProtocol::OpenVpnOverCloakProtocol(const QJsonObject &configuration, QObject *parent): - OpenVpnProtocol(configuration, parent) -{ - readCloakConfiguration(configuration); -} - -OpenVpnOverCloakProtocol::~OpenVpnOverCloakProtocol() -{ - OpenVpnOverCloakProtocol::stop(); - m_ckProcess.close(); -} - -ErrorCode OpenVpnOverCloakProtocol::start() -{ - if (!QFileInfo::exists(cloakExecPath())) { - setLastError(ErrorCode::CloakExecutableMissing); - return lastError(); - } - - if (Utils::processIsRunning(Utils::executable("ck-client", false))) { - Utils::killProcessByName(Utils::executable("ck-client", false)); - } - - // workaround for desktop releases >= 3.0.7 - if (!m_cloakConfig.contains("RemoteHost") && m_cloakConfig.contains(config_key::remote)) { - m_cloakConfig["RemoteHost"] = m_cloakConfig.value(config_key::remote); - m_cloakConfig["RemotePort"] = m_cloakConfig.value(config_key::port); - } - -#ifdef QT_DEBUG - m_cloakCfgFile.setAutoRemove(false); -#endif - m_cloakCfgFile.open(); - m_cloakCfgFile.write(QJsonDocument(m_cloakConfig).toJson()); - m_cloakCfgFile.close(); - - QStringList args = QStringList() << "-c" << m_cloakCfgFile.fileName() - << "-l" << amnezia::protocols::openvpn::defaultPort; - - qDebug().noquote() << "OpenVpnOverCloakProtocol::start()" - << cloakExecPath() << args.join(" "); - - m_ckProcess.setProcessChannelMode(QProcess::MergedChannels); - - m_ckProcess.setProgram(cloakExecPath()); - m_ckProcess.setArguments(args); - - connect(&m_ckProcess, &QProcess::readyReadStandardOutput, this, [this](){ - qDebug().noquote() << "ck-client:" << m_ckProcess.readAllStandardOutput(); - }); - - m_errorHandlerConnection = connect(&m_ckProcess, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){ - qDebug().noquote() << "OpenVpnOverCloakProtocol finished, exitCode, exiStatus" << exitCode << exitStatus; - setConnectionState(Vpn::ConnectionState::Disconnected); - if (exitStatus != QProcess::NormalExit){ - emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed); - stop(); - } - if (exitCode !=0 ) { - emit protocolError(amnezia::ErrorCode::InternalError); - stop(); - } - }); - - m_ckProcess.start(); - m_ckProcess.waitForStarted(); - - if (m_ckProcess.state() == QProcess::ProcessState::Running) { - setConnectionState(Vpn::ConnectionState::Connecting); - - return OpenVpnProtocol::start(); - } - else return ErrorCode::CloakExecutableMissing; -} - -void OpenVpnOverCloakProtocol::stop() -{ - disconnect(m_errorHandlerConnection); - OpenVpnProtocol::stop(); - - qDebug() << "OpenVpnOverCloakProtocol::stop()"; - -#ifdef Q_OS_WIN - Utils::signalCtrl(m_ckProcess.processId(), CTRL_C_EVENT); -#endif - - m_ckProcess.terminate(); - - if (Utils::processIsRunning(Utils::executable("ck-client", false))) { - QThread::msleep(1000); - Utils::killProcessByName(Utils::executable("ck-client", false)); - } -} - -QString OpenVpnOverCloakProtocol::cloakExecPath() -{ -#ifdef Q_OS_WIN - return Utils::executable(QString("cloak/ck-client"), true); -#else - return Utils::executable(QString("/ck-client"), true); -#endif -} - -void OpenVpnOverCloakProtocol::readCloakConfiguration(const QJsonObject &configuration) -{ - m_cloakConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::Cloak)).toObject(); -} diff --git a/client/protocols/openvpnovercloakprotocol.h b/client/protocols/openvpnovercloakprotocol.h deleted file mode 100644 index 7fa3551e9..000000000 --- a/client/protocols/openvpnovercloakprotocol.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef OPENVPNOVERCLOAKPROTOCOL_H -#define OPENVPNOVERCLOAKPROTOCOL_H - -#include "openvpnprotocol.h" -#include "QProcess" - -class OpenVpnOverCloakProtocol : public OpenVpnProtocol -{ -public: - OpenVpnOverCloakProtocol(const QJsonObject& configuration, QObject* parent = nullptr); - virtual ~OpenVpnOverCloakProtocol() override; - - ErrorCode start() override; - void stop() override; - -protected: - void readCloakConfiguration(const QJsonObject &configuration); - -protected: - QJsonObject m_cloakConfig; - -private: - static QString cloakExecPath(); - -private: -#ifndef Q_OS_IOS - QProcess m_ckProcess; -#endif - QTemporaryFile m_cloakCfgFile; - QMetaObject::Connection m_errorHandlerConnection; -}; - -#endif // OPENVPNOVERCLOAKPROTOCOL_H diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h deleted file mode 100644 index c1cd868d8..000000000 --- a/client/protocols/protocols_defs.h +++ /dev/null @@ -1,334 +0,0 @@ -#ifndef PROTOCOLS_DEFS_H -#define PROTOCOLS_DEFS_H - -#include -#include -#include - -namespace amnezia -{ - namespace config_key - { - - // Json config strings - constexpr char hostName[] = "hostName"; - constexpr char userName[] = "userName"; - constexpr char password[] = "password"; - constexpr char port[] = "port"; - constexpr char local_port[] = "local_port"; - - constexpr char dns1[] = "dns1"; - constexpr char dns2[] = "dns2"; - - constexpr char serverIndex[] = "serverIndex"; - constexpr char description[] = "description"; - constexpr char name[] = "name"; - constexpr char cert[] = "cert"; - constexpr char config[] = "config"; - - constexpr char containers[] = "containers"; - constexpr char container[] = "container"; - constexpr char defaultContainer[] = "defaultContainer"; - - constexpr char vpnproto[] = "protocol"; - constexpr char protocols[] = "protocols"; - - constexpr char remote[] = "remote"; - constexpr char transport_proto[] = "transport_proto"; - constexpr char cipher[] = "cipher"; - constexpr char hash[] = "hash"; - constexpr char ncp_disable[] = "ncp_disable"; - constexpr char tls_auth[] = "tls_auth"; - - constexpr char client_priv_key[] = "client_priv_key"; - constexpr char client_pub_key[] = "client_pub_key"; - constexpr char server_priv_key[] = "server_priv_key"; - constexpr char server_pub_key[] = "server_pub_key"; - constexpr char psk_key[] = "psk_key"; - constexpr char mtu[] = "mtu"; - constexpr char allowed_ips[] = "allowed_ips"; - constexpr char persistent_keep_alive[] = "persistent_keep_alive"; - - constexpr char client_ip[] = "client_ip"; // internal ip address - - constexpr char site[] = "site"; - constexpr char block_outside_dns[] = "block_outside_dns"; - - constexpr char subnet_address[] = "subnet_address"; - constexpr char subnet_mask[] = "subnet_mask"; - constexpr char subnet_cidr[] = "subnet_cidr"; - - constexpr char additional_client_config[] = "additional_client_config"; - constexpr char additional_server_config[] = "additional_server_config"; - - // proto config keys - constexpr char last_config[] = "last_config"; - - constexpr char isThirdPartyConfig[] = "isThirdPartyConfig"; - constexpr char isObfuscationEnabled[] = "isObfuscationEnabled"; - - constexpr char junkPacketCount[] = "Jc"; - constexpr char junkPacketMinSize[] = "Jmin"; - constexpr char junkPacketMaxSize[] = "Jmax"; - constexpr char initPacketJunkSize[] = "S1"; - constexpr char responsePacketJunkSize[] = "S2"; - constexpr char cookieReplyPacketJunkSize[] = "S3"; - constexpr char transportPacketJunkSize[] = "S4"; - constexpr char initPacketMagicHeader[] = "H1"; - constexpr char responsePacketMagicHeader[] = "H2"; - constexpr char underloadPacketMagicHeader[] = "H3"; - constexpr char transportPacketMagicHeader[] = "H4"; - constexpr char specialJunk1[] = "I1"; - constexpr char specialJunk2[] = "I2"; - constexpr char specialJunk3[] = "I3"; - constexpr char specialJunk4[] = "I4"; - constexpr char specialJunk5[] = "I5"; - - constexpr char protocolVersion[] = "protocol_version"; - - constexpr char openvpn[] = "openvpn"; - constexpr char wireguard[] = "wireguard"; - constexpr char shadowsocks[] = "shadowsocks"; - constexpr char cloak[] = "cloak"; - constexpr char sftp[] = "sftp"; - constexpr char awg[] = "awg"; - constexpr char xray[] = "xray"; - constexpr char ssxray[] = "ssxray"; - constexpr char socks5proxy[] = "socks5proxy"; - - constexpr char configVersion[] = "config_version"; - - constexpr char splitTunnelSites[] = "splitTunnelSites"; - constexpr char splitTunnelType[] = "splitTunnelType"; - - constexpr char splitTunnelApps[] = "splitTunnelApps"; - constexpr char appSplitTunnelType[] = "appSplitTunnelType"; - - constexpr char allowedDnsServers[] = "allowedDnsServers"; - - constexpr char killSwitchOption[] = "killSwitchOption"; - - constexpr char crc[] = "crc"; - - constexpr char clientId[] = "clientId"; - - constexpr char nameOverriddenByUser[] = "nameOverriddenByUser"; - - } - - namespace protocols - { - - namespace dns - { - constexpr char amneziaDnsIp[] = "172.29.172.254"; - } - - namespace openvpn - { - constexpr char defaultSubnetAddress[] = "10.8.0.0"; - constexpr char defaultSubnetMask[] = "255.255.255.0"; - constexpr char defaultSubnetCidr[] = "24"; - constexpr char defaultMtu[] = "1500"; - - constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf"; - constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt"; - constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued"; - constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key"; - constexpr char clientsDirPath[] = "/opt/amnezia/openvpn/clients"; - constexpr char defaultPort[] = "1194"; - constexpr char defaultTransportProto[] = "udp"; - constexpr char defaultCipher[] = "AES-256-GCM"; - constexpr char defaultHash[] = "SHA512"; - constexpr bool defaultBlockOutsideDns = true; - constexpr bool defaultNcpDisable = false; - constexpr bool defaultTlsAuth = true; - constexpr char ncpDisableString[] = "ncp-disable"; - constexpr char tlsAuthString[] = "tls-auth /opt/amnezia/openvpn/ta.key 0"; - - constexpr char defaultAdditionalClientConfig[] = ""; - constexpr char defaultAdditionalServerConfig[] = ""; - } - - namespace shadowsocks - { - constexpr char ssKeyPath[] = "/opt/amnezia/shadowsocks/shadowsocks.key"; - constexpr char defaultPort[] = "6789"; - constexpr char defaultLocalProxyPort[] = "8585"; - constexpr char defaultCipher[] = "chacha20-ietf-poly1305"; - } - - namespace xray - { - constexpr char serverConfigPath[] = "/opt/amnezia/xray/server.json"; - constexpr char uuidPath[] = "/opt/amnezia/xray/xray_uuid.key"; - constexpr char PublicKeyPath[] = "/opt/amnezia/xray/xray_public.key"; - constexpr char PrivateKeyPath[] = "/opt/amnezia/xray/xray_private.key"; - constexpr char shortidPath[] = "/opt/amnezia/xray/xray_short_id.key"; - constexpr char defaultSite[] = "www.googletagmanager.com"; - - constexpr char defaultPort[] = "443"; - constexpr char defaultLocalProxyPort[] = "10808"; - constexpr char defaultLocalAddr[] = "10.33.0.2"; - } - - namespace cloak - { - constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key"; - constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key"; - constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key"; - constexpr char defaultPort[] = "443"; - constexpr char defaultRedirSite[] = "tile.openstreetmap.org"; - constexpr char defaultCipher[] = "chacha20-poly1305"; - } - - namespace wireguard - { - constexpr char defaultSubnetAddress[] = "10.8.1.0"; - constexpr char defaultSubnetMask[] = "255.255.255.0"; - constexpr char defaultSubnetCidr[] = "24"; - - constexpr char defaultPort[] = "51820"; - -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) - constexpr char defaultMtu[] = "1280"; -#else - constexpr char defaultMtu[] = "1376"; -#endif - constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf"; - constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key"; - constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key"; - - } - - namespace sftp - { - constexpr char defaultUserName[] = "sftp_user"; - - } // namespace sftp - - namespace awg - { - constexpr char defaultPort[] = "55424"; -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) - constexpr char defaultMtu[] = "1280"; -#else - constexpr char defaultMtu[] = "1376"; -#endif - - constexpr char serverConfigPath[] = "/opt/amnezia/awg/awg0.conf"; - constexpr char serverLegacyConfigPath[] = "/opt/amnezia/awg/wg0.conf"; - constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key"; - constexpr char serverPskKeyPath[] = "/opt/amnezia/awg/wireguard_psk.key"; - - constexpr char defaultJunkPacketCount[] = "3"; - constexpr char defaultJunkPacketMinSize[] = "10"; - constexpr char defaultJunkPacketMaxSize[] = "30"; - constexpr char defaultInitPacketJunkSize[] = "15"; - constexpr char defaultResponsePacketJunkSize[] = "18"; - constexpr char defaultCookieReplyPacketJunkSize[] = "20"; - constexpr char defaultTransportPacketJunkSize[] = "23"; - - constexpr char defaultInitPacketMagicHeader[] = "1020325451"; - constexpr char defaultResponsePacketMagicHeader[] = "3288052141"; - constexpr char defaultTransportPacketMagicHeader[] = "2528465083"; - constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858"; - constexpr char defaultSpecialJunk1[] = ""; - constexpr char defaultSpecialJunk2[] = ""; - constexpr char defaultSpecialJunk3[] = ""; - constexpr char defaultSpecialJunk4[] = ""; - constexpr char defaultSpecialJunk5[] = ""; - - constexpr char awgV1_5[] = "1.5"; - constexpr char awgV2[] = "2"; - } - - namespace socks5Proxy - { - constexpr char defaultUserName[] = "proxy_user"; - constexpr char defaultPort[] = "38080"; - - constexpr char proxyConfigPath[] = "/usr/local/3proxy/conf/3proxy.cfg"; - } - - } // namespace protocols - - namespace ProtocolEnumNS - { - Q_NAMESPACE - - enum TransportProto { - Udp, - Tcp, - TcpAndUdp - }; - Q_ENUM_NS(TransportProto) - - enum Proto { - Any = 0, - OpenVpn, - ShadowSocks, - Cloak, - WireGuard, - Awg, - Ikev2, - L2tp, - Xray, - SSXray, - - // non-vpn - TorWebSite, - Dns, - Sftp, - Socks5Proxy - }; - Q_ENUM_NS(Proto) - - enum ServiceType { - None = 0, - Vpn, - Other - }; - Q_ENUM_NS(ServiceType) - } // namespace ProtocolEnumNS - - using namespace ProtocolEnumNS; - - class ProtocolProps : public QObject - { - Q_OBJECT - - public: - Q_INVOKABLE static QList allProtocols(); - - // spelling may differ for various protocols - TCP for OpenVPN, tcp for others - Q_INVOKABLE static TransportProto transportProtoFromString(QString p); - Q_INVOKABLE static QString transportProtoToString(TransportProto proto, Proto p = Proto::Any); - - Q_INVOKABLE static Proto protoFromString(QString p); - Q_INVOKABLE static QString protoToString(Proto p); - - Q_INVOKABLE static QMap protocolHumanNames(); - Q_INVOKABLE static QMap protocolDescriptions(); - - Q_INVOKABLE static ServiceType protocolService(Proto p); - - Q_INVOKABLE static int getPortForInstall(Proto p); - - Q_INVOKABLE static int defaultPort(Proto p); - Q_INVOKABLE static bool defaultPortChangeable(Proto p); - - Q_INVOKABLE static TransportProto defaultTransportProto(Proto p); - Q_INVOKABLE static bool defaultTransportProtoChangeable(Proto p); - - Q_INVOKABLE static QString key_proto_config_data(Proto p); - Q_INVOKABLE static QString key_proto_config_path(Proto p); - - static QString getProtocolVersion(const QJsonObject &protocolConfig); - static QString getProtocolVersionString(const QJsonObject &protocolConfig); - }; -} // namespace amnezia - -QDebug operator<<(QDebug debug, const amnezia::Proto &p); - -#endif // PROTOCOLS_DEFS_H diff --git a/client/protocols/shadowsocksvpnprotocol.cpp b/client/protocols/shadowsocksvpnprotocol.cpp deleted file mode 100644 index 0ffc2768a..000000000 --- a/client/protocols/shadowsocksvpnprotocol.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "shadowsocksvpnprotocol.h" - -#include "logger.h" -#include "utilities.h" -#include "containers/containers_defs.h" - -#include -#include -#include - -ShadowSocksVpnProtocol::ShadowSocksVpnProtocol(const QJsonObject &configuration, QObject *parent): - OpenVpnProtocol(configuration, parent) -{ - readShadowSocksConfiguration(configuration); -} - -ShadowSocksVpnProtocol::~ShadowSocksVpnProtocol() -{ - qDebug() << "ShadowSocksVpnProtocol::~ShadowSocksVpnProtocol"; - ShadowSocksVpnProtocol::stop(); - QThread::msleep(200); -#ifndef Q_OS_IOS - m_ssProcess.close(); -#endif -} - -ErrorCode ShadowSocksVpnProtocol::start() -{ - - if (!QFileInfo::exists(shadowSocksExecPath())) { - setLastError(ErrorCode::ShadowSocksExecutableMissing); - return lastError(); - } - - -#ifndef Q_OS_IOS - if (Utils::processIsRunning(Utils::executable("ss-local", false))) { - Utils::killProcessByName(Utils::executable("ss-local", false)); - } - -#ifdef QT_DEBUG - m_shadowSocksCfgFile.setAutoRemove(false); -#endif - m_shadowSocksCfgFile.open(); - m_shadowSocksCfgFile.write(QJsonDocument(m_shadowSocksConfig).toJson()); - m_shadowSocksCfgFile.close(); - -#ifdef Q_OS_LINUX - QStringList args = QStringList() << "-c" << m_shadowSocksCfgFile.fileName(); -#else - QStringList args = QStringList() << "-c" << m_shadowSocksCfgFile.fileName() - << "--no-delay"; -#endif - - qDebug().noquote() << "ShadowSocksVpnProtocol::start()" - << shadowSocksExecPath() << args.join(" "); - - m_ssProcess.setProcessChannelMode(QProcess::MergedChannels); - - m_ssProcess.setProgram(shadowSocksExecPath()); - m_ssProcess.setArguments(args); - - connect(&m_ssProcess, &QProcess::readyReadStandardOutput, this, [this](){ - qDebug().noquote() << "ss-local:" << m_ssProcess.readAllStandardOutput(); - }); - - connect(&m_ssProcess, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){ - qDebug().noquote() << "ShadowSocksVpnProtocol finished, exitCode, exiStatus" << exitCode << exitStatus; - setConnectionState(Vpn::ConnectionState::Disconnected); - if (exitStatus != QProcess::NormalExit){ - emit protocolError(amnezia::ErrorCode::ShadowSocksExecutableCrashed); - stop(); - } - if (exitCode !=0 ){ - emit protocolError(amnezia::ErrorCode::InternalError); - stop(); - } - }); - - m_ssProcess.start(); - m_ssProcess.waitForStarted(); - - if (m_ssProcess.state() == QProcess::ProcessState::Running) { - setConnectionState(Vpn::ConnectionState::Connecting); - - return OpenVpnProtocol::start(); - } - else return ErrorCode::ShadowSocksExecutableMissing; -#else - return ErrorCode::NotImplementedError; -#endif -} - -void ShadowSocksVpnProtocol::stop() -{ - OpenVpnProtocol::stop(); - - qDebug() << "ShadowSocksVpnProtocol::stop()"; -#ifndef Q_OS_IOS - m_ssProcess.terminate(); -#endif - -#ifdef Q_OS_WIN - Utils::signalCtrl(m_ssProcess.processId(), CTRL_C_EVENT); -#endif -} - -QString ShadowSocksVpnProtocol::shadowSocksExecPath() -{ -#ifdef Q_OS_WIN - return Utils::executable(QString("ss/ss-local"), true); -#else - return Utils::executable(QString("/ss-local"), true); -#endif -} - -void ShadowSocksVpnProtocol::readShadowSocksConfiguration(const QJsonObject &configuration) -{ - QJsonObject shadowSocksConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::ShadowSocks)).toObject(); - bool isLocalPortConvertOk = false; - bool isServerPortConvertOk = false; - int localPort = shadowSocksConfig.value("local_port").toString().toInt(&isLocalPortConvertOk); - int serverPort = shadowSocksConfig.value("server_port").toString().toInt(&isServerPortConvertOk); - if (!isLocalPortConvertOk) { - qDebug() << "Error when converting local_port field in ShadowSocks config"; - } else if (!isServerPortConvertOk) { - qDebug() << "Error when converting server_port field in ShadowSocks config"; - } - shadowSocksConfig["local_port"] = localPort; - shadowSocksConfig["server_port"] = serverPort; - m_shadowSocksConfig = shadowSocksConfig; -} diff --git a/client/protocols/shadowsocksvpnprotocol.h b/client/protocols/shadowsocksvpnprotocol.h deleted file mode 100644 index e5851255d..000000000 --- a/client/protocols/shadowsocksvpnprotocol.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef SHADOWSOCKSVPNPROTOCOL_H -#define SHADOWSOCKSVPNPROTOCOL_H - -#include "openvpnprotocol.h" -#include "QProcess" -#include "containers/containers_defs.h" - -class ShadowSocksVpnProtocol : public OpenVpnProtocol -{ -public: - ShadowSocksVpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr); - virtual ~ShadowSocksVpnProtocol() override; - - ErrorCode start() override; - void stop() override; - -protected: - void readShadowSocksConfiguration(const QJsonObject &configuration); - -protected: - QJsonObject m_shadowSocksConfig; - -private: - static QString shadowSocksExecPath(); - -private: -#ifndef Q_OS_IOS - QProcess m_ssProcess; -#endif - QTemporaryFile m_shadowSocksCfgFile; -}; - -#endif // SHADOWSOCKSVPNPROTOCOL_H diff --git a/client/resources.qrc b/client/resources.qrc deleted file mode 100644 index 51b378af5..000000000 --- a/client/resources.qrc +++ /dev/null @@ -1,516 +0,0 @@ - - - fonts/pt-root-ui_vf.ttf - images/amneziaBigLogo.png - images/AmneziaVPN.png - images/controls/alert-circle.svg - images/controls/amnezia.svg - images/controls/app.svg - images/controls/archive-restore.svg - images/controls/arrow-left.svg - images/controls/arrow-right.svg - images/controls/bug.svg - images/controls/check.svg - images/controls/chevron-down.svg - images/controls/chevron-right.svg - images/controls/chevron-up.svg - images/controls/close.svg - images/controls/copy.svg - images/controls/delete.svg - images/controls/download.svg - images/controls/edit-3.svg - images/controls/eye-off.svg - images/controls/eye.svg - images/controls/external-link.svg - images/controls/file-check-2.svg - images/controls/file-cog-2.svg - images/controls/folder-open.svg - images/controls/folder-search-2.svg - images/controls/gauge.svg - images/controls/globe-2.svg - images/controls/github.svg - images/controls/help-circle.svg - images/controls/history.svg - images/controls/home.svg - images/controls/infinity.svg - images/controls/info.svg - images/controls/mail.svg - images/controls/map-pin.svg - images/controls/more-vertical.svg - images/controls/news.svg - images/controls/news-unread.svg - images/controls/unread-dot.svg - images/controls/plus.svg - images/controls/qr-code.svg - images/controls/radio-button-inner-circle-pressed.png - images/controls/radio-button-inner-circle.png - images/controls/radio-button-pressed.svg - images/controls/radio-button.svg - images/controls/radio.svg - images/controls/refresh-cw.svg - images/controls/save.svg - images/controls/scan-line.svg - images/controls/search.svg - images/controls/server.svg - images/controls/settings-2.svg - images/controls/settings.svg - images/controls/settings-news.svg - images/controls/share-2.svg - images/controls/split-tunneling.svg - images/controls/smartphone.svg - images/controls/tag.svg - images/controls/telegram.svg - images/controls/text-cursor.svg - images/controls/trash.svg - images/controls/x-circle.svg - images/tray/active.png - images/tray/default.png - images/tray/error.png - server_scripts/awg/configure_container.sh - server_scripts/awg/Dockerfile - server_scripts/awg/run_container.sh - server_scripts/awg/start.sh - server_scripts/awg/template.conf - server_scripts/awg_legacy/configure_container.sh - server_scripts/awg_legacy/Dockerfile - server_scripts/awg_legacy/run_container.sh - server_scripts/awg_legacy/start.sh - server_scripts/awg_legacy/template.conf - server_scripts/build_container.sh - server_scripts/check_connection.sh - server_scripts/check_server_is_busy.sh - server_scripts/check_user_in_sudo.sh - server_scripts/dns/configure_container.sh - server_scripts/dns/Dockerfile - server_scripts/dns/run_container.sh - server_scripts/install_docker.sh - server_scripts/ipsec/configure_container.sh - server_scripts/ipsec/Dockerfile - server_scripts/ipsec/mobileconfig.plist - server_scripts/ipsec/run_container.sh - server_scripts/ipsec/start.sh - server_scripts/ipsec/strongswan.profile - server_scripts/openvpn_cloak/configure_container.sh - server_scripts/openvpn_cloak/Dockerfile - server_scripts/openvpn_cloak/run_container.sh - server_scripts/openvpn_cloak/start.sh - server_scripts/openvpn_cloak/template.ovpn - server_scripts/openvpn_shadowsocks/configure_container.sh - server_scripts/openvpn_shadowsocks/Dockerfile - server_scripts/openvpn_shadowsocks/run_container.sh - server_scripts/openvpn_shadowsocks/start.sh - server_scripts/openvpn_shadowsocks/template.ovpn - server_scripts/openvpn/configure_container.sh - server_scripts/openvpn/Dockerfile - server_scripts/openvpn/run_container.sh - server_scripts/openvpn/start.sh - server_scripts/openvpn/template.ovpn - server_scripts/prepare_host.sh - server_scripts/remove_all_containers.sh - server_scripts/remove_container.sh - server_scripts/setup_host_firewall.sh - server_scripts/sftp/configure_container.sh - server_scripts/sftp/Dockerfile - server_scripts/sftp/run_container.sh - server_scripts/socks5_proxy/configure_container.sh - server_scripts/socks5_proxy/Dockerfile - server_scripts/socks5_proxy/run_container.sh - server_scripts/socks5_proxy/start.sh - server_scripts/website_tor/configure_container.sh - server_scripts/website_tor/Dockerfile - server_scripts/website_tor/run_container.sh - server_scripts/wireguard/configure_container.sh - server_scripts/wireguard/Dockerfile - server_scripts/wireguard/run_container.sh - server_scripts/wireguard/start.sh - server_scripts/wireguard/template.conf - server_scripts/xray/configure_container.sh - server_scripts/xray/Dockerfile - server_scripts/xray/run_container.sh - server_scripts/xray/start.sh - server_scripts/xray/template.json - ui/qml/Components/AdLabel.qml - ui/qml/Components/ConnectButton.qml - ui/qml/Components/ConnectionTypeSelectionDrawer.qml - ui/qml/Components/GamepadLoader.qml - ui/qml/Components/HomeContainersListView.qml - ui/qml/Components/HomeSplitTunnelingDrawer.qml - ui/qml/Components/InstalledAppsDrawer.qml - ui/qml/Components/BenefitRow.qml - ui/qml/Components/BenefitsPanel.qml - ui/qml/Components/SubscriptionPlanCard.qml - ui/qml/Components/TermsAndPrivacyText.qml - ui/qml/Components/QuestionDrawer.qml - ui/qml/Components/SelectLanguageDrawer.qml - ui/qml/Components/SubscriptionExpiredDrawer.qml - ui/qml/Components/ServersListView.qml - ui/qml/Components/SettingsContainersListView.qml - ui/qml/Components/TransportProtoSelector.qml - ui/qml/Components/AddSitePanel.qml - ui/qml/Config/GlobalConfig.qml - ui/qml/Config/qmldir - ui/qml/Controls2/BackButtonType.qml - ui/qml/Controls2/BasicButtonType.qml - ui/qml/Controls2/BusyIndicatorType.qml - ui/qml/Controls2/CardType.qml - ui/qml/Controls2/CardWithIconsType.qml - ui/qml/Controls2/CheckBoxType.qml - ui/qml/Controls2/ContextMenuType.qml - ui/qml/Controls2/DividerType.qml - ui/qml/Controls2/DrawerType2.qml - ui/qml/Controls2/DropDownType.qml - ui/qml/Controls2/FlickableType.qml - ui/qml/Controls2/Header2Type.qml - ui/qml/Controls2/BaseHeaderType.qml - ui/qml/Controls2/HeaderTypeWithButton.qml - ui/qml/Controls2/HeaderTypeWithSwitcher.qml - ui/qml/Controls2/HorizontalRadioButton.qml - ui/qml/Controls2/ImageButtonType.qml - ui/qml/Controls2/LabelWithButtonType.qml - ui/qml/Controls2/LabelWithImageType.qml - ui/qml/Controls2/ListViewWithLabelsType.qml - ui/qml/Controls2/ListViewWithRadioButtonType.qml - ui/qml/Controls2/PageType.qml - ui/qml/Controls2/PopupType.qml - ui/qml/Controls2/ProgressBarType.qml - ui/qml/Controls2/ScrollBarType.qml - ui/qml/Controls2/StackViewType.qml - ui/qml/Controls2/SwitcherType.qml - ui/qml/Controls2/TabButtonType.qml - ui/qml/Controls2/TabImageButtonType.qml - ui/qml/Controls2/TextAreaType.qml - ui/qml/Controls2/TextAreaWithFooterType.qml - ui/qml/Controls2/TextFieldWithHeaderType.qml - ui/qml/Controls2/TextTypes/ButtonTextType.qml - ui/qml/Controls2/TextTypes/CaptionTextType.qml - ui/qml/Controls2/TextTypes/Header1TextType.qml - ui/qml/Controls2/TextTypes/Header2TextType.qml - ui/qml/Controls2/TextTypes/LabelTextType.qml - ui/qml/Controls2/TextTypes/ListItemTitleType.qml - ui/qml/Controls2/TextTypes/ParagraphTextType.qml - ui/qml/Controls2/TextTypes/BadgeTextType.qml - ui/qml/Controls2/TextTypes/SmallTextType.qml - ui/qml/Controls2/TopCloseButtonType.qml - ui/qml/Controls2/VerticalRadioButton.qml - ui/qml/Controls2/WarningType.qml - ui/qml/Filters/ContainersModelFilters.qml - ui/qml/main2.qml - ui/qml/Modules/Style/AmneziaStyle.qml - ui/qml/Modules/Style/qmldir - ui/qml/Pages2/PageDeinstalling.qml - ui/qml/Pages2/PageDevMenu.qml - ui/qml/Pages2/PageHome.qml - ui/qml/Pages2/PageProtocolAwgSettings.qml - ui/qml/Pages2/PageProtocolCloakSettings.qml - ui/qml/Pages2/PageProtocolOpenVpnSettings.qml - ui/qml/Pages2/PageProtocolRaw.qml - ui/qml/Pages2/PageProtocolShadowSocksSettings.qml - ui/qml/Pages2/PageProtocolWireGuardSettings.qml - ui/qml/Pages2/PageProtocolXraySettings.qml - ui/qml/Pages2/PageServiceDnsSettings.qml - ui/qml/Pages2/PageServiceSftpSettings.qml - ui/qml/Pages2/PageServiceSocksProxySettings.qml - ui/qml/Pages2/PageServiceTorWebsiteSettings.qml - ui/qml/Pages2/PageSettings.qml - ui/qml/Pages2/PageSettingsAbout.qml - ui/qml/Pages2/PageSettingsApiAvailableCountries.qml - ui/qml/Pages2/PageSettingsApiServerInfo.qml - ui/qml/Pages2/PageSettingsApplication.qml - ui/qml/Pages2/PageSettingsAppSplitTunneling.qml - ui/qml/Pages2/PageSettingsBackup.qml - ui/qml/Pages2/PageSettingsConnection.qml - ui/qml/Pages2/PageSettingsDns.qml - ui/qml/Pages2/PageSettingsKillSwitch.qml - ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml - ui/qml/Pages2/PageSettingsLogging.qml - ui/qml/Pages2/PageSettingsServerData.qml - ui/qml/Pages2/PageSettingsServerInfo.qml - ui/qml/Pages2/PageSettingsServerProtocol.qml - ui/qml/Pages2/PageSettingsServerProtocols.qml - ui/qml/Pages2/PageSettingsServerServices.qml - ui/qml/Pages2/PageSettingsServersList.qml - ui/qml/Pages2/PageSettingsSplitTunneling.qml - ui/qml/Pages2/PageSettingsNewsNotifications.qml - ui/qml/Pages2/PageSettingsNewsDetail.qml - ui/qml/Pages2/PageProtocolAwgClientSettings.qml - ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml - ui/qml/Pages2/PageSetupWizardApiFreeInfo.qml - ui/qml/Pages2/PageSetupWizardApiPremiumInfo.qml - ui/qml/Pages2/PageSetupWizardApiTrialEmail.qml - ui/qml/Pages2/PageSetupWizardApiServicesList.qml - ui/qml/Pages2/PageSetupWizardConfigSource.qml - ui/qml/Pages2/PageSetupWizardCredentials.qml - ui/qml/Pages2/PageSetupWizardEasy.qml - ui/qml/Pages2/PageSetupWizardInstalling.qml - ui/qml/Pages2/PageSetupWizardProtocols.qml - ui/qml/Pages2/PageSetupWizardProtocolSettings.qml - ui/qml/Pages2/PageSetupWizardQrReader.qml - ui/qml/Pages2/PageSetupWizardStart.qml - ui/qml/Pages2/PageSetupWizardTextKey.qml - ui/qml/Pages2/PageSetupWizardViewConfig.qml - ui/qml/Pages2/PageShare.qml - ui/qml/Pages2/PageShareFullAccess.qml - ui/qml/Pages2/PageShareConnection.qml - ui/qml/Pages2/PageStart.qml - ui/qml/Components/RenameServerDrawer.qml - ui/qml/Controls2/ListViewType.qml - ui/qml/Pages2/PageSettingsApiSupport.qml - ui/qml/Pages2/PageSettingsApiInstructions.qml - ui/qml/Pages2/PageSettingsApiNativeConfigs.qml - ui/qml/Pages2/PageSettingsApiDevices.qml - images/controls/monitor.svg - ui/qml/Components/AwgTextField.qml - ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml - ui/qml/Components/SmartScroll.qml - - - images/flagKit/ZW.svg - images/flagKit/ZM.svg - images/flagKit/ZA.svg - images/flagKit/YT.svg - images/flagKit/YE.svg - images/flagKit/XK.svg - images/flagKit/WS.svg - images/flagKit/WF.svg - images/flagKit/VU.svg - images/flagKit/VN.svg - images/flagKit/VI.svg - images/flagKit/VG.svg - images/flagKit/VE.svg - images/flagKit/VC.svg - images/flagKit/VA.svg - images/flagKit/UZ.svg - images/flagKit/UY.svg - images/flagKit/US.svg - images/flagKit/UM.svg - images/flagKit/UG.svg - images/flagKit/UA.svg - images/flagKit/TZ.svg - images/flagKit/TW.svg - images/flagKit/TV.svg - images/flagKit/TT.svg - images/flagKit/TR.svg - images/flagKit/TO.svg - images/flagKit/TN.svg - images/flagKit/TM.svg - images/flagKit/TL.svg - images/flagKit/TK.svg - images/flagKit/TJ.svg - images/flagKit/TH.svg - images/flagKit/TG.svg - images/flagKit/TF.svg - images/flagKit/TD.svg - images/flagKit/TC.svg - images/flagKit/SZ.svg - images/flagKit/SY.svg - images/flagKit/SX.svg - images/flagKit/SV.svg - images/flagKit/ST.svg - images/flagKit/SS.svg - images/flagKit/SR.svg - images/flagKit/SO.svg - images/flagKit/SN.svg - images/flagKit/SM.svg - images/flagKit/SL.svg - images/flagKit/SK.svg - images/flagKit/SJ.svg - images/flagKit/SI.svg - images/flagKit/SH.svg - images/flagKit/SG.svg - images/flagKit/SE.svg - images/flagKit/SD.svg - images/flagKit/SC.svg - images/flagKit/SB.svg - images/flagKit/SA.svg - images/flagKit/RW.svg - images/flagKit/RU.svg - images/flagKit/RS.svg - images/flagKit/RO.svg - images/flagKit/RE.svg - images/flagKit/QA.svg - images/flagKit/PY.svg - images/flagKit/PW.svg - images/flagKit/PT.svg - images/flagKit/PS.svg - images/flagKit/PR.svg - images/flagKit/PN.svg - images/flagKit/PM.svg - images/flagKit/PL.svg - images/flagKit/PK.svg - images/flagKit/PH.svg - images/flagKit/PG.svg - images/flagKit/PF.svg - images/flagKit/PE.svg - images/flagKit/PA.svg - images/flagKit/OM.svg - images/flagKit/NZ.svg - images/flagKit/NU.svg - images/flagKit/NR.svg - images/flagKit/NP.svg - images/flagKit/NO.svg - images/flagKit/NL.svg - images/flagKit/NI.svg - images/flagKit/NG.svg - images/flagKit/NF.svg - images/flagKit/NE.svg - images/flagKit/NC.svg - images/flagKit/NA.svg - images/flagKit/MZ.svg - images/flagKit/MY.svg - images/flagKit/MX.svg - images/flagKit/MW.svg - images/flagKit/MV.svg - images/flagKit/MU.svg - images/flagKit/MT.svg - images/flagKit/MS.svg - images/flagKit/MR.svg - images/flagKit/MQ.svg - images/flagKit/MP.svg - images/flagKit/MO.svg - images/flagKit/MN.svg - images/flagKit/MM.svg - images/flagKit/ML.svg - images/flagKit/MK.svg - images/flagKit/MH.svg - images/flagKit/MG.svg - images/flagKit/MF.svg - images/flagKit/ME.svg - images/flagKit/MD.svg - images/flagKit/MC.svg - images/flagKit/MA.svg - images/flagKit/LY.svg - images/flagKit/LV.svg - images/flagKit/LU.svg - images/flagKit/LT.svg - images/flagKit/LS.svg - images/flagKit/LR.svg - images/flagKit/LK.svg - images/flagKit/LI.svg - images/flagKit/LC.svg - images/flagKit/LB.svg - images/flagKit/LA.svg - images/flagKit/KZ.svg - images/flagKit/KY.svg - images/flagKit/KW.svg - images/flagKit/KR.svg - images/flagKit/KP.svg - images/flagKit/KN.svg - images/flagKit/KM.svg - images/flagKit/KI.svg - images/flagKit/KH.svg - images/flagKit/KG.svg - images/flagKit/KE.svg - images/flagKit/JP.svg - images/flagKit/JO.svg - images/flagKit/JM.svg - images/flagKit/JE.svg - images/flagKit/IT.svg - images/flagKit/IS.svg - images/flagKit/IR.svg - images/flagKit/IQ.svg - images/flagKit/IO.svg - images/flagKit/IN.svg - images/flagKit/IM.svg - images/flagKit/IL.svg - images/flagKit/IE.svg - images/flagKit/ID.svg - images/flagKit/HU.svg - images/flagKit/HT.svg - images/flagKit/HR.svg - images/flagKit/HN.svg - images/flagKit/HM.svg - images/flagKit/HK.svg - images/flagKit/GY.svg - images/flagKit/GW.svg - images/flagKit/GU.svg - images/flagKit/GT.svg - images/flagKit/GS.svg - images/flagKit/GR.svg - images/flagKit/GQ.svg - images/flagKit/GP.svg - images/flagKit/GN.svg - images/flagKit/GM.svg - images/flagKit/GL.svg - images/flagKit/GI.svg - images/flagKit/GH.svg - images/flagKit/GG.svg - images/flagKit/GF.svg - images/flagKit/GE.svg - images/flagKit/GD.svg - images/flagKit/GB.svg - images/flagKit/GA.svg - images/flagKit/FR.svg - images/flagKit/FO.svg - images/flagKit/FM.svg - images/flagKit/FK.svg - images/flagKit/FJ.svg - images/flagKit/FI.svg - images/flagKit/EU.svg - images/flagKit/ET.svg - images/flagKit/ES.svg - images/flagKit/ER.svg - images/flagKit/EG.svg - images/flagKit/EE.svg - images/flagKit/EC.svg - images/flagKit/DZ.svg - images/flagKit/DO.svg - images/flagKit/DM.svg - images/flagKit/DK.svg - images/flagKit/DJ.svg - images/flagKit/DE.svg - images/flagKit/CZ.svg - images/flagKit/CY.svg - images/flagKit/CX.svg - images/flagKit/CW.svg - images/flagKit/CV.svg - images/flagKit/CU.svg - images/flagKit/CR.svg - images/flagKit/CO.svg - images/flagKit/CN.svg - images/flagKit/CM.svg - images/flagKit/CL.svg - images/flagKit/CK.svg - images/flagKit/CI.svg - images/flagKit/CH.svg - images/flagKit/CG.svg - images/flagKit/CF.svg - images/flagKit/CD.svg - images/flagKit/CC.svg - images/flagKit/CA.svg - images/flagKit/BZ.svg - images/flagKit/BY.svg - images/flagKit/BW.svg - images/flagKit/BV.svg - images/flagKit/BT.svg - images/flagKit/BS.svg - images/flagKit/BR.svg - images/flagKit/BO.svg - images/flagKit/BN.svg - images/flagKit/BM.svg - images/flagKit/BL.svg - images/flagKit/BJ.svg - images/flagKit/BI.svg - images/flagKit/BH.svg - images/flagKit/BG.svg - images/flagKit/BF.svg - images/flagKit/BE.svg - images/flagKit/BD.svg - images/flagKit/BB.svg - images/flagKit/BA.svg - images/flagKit/AZ.svg - images/flagKit/AX.svg - images/flagKit/AW.svg - images/flagKit/AU.svg - images/flagKit/AT.svg - images/flagKit/AS.svg - images/flagKit/AR.svg - images/flagKit/AO.svg - images/flagKit/AM.svg - images/flagKit/AL.svg - images/flagKit/AI.svg - images/flagKit/AG.svg - images/flagKit/AF.svg - images/flagKit/AE.svg - images/flagKit/AD.svg - - diff --git a/client/secure_qsettings.cpp b/client/secureQSettings.cpp similarity index 92% rename from client/secure_qsettings.cpp rename to client/secureQSettings.cpp index 4104e1e00..6c6c6b0a7 100644 --- a/client/secure_qsettings.cpp +++ b/client/secureQSettings.cpp @@ -1,8 +1,8 @@ -#include "secure_qsettings.h" +#include "secureQSettings.h" #include "../client/3rd/QSimpleCrypto/src/include/QAead.h" #include "../client/3rd/QSimpleCrypto/src/include/QBlockCipher.h" -#include "utilities.h" +#include "core/utils/utilities.h" #include #include #include @@ -21,8 +21,8 @@ namespace { constexpr const char *keyChainName = "AmneziaVPN-Keychain"; } -SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent) - : QObject { parent }, m_settings(organization, application, parent), encryptedKeys({ "Servers/serversList" }) +SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent, bool enableEncryption) + : QObject { parent }, m_settings(organization, application, parent), encryptedKeys({ "Servers/serversList" }), m_encryptionEnabled(enableEncryption) { bool encrypted = m_settings.value("Conf/encrypted").toBool(); @@ -204,6 +204,9 @@ QByteArray SecureQSettings::decryptText(const QByteArray &ba) const bool SecureQSettings::encryptionRequired() const { + if (!m_encryptionEnabled) { + return false; + } #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) // QtKeyChain failing on Linux return false; @@ -213,6 +216,10 @@ bool SecureQSettings::encryptionRequired() const QByteArray SecureQSettings::getEncKey() const { + + if (!m_key.isEmpty()) { + return m_key; + } // load keys from system key storage m_key = getSecTag(settingsKeyTag); @@ -239,6 +246,9 @@ QByteArray SecureQSettings::getEncKey() const QByteArray SecureQSettings::getEncIv() const { + if (!m_iv.isEmpty()) { + return m_iv; + } // load keys from system key storage m_iv = getSecTag(settingsIvTag); diff --git a/client/secure_qsettings.h b/client/secureQSettings.h similarity index 87% rename from client/secure_qsettings.h rename to client/secureQSettings.h index e8e267d65..9247b71bd 100644 --- a/client/secure_qsettings.h +++ b/client/secureQSettings.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -14,7 +15,7 @@ class SecureQSettings : public QObject public: explicit SecureQSettings(const QString &organization, const QString &application = QString(), - QObject *parent = nullptr); + QObject *parent = nullptr, bool enableEncryption = true); QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; void setValue(const QString &key, const QVariant &value); @@ -52,6 +53,8 @@ private: const QByteArray magicString { "EncData" }; // Magic keyword used for mark encrypted QByteArray + bool m_encryptionEnabled; + mutable QRecursiveMutex m_mutex; }; diff --git a/client/server_scripts/openvpn_cloak/Dockerfile b/client/server_scripts/openvpn_cloak/Dockerfile deleted file mode 100644 index 0dac6ed6d..000000000 --- a/client/server_scripts/openvpn_cloak/Dockerfile +++ /dev/null @@ -1,63 +0,0 @@ -FROM alpine:3.15 -LABEL maintainer="AmneziaVPN" - -ARG SS_RELEASE="v1.18.1" -ARG CLOAK_RELEASE="v2.8.0" - -#Install required packages -RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools -RUN apk --update upgrade --no-cache - -ENV EASYRSA_BATCH 1 -ENV PATH="/usr/share/easy-rsa:${PATH}" - -RUN mkdir -p /opt/amnezia -RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh -RUN chmod a+x /opt/amnezia/start.sh - -RUN SERVER_ARCH=$(uname -m) && \ - if [ $SERVER_ARCH="x86_64" ]; then CK_ARCH="amd64"; \ - elif [ $SERVER_ARCH="i686" ]; then CK_ARCH="386"; \ - elif [ $SERVER_ARCH="aarch64" ]; then CK_ARCH="arm64"; \ - elif [ $SERVER_ARCH="arm" ]; then CK_ARCH="arm"; \ - else exit -1; fi && \ - curl -L https://github.com/cbeuw/Cloak/releases/download/${CLOAK_RELEASE}/ck-server-linux-${CK_ARCH}-${CLOAK_RELEASE} > /usr/bin/ck-server && \ - chmod a+x /usr/bin/ck-server && \ - curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz && \ - tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/ && \ - chmod a+x /usr/bin/ssserver - -# Tune network -RUN echo -e " \n\ - fs.file-max = 51200 \n\ - \n\ - net.core.rmem_max = 67108864 \n\ - net.core.wmem_max = 67108864 \n\ - net.core.netdev_max_backlog = 250000 \n\ - net.core.somaxconn = 4096 \n\ - \n\ - net.ipv4.tcp_syncookies = 1 \n\ - net.ipv4.tcp_tw_reuse = 1 \n\ - net.ipv4.tcp_tw_recycle = 0 \n\ - net.ipv4.tcp_fin_timeout = 30 \n\ - net.ipv4.tcp_keepalive_time = 1200 \n\ - net.ipv4.ip_local_port_range = 10000 65000 \n\ - net.ipv4.tcp_max_syn_backlog = 8192 \n\ - net.ipv4.tcp_max_tw_buckets = 5000 \n\ - net.ipv4.tcp_fastopen = 3 \n\ - net.ipv4.tcp_mem = 25600 51200 102400 \n\ - net.ipv4.tcp_rmem = 4096 87380 67108864 \n\ - net.ipv4.tcp_wmem = 4096 65536 67108864 \n\ - net.ipv4.tcp_mtu_probing = 1 \n\ - net.ipv4.tcp_congestion_control = hybla \n\ - # for low-latency network, use cubic instead \n\ - # net.ipv4.tcp_congestion_control = cubic \n\ - " | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \ - mkdir -p /etc/security && \ - echo -e " \n\ - * soft nofile 51200 \n\ - * hard nofile 51200 \n\ - " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf - -ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ] -CMD [ "" ] diff --git a/client/server_scripts/openvpn_cloak/configure_container.sh b/client/server_scripts/openvpn_cloak/configure_container.sh deleted file mode 100644 index 2bb537242..000000000 --- a/client/server_scripts/openvpn_cloak/configure_container.sh +++ /dev/null @@ -1,77 +0,0 @@ -cat > /opt/amnezia/openvpn/server.conf < /opt/amnezia/cloak/cloak_admin_uid.key -CLOAK_BYPASS_UID=$(ck-server -u) && echo $CLOAK_BYPASS_UID > /opt/amnezia/cloak/cloak_bypass_uid.key -IFS=, read CLOAK_PUBLIC_KEY CLOAK_PRIVATE_KEY <<<$(ck-server -k) -echo $CLOAK_PUBLIC_KEY > /opt/amnezia/cloak/cloak_public.key -echo $CLOAK_PRIVATE_KEY > /opt/amnezia/cloak/cloak_private.key - -cat > /opt/amnezia/cloak/ck-config.json < /opt/amnezia/shadowsocks/shadowsocks.key -cat > /opt/amnezia/shadowsocks/ss-config.json < -$OPENVPN_CA_CERT - - -$OPENVPN_CLIENT_CERT - - -$OPENVPN_PRIV_KEY - - -$OPENVPN_TA_KEY - diff --git a/client/server_scripts/openvpn_shadowsocks/Dockerfile b/client/server_scripts/openvpn_shadowsocks/Dockerfile deleted file mode 100644 index ff89111c4..000000000 --- a/client/server_scripts/openvpn_shadowsocks/Dockerfile +++ /dev/null @@ -1,63 +0,0 @@ -FROM alpine:3.15 -LABEL maintainer="AmneziaVPN" - -ARG SS_RELEASE="v1.18.1" - -#Install required packages -RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools xz -RUN apk --update upgrade --no-cache - -ENV EASYRSA_BATCH 1 -ENV PATH="/usr/share/easy-rsa:${PATH}" - -RUN mkdir -p /opt/amnezia -RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh -RUN chmod a+x /opt/amnezia/start.sh - -RUN SERVER_ARCH=$(uname -m); \ - SUFFIX=""; \ - if [ ! -z "$(echo ${SERVER_ARCH} | grep -i arm)" ]; then \ - if [ ! -z "$(cat /proc/cpuinfo | grep -i vfp)" ]; then \ - SUFFIX="eabihf"; \ - else \ - SUFFIX="eabi"; \ - fi; \ - fi; \ - curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl${SUFFIX}.tar.xz > /usr/bin/ss.tar.xz;\ - tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/;\ - chmod a+x /usr/bin/ssserver; - -# Tune network -RUN echo -e " \n\ - fs.file-max = 51200 \n\ - \n\ - net.core.rmem_max = 67108864 \n\ - net.core.wmem_max = 67108864 \n\ - net.core.netdev_max_backlog = 250000 \n\ - net.core.somaxconn = 4096 \n\ - \n\ - net.ipv4.tcp_syncookies = 1 \n\ - net.ipv4.tcp_tw_reuse = 1 \n\ - net.ipv4.tcp_tw_recycle = 0 \n\ - net.ipv4.tcp_fin_timeout = 30 \n\ - net.ipv4.tcp_keepalive_time = 1200 \n\ - net.ipv4.ip_local_port_range = 10000 65000 \n\ - net.ipv4.tcp_max_syn_backlog = 8192 \n\ - net.ipv4.tcp_max_tw_buckets = 5000 \n\ - net.ipv4.tcp_fastopen = 3 \n\ - net.ipv4.tcp_mem = 25600 51200 102400 \n\ - net.ipv4.tcp_rmem = 4096 87380 67108864 \n\ - net.ipv4.tcp_wmem = 4096 65536 67108864 \n\ - net.ipv4.tcp_mtu_probing = 1 \n\ - net.ipv4.tcp_congestion_control = hybla \n\ - # for low-latency network, use cubic instead \n\ - # net.ipv4.tcp_congestion_control = cubic \n\ - " | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \ - mkdir -p /etc/security && \ - echo -e " \n\ - * soft nofile 51200 \n\ - * hard nofile 51200 \n\ - " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf - -ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ] -CMD [ "" ] diff --git a/client/server_scripts/openvpn_shadowsocks/configure_container.sh b/client/server_scripts/openvpn_shadowsocks/configure_container.sh deleted file mode 100644 index fae89a168..000000000 --- a/client/server_scripts/openvpn_shadowsocks/configure_container.sh +++ /dev/null @@ -1,46 +0,0 @@ -cat > /opt/amnezia/openvpn/server.conf < /opt/amnezia/shadowsocks/shadowsocks.key - -cat > /opt/amnezia/shadowsocks/ss-config.json < -$OPENVPN_CA_CERT - - -$OPENVPN_CLIENT_CERT - - -$OPENVPN_PRIV_KEY - - -$OPENVPN_TA_KEY - diff --git a/client/server_scripts/serverScripts.qrc b/client/server_scripts/serverScripts.qrc new file mode 100644 index 000000000..5ed57b01e --- /dev/null +++ b/client/server_scripts/serverScripts.qrc @@ -0,0 +1,58 @@ + + + awg/configure_container.sh + awg/Dockerfile + awg/run_container.sh + awg/start.sh + awg/template.conf + awg_legacy/configure_container.sh + awg_legacy/Dockerfile + awg_legacy/run_container.sh + awg_legacy/start.sh + awg_legacy/template.conf + build_container.sh + check_connection.sh + check_server_is_busy.sh + check_user_in_sudo.sh + dns/configure_container.sh + dns/Dockerfile + dns/run_container.sh + install_docker.sh + ipsec/configure_container.sh + ipsec/Dockerfile + ipsec/mobileconfig.plist + ipsec/run_container.sh + ipsec/start.sh + ipsec/strongswan.profile + openvpn/configure_container.sh + openvpn/Dockerfile + openvpn/run_container.sh + openvpn/start.sh + openvpn/template.ovpn + prepare_host.sh + remove_all_containers.sh + remove_container.sh + setup_host_firewall.sh + sftp/configure_container.sh + sftp/Dockerfile + sftp/run_container.sh + socks5_proxy/configure_container.sh + socks5_proxy/Dockerfile + socks5_proxy/run_container.sh + socks5_proxy/start.sh + website_tor/configure_container.sh + website_tor/Dockerfile + website_tor/run_container.sh + wireguard/configure_container.sh + wireguard/Dockerfile + wireguard/run_container.sh + wireguard/start.sh + wireguard/template.conf + xray/configure_container.sh + xray/Dockerfile + xray/run_container.sh + xray/start.sh + xray/template.json + + + diff --git a/client/settings.cpp b/client/settings.cpp deleted file mode 100644 index 2f7b24cbe..000000000 --- a/client/settings.cpp +++ /dev/null @@ -1,567 +0,0 @@ -#include "settings.h" - -#include "QCoreApplication" -#include "QThread" - -#include "core/networkUtilities.h" -#include "version.h" - -#include "containers/containers_defs.h" -#include "logger.h" - -namespace -{ - const char cloudFlareNs1[] = "1.1.1.1"; - const char cloudFlareNs2[] = "1.0.0.1"; - - constexpr char gatewayEndpoint[] = "http://gw.amnezia.org:80/"; -} - -Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this) -{ - // Import old settings - if (serversCount() == 0) { - QString user = m_settings.value("Server/userName").toString(); - QString password = m_settings.value("Server/password").toString(); - QString serverName = m_settings.value("Server/serverName").toString(); - int port = m_settings.value("Server/serverPort").toInt(); - - if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) { - QJsonObject server; - server.insert(config_key::userName, user); - server.insert(config_key::password, password); - server.insert(config_key::hostName, serverName); - server.insert(config_key::port, port); - server.insert(config_key::description, tr("Server #1")); - - addServer(server); - - m_settings.remove("Server/userName"); - m_settings.remove("Server/password"); - m_settings.remove("Server/serverName"); - m_settings.remove("Server/serverPort"); - } - } - - m_gatewayEndpoint = gatewayEndpoint; -} - -int Settings::serversCount() const -{ - return serversArray().size(); -} - -QJsonObject Settings::server(int index) const -{ - const QJsonArray &servers = serversArray(); - if (index >= servers.size()) - return QJsonObject(); - - return servers.at(index).toObject(); -} - -void Settings::addServer(const QJsonObject &server) -{ - QJsonArray servers = serversArray(); - servers.append(server); - setServersArray(servers); -} - -void Settings::removeServer(int index) -{ - QJsonArray servers = serversArray(); - if (index >= servers.size()) - return; - - servers.removeAt(index); - setServersArray(servers); - emit serverRemoved(index); -} - -bool Settings::editServer(int index, const QJsonObject &server) -{ - QJsonArray servers = serversArray(); - if (index >= servers.size()) - return false; - - servers.replace(index, server); - setServersArray(servers); - return true; -} - -void Settings::setDefaultContainer(int serverIndex, DockerContainer container) -{ - QJsonObject s = server(serverIndex); - s.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - editServer(serverIndex, s); -} - -DockerContainer Settings::defaultContainer(int serverIndex) const -{ - return ContainerProps::containerFromString(defaultContainerName(serverIndex)); -} - -QString Settings::defaultContainerName(int serverIndex) const -{ - QString name = server(serverIndex).value(config_key::defaultContainer).toString(); - if (name.isEmpty()) { - return ContainerProps::containerToString(DockerContainer::None); - } else - return name; -} - -QMap Settings::containers(int serverIndex) const -{ - const QJsonArray &containers = server(serverIndex).value(config_key::containers).toArray(); - - QMap containersMap; - for (const QJsonValue &val : containers) { - containersMap.insert(ContainerProps::containerFromString(val.toObject().value(config_key::container).toString()), val.toObject()); - } - - return containersMap; -} - -void Settings::setContainers(int serverIndex, const QMap &containers) -{ - QJsonObject s = server(serverIndex); - QJsonArray c; - for (const QJsonObject &o : containers) { - c.append(o); - } - s.insert(config_key::containers, c); - editServer(serverIndex, s); -} - -QJsonObject Settings::containerConfig(int serverIndex, DockerContainer container) -{ - if (container == DockerContainer::None) - return QJsonObject(); - return containers(serverIndex).value(container); -} - -void Settings::setContainerConfig(int serverIndex, DockerContainer container, const QJsonObject &config) -{ - if (container == DockerContainer::None) { - qCritical() << "Settings::setContainerConfig trying to set config for container == DockerContainer::None"; - return; - } - auto c = containers(serverIndex); - c[container] = config; - c[container][config_key::container] = ContainerProps::containerToString(container); - setContainers(serverIndex, c); -} - -void Settings::removeContainerConfig(int serverIndex, DockerContainer container) -{ - if (container == DockerContainer::None) { - qCritical() << "Settings::removeContainerConfig trying to remove config for container == DockerContainer::None"; - return; - } - - auto c = containers(serverIndex); - c.remove(container); - setContainers(serverIndex, c); -} - -QJsonObject Settings::protocolConfig(int serverIndex, DockerContainer container, Proto proto) -{ - const QJsonObject &c = containerConfig(serverIndex, container); - return c.value(ProtocolProps::protoToString(proto)).toObject(); -} - -void Settings::setProtocolConfig(int serverIndex, DockerContainer container, Proto proto, const QJsonObject &config) -{ - QJsonObject c = containerConfig(serverIndex, container); - c.insert(ProtocolProps::protoToString(proto), config); - - setContainerConfig(serverIndex, container, c); -} - -void Settings::clearLastConnectionConfig(int serverIndex, DockerContainer container, Proto proto) -{ - // recursively remove - if (proto == Proto::Any) { - for (Proto p : ContainerProps::protocolsForContainer(container)) { - clearLastConnectionConfig(serverIndex, container, p); - } - return; - } - - QJsonObject c = protocolConfig(serverIndex, container, proto); - c.remove(config_key::last_config); - setProtocolConfig(serverIndex, container, proto, c); -} - -bool Settings::haveAuthData(int serverIndex) const -{ - if (serverIndex < 0) - return false; - ServerCredentials cred = serverCredentials(serverIndex); - return (!cred.hostName.isEmpty() && !cred.userName.isEmpty() && !cred.secretData.isEmpty()); -} - -QString Settings::nextAvailableServerName() const -{ - int i = 0; - bool nameExist = false; - - do { - i++; - nameExist = false; - for (const QJsonValue &server : serversArray()) { - if (server.toObject().value(config_key::description).toString() == tr("Server") + " " + QString::number(i)) { - nameExist = true; - break; - } - } - } while (nameExist); - - return tr("Server") + " " + QString::number(i); -} - -void Settings::setSaveLogs(bool enabled) -{ - m_settings.setValue("Conf/saveLogs", enabled); -#ifndef Q_OS_ANDROID - if (!isSaveLogs()) { - Logger::deInit(); - } else { - if (!Logger::init(false)) { - qWarning() << "Initialization of debug subsystem failed"; - } - } -#endif - Logger::setServiceLogsEnabled(enabled); - - if (enabled) { - setLogEnableDate(QDateTime::currentDateTime()); - } - emit saveLogsChanged(enabled); -} - -QDateTime Settings::getLogEnableDate() -{ - return m_settings.value("Conf/logEnableDate").toDateTime(); -} - -void Settings::setLogEnableDate(QDateTime date) -{ - m_settings.setValue("Conf/logEnableDate", date); -} - -QString Settings::routeModeString(RouteMode mode) const -{ - switch (mode) { - case VpnAllSites: return "AllSites"; - case VpnOnlyForwardSites: return "ForwardSites"; - case VpnAllExceptSites: return "ExceptSites"; - } -} - -Settings::RouteMode Settings::routeMode() const -{ - return static_cast(m_settings.value("Conf/routeMode", 0).toInt()); -} - -bool Settings::isSitesSplitTunnelingEnabled() const -{ - return m_settings.value("Conf/sitesSplitTunnelingEnabled", false).toBool(); -} - -void Settings::setSitesSplitTunnelingEnabled(bool enabled) -{ - m_settings.setValue("Conf/sitesSplitTunnelingEnabled", enabled); -} - -bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) -{ - QVariantMap sites = vpnSites(mode); - if (sites.contains(site) && ip.isEmpty()) - return false; - - sites.insert(site, ip); - setVpnSites(mode, sites); - return true; -} - -void Settings::addVpnSites(RouteMode mode, const QMap &sites) -{ - QVariantMap allSites = vpnSites(mode); - for (auto i = sites.constBegin(); i != sites.constEnd(); ++i) { - const QString &site = i.key(); - const QString &ip = i.value(); - - if (allSites.contains(site) && allSites.value(site) == ip) - continue; - - allSites.insert(site, ip); - } - - setVpnSites(mode, allSites); -} - -QStringList Settings::getVpnIps(RouteMode mode) const -{ - QStringList ips; - const QVariantMap &m = vpnSites(mode); - for (auto i = m.constBegin(); i != m.constEnd(); ++i) { - if (NetworkUtilities::checkIpSubnetFormat(i.key())) { - ips.append(i.key()); - } else if (NetworkUtilities::checkIpSubnetFormat(i.value().toString())) { - ips.append(i.value().toString()); - } - } - ips.removeDuplicates(); - return ips; -} - -void Settings::removeVpnSite(RouteMode mode, const QString &site) -{ - QVariantMap sites = vpnSites(mode); - if (!sites.contains(site)) - return; - - sites.remove(site); - setVpnSites(mode, sites); -} - -void Settings::addVpnIps(RouteMode mode, const QStringList &ips) -{ - QVariantMap sites = vpnSites(mode); - for (const QString &ip : ips) { - if (ip.isEmpty()) - continue; - - sites.insert(ip, ""); - } - - setVpnSites(mode, sites); -} - -void Settings::removeVpnSites(RouteMode mode, const QStringList &sites) -{ - QVariantMap sitesMap = vpnSites(mode); - for (const QString &site : sites) { - if (site.isEmpty()) - continue; - - sitesMap.remove(site); - } - - setVpnSites(mode, sitesMap); -} - -void Settings::removeAllVpnSites(RouteMode mode) -{ - setVpnSites(mode, QVariantMap()); -} - -QString Settings::primaryDns() const -{ - return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); -} - -QString Settings::secondaryDns() const -{ - return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); -} - -void Settings::clearSettings() -{ - auto uuid = getInstallationUuid(false); - m_settings.clearSettings(); - setInstallationUuid(uuid); - emit settingsCleared(); -} - -QString Settings::appsRouteModeString(AppsRouteMode mode) const -{ - switch (mode) { - case VpnAllApps: return "AllApps"; - case VpnOnlyForwardApps: return "ForwardApps"; - case VpnAllExceptApps: return "ExceptApps"; - } -} - -Settings::AppsRouteMode Settings::getAppsRouteMode() const -{ - return static_cast(m_settings.value("Conf/appsRouteMode", 0).toInt()); -} - -void Settings::setAppsRouteMode(AppsRouteMode mode) -{ - m_settings.setValue("Conf/appsRouteMode", mode); -} - -QVector Settings::getVpnApps(AppsRouteMode mode) const -{ - QVector apps; - auto appsArray = m_settings.value("Conf/" + appsRouteModeString(mode)).toJsonArray(); - for (const auto &app : appsArray) { - InstalledAppInfo appInfo; - appInfo.appName = app.toObject().value("appName").toString(); - appInfo.packageName = app.toObject().value("packageName").toString(); - appInfo.appPath = app.toObject().value("appPath").toString(); - - apps.push_back(appInfo); - } - return apps; -} - -void Settings::setVpnApps(AppsRouteMode mode, const QVector &apps) -{ - QJsonArray appsArray; - for (const auto &app : apps) { - QJsonObject appInfo; - appInfo.insert("appName", app.appName); - appInfo.insert("packageName", app.packageName); - appInfo.insert("appPath", app.appPath); - appsArray.push_back(appInfo); - } - m_settings.setValue("Conf/" + appsRouteModeString(mode), appsArray); -} - -bool Settings::isAppsSplitTunnelingEnabled() const -{ - return m_settings.value("Conf/appsSplitTunnelingEnabled", false).toBool(); -} - -void Settings::setAppsSplitTunnelingEnabled(bool enabled) -{ - m_settings.setValue("Conf/appsSplitTunnelingEnabled", enabled); -} - -bool Settings::isKillSwitchEnabled() const -{ - return m_settings.value("Conf/killSwitchEnabled", true).toBool(); -} - -void Settings::setKillSwitchEnabled(bool enabled) -{ - m_settings.setValue("Conf/killSwitchEnabled", enabled); -} - -bool Settings::isStrictKillSwitchEnabled() const -{ - return m_settings.value("Conf/strictKillSwitchEnabled", false).toBool(); -} - -void Settings::setStrictKillSwitchEnabled(bool enabled) -{ - m_settings.setValue("Conf/strictKillSwitchEnabled", enabled); -} - -QString Settings::getInstallationUuid(const bool needCreate) -{ - auto uuid = m_settings.value("Conf/installationUuid", "").toString(); - if (needCreate && uuid.isEmpty()) { - uuid = QUuid::createUuid().toString(); - - //remove {} from uuid - uuid.remove(0, 1); - uuid.chop(1); - - setInstallationUuid(uuid); - } else if (uuid.contains("{") && uuid.contains("}")) { - //remove {} from old uuid - uuid.remove(0, 1); - uuid.chop(1); - - setInstallationUuid(uuid); - } - return uuid; -} - -void Settings::setInstallationUuid(const QString &uuid) -{ - m_settings.setValue("Conf/installationUuid", uuid); -} - -ServerCredentials Settings::defaultServerCredentials() const -{ - return serverCredentials(defaultServerIndex()); -} - -ServerCredentials Settings::serverCredentials(int index) const -{ - const QJsonObject &s = server(index); - - ServerCredentials credentials; - credentials.hostName = s.value(config_key::hostName).toString(); - credentials.userName = s.value(config_key::userName).toString(); - credentials.secretData = s.value(config_key::password).toString(); - credentials.port = s.value(config_key::port).toInt(); - - return credentials; -} - -void Settings::resetGatewayEndpoint() -{ - m_gatewayEndpoint = gatewayEndpoint; -} - -void Settings::setGatewayEndpoint(const QString &endpoint) -{ - m_gatewayEndpoint = endpoint; -} - -void Settings::setDevGatewayEndpoint() -{ - m_gatewayEndpoint = DEV_AGW_ENDPOINT; -} - -QString Settings::getGatewayEndpoint(bool isTestPurchase) -{ - return isTestPurchase ? DEV_AGW_ENDPOINT : m_gatewayEndpoint; -} - -bool Settings::isDevGatewayEnv(bool isTestPurchase) -{ - return isTestPurchase ? true : m_settings.value("Conf/devGatewayEnv", false).toBool(); -} - -void Settings::toggleDevGatewayEnv(bool enabled) -{ - m_settings.setValue("Conf/devGatewayEnv", enabled); -} - -bool Settings::isHomeAdLabelVisible() -{ - return m_settings.value("Conf/homeAdLabelVisible", true).toBool(); -} - -void Settings::disableHomeAdLabel() -{ - m_settings.setValue("Conf/homeAdLabelVisible", false); -} - -bool Settings::isPremV1MigrationReminderActive() -{ - return m_settings.value("Conf/premV1MigrationReminderActive", true).toBool(); -} - -void Settings::disablePremV1MigrationReminder() -{ - m_settings.setValue("Conf/premV1MigrationReminderActive", false); -} - -QStringList Settings::allowedDnsServers() const -{ - return m_settings.value("Conf/allowedDnsServers").toStringList(); -} - -void Settings::setAllowedDnsServers(const QStringList &servers) -{ - m_settings.setValue("Conf/allowedDnsServers", servers); -} - -QStringList Settings::readNewsIds() const -{ - return m_settings.value("News/readIds").toStringList(); -} - -void Settings::setReadNewsIds(const QStringList &ids) -{ - m_settings.setValue("News/readIds", ids); -} diff --git a/client/settings.h b/client/settings.h deleted file mode 100644 index 45fe39e10..000000000 --- a/client/settings.h +++ /dev/null @@ -1,264 +0,0 @@ -#ifndef SETTINGS_H -#define SETTINGS_H - -#include -#include -#include - -#include -#include -#include - -#include "containers/containers_defs.h" -#include "core/defs.h" -#include "secure_qsettings.h" - -using namespace amnezia; - -class QSettings; - -class Settings : public QObject -{ - Q_OBJECT - -public: - explicit Settings(QObject *parent = nullptr); - - ServerCredentials defaultServerCredentials() const; - ServerCredentials serverCredentials(int index) const; - - QJsonArray serversArray() const - { - return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array(); - } - void setServersArray(const QJsonArray &servers) - { - m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson()); - } - - // Servers section - int serversCount() const; - QJsonObject server(int index) const; - void addServer(const QJsonObject &server); - void removeServer(int index); - bool editServer(int index, const QJsonObject &server); - - int defaultServerIndex() const - { - return m_settings.value("Servers/defaultServerIndex", 0).toInt(); - } - void setDefaultServer(int index) - { - m_settings.setValue("Servers/defaultServerIndex", index); - } - QJsonObject defaultServer() const - { - return server(defaultServerIndex()); - } - - void setDefaultContainer(int serverIndex, DockerContainer container); - DockerContainer defaultContainer(int serverIndex) const; - QString defaultContainerName(int serverIndex) const; - - QMap containers(int serverIndex) const; - void setContainers(int serverIndex, const QMap &containers); - - QJsonObject containerConfig(int serverIndex, DockerContainer container); - void setContainerConfig(int serverIndex, DockerContainer container, const QJsonObject &config); - void removeContainerConfig(int serverIndex, DockerContainer container); - - QJsonObject protocolConfig(int serverIndex, DockerContainer container, Proto proto); - void setProtocolConfig(int serverIndex, DockerContainer container, Proto proto, const QJsonObject &config); - - void clearLastConnectionConfig(int serverIndex, DockerContainer container, Proto proto = Proto::Any); - - bool haveAuthData(int serverIndex) const; - QString nextAvailableServerName() const; - - // App settings section - bool isAutoConnect() const - { - return m_settings.value("Conf/autoConnect", false).toBool(); - } - void setAutoConnect(bool enabled) - { - m_settings.setValue("Conf/autoConnect", enabled); - } - - bool isStartMinimized() const - { - return m_settings.value("Conf/startMinimized", false).toBool(); - } - void setStartMinimized(bool enabled) - { - m_settings.setValue("Conf/startMinimized", enabled); - } - - bool isNewsNotifications() const - { - return m_settings.value("Conf/newsNotifications", true).toBool(); - } - void setNewsNotifications(bool enabled) - { - m_settings.setValue("Conf/newsNotifications", enabled); - } - - bool isSaveLogs() const - { - return m_settings.value("Conf/saveLogs", false).toBool(); - } - void setSaveLogs(bool enabled); - - QDateTime getLogEnableDate(); - void setLogEnableDate(QDateTime date); - - enum RouteMode { - VpnAllSites, - VpnOnlyForwardSites, - VpnAllExceptSites - }; - Q_ENUM(RouteMode) - - QString routeModeString(RouteMode mode) const; - - RouteMode routeMode() const; - void setRouteMode(RouteMode mode) { m_settings.setValue("Conf/routeMode", mode); } - - bool isSitesSplitTunnelingEnabled() const; - void setSitesSplitTunnelingEnabled(bool enabled); - - QVariantMap vpnSites(RouteMode mode) const - { - return m_settings.value("Conf/" + routeModeString(mode)).toMap(); - } - void setVpnSites(RouteMode mode, const QVariantMap &sites) - { - m_settings.setValue("Conf/" + routeModeString(mode), sites); - } - bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); - void addVpnSites(RouteMode mode, const QMap &sites); // map - QStringList getVpnIps(RouteMode mode) const; - void removeVpnSite(RouteMode mode, const QString &site); - - void addVpnIps(RouteMode mode, const QStringList &ip); - void removeVpnSites(RouteMode mode, const QStringList &sites); - void removeAllVpnSites(RouteMode mode); - - bool useAmneziaDns() const - { - return m_settings.value("Conf/useAmneziaDns", true).toBool(); - } - void setUseAmneziaDns(bool enabled) - { - m_settings.setValue("Conf/useAmneziaDns", enabled); - } - - QString primaryDns() const; - QString secondaryDns() const; - - // QString primaryDns() const { return m_primaryDns; } - void setPrimaryDns(const QString &primaryDns) - { - m_settings.setValue("Conf/primaryDns", primaryDns); - } - - // QString secondaryDns() const { return m_secondaryDns; } - void setSecondaryDns(const QString &secondaryDns) - { - m_settings.setValue("Conf/secondaryDns", secondaryDns); - } - - // static constexpr char openNicNs5[] = "94.103.153.176"; - // static constexpr char openNicNs13[] = "144.76.103.143"; - - QByteArray backupAppConfig() const - { - return m_settings.backupAppConfig(); - } - bool restoreAppConfig(const QByteArray &cfg) - { - return m_settings.restoreAppConfig(cfg); - } - - QLocale getAppLanguage() - { - QString localeStr = m_settings.value("Conf/appLanguage", QLocale::system().name()).toString(); - return QLocale(localeStr); - }; - void setAppLanguage(QLocale locale) - { - m_settings.setValue("Conf/appLanguage", locale.name()); - }; - - bool isScreenshotsEnabled() const - { - return m_settings.value("Conf/screenshotsEnabled", true).toBool(); - } - void setScreenshotsEnabled(bool enabled) - { - m_settings.setValue("Conf/screenshotsEnabled", enabled); - emit screenshotsEnabledChanged(enabled); - } - - void clearSettings(); - - enum AppsRouteMode { - VpnAllApps, - VpnOnlyForwardApps, - VpnAllExceptApps - }; - Q_ENUM(AppsRouteMode) - - QString appsRouteModeString(AppsRouteMode mode) const; - - AppsRouteMode getAppsRouteMode() const; - void setAppsRouteMode(AppsRouteMode mode); - - QVector getVpnApps(AppsRouteMode mode) const; - void setVpnApps(AppsRouteMode mode, const QVector &apps); - - bool isAppsSplitTunnelingEnabled() const; - void setAppsSplitTunnelingEnabled(bool enabled); - - bool isKillSwitchEnabled() const; - void setKillSwitchEnabled(bool enabled); - - bool isStrictKillSwitchEnabled() const; - void setStrictKillSwitchEnabled(bool enabled); - - QString getInstallationUuid(const bool needCreate); - - void resetGatewayEndpoint(); - void setGatewayEndpoint(const QString &endpoint); - void setDevGatewayEndpoint(); - QString getGatewayEndpoint(bool isTestPurchase = false); - bool isDevGatewayEnv(bool isTestPurchase = false); - void toggleDevGatewayEnv(bool enabled); - - bool isHomeAdLabelVisible(); - void disableHomeAdLabel(); - - bool isPremV1MigrationReminderActive(); - void disablePremV1MigrationReminder(); - - QStringList allowedDnsServers() const; - void setAllowedDnsServers(const QStringList &servers); - - QStringList readNewsIds() const; - void setReadNewsIds(const QStringList &ids); - -signals: - void saveLogsChanged(bool enabled); - void screenshotsEnabledChanged(bool enabled); - void serverRemoved(int serverIndex); - void settingsCleared(); - -private: - void setInstallationUuid(const QString &uuid); - - mutable SecureQSettings m_settings; - - QString m_gatewayEndpoint; -}; - -#endif // SETTINGS_H diff --git a/client/tests/CMakeLists.txt b/client/tests/CMakeLists.txt new file mode 100644 index 000000000..e541a4e6f --- /dev/null +++ b/client/tests/CMakeLists.txt @@ -0,0 +1,155 @@ +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_gateway_stacks + testGatewayStacks.cpp +) + +target_link_libraries(test_gateway_stacks 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 GatewayStacksTest COMMAND test_gateway_stacks) +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) diff --git a/client/tests/testAdminSelfHostedExport.cpp b/client/tests/testAdminSelfHostedExport.cpp new file mode 100644 index 000000000..9cd8a6976 --- /dev/null +++ b/client/tests/testAdminSelfHostedExport.cpp @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.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 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::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"); + + int serverIndex = m_coreController->m_serversRepository->defaultServerIndex(); + auto exportResult = m_coreController->m_exportController->generateFullAccessConfig(serverIndex); + + 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" diff --git a/client/tests/testComplexOperations.cpp b/client/tests/testComplexOperations.cpp new file mode 100644 index 000000000..878a12510 --- /dev/null +++ b/client/tests/testComplexOperations.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.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::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(), -1, false); + } + } + + 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"); + + ServerConfig server0 = m_coreController->m_serversController->getServerConfig(0); + server0.visit([](auto& arg) { + arg.description = "Edited First Server"; + }); + m_coreController->m_serversController->editServer(0, server0); + + QVERIFY2(serverEditedSpy.count() == 1, "serverEdited should be emitted"); + QString editedDesc0 = m_coreController->m_serversRepository->server(0).description(); + QVERIFY2(editedDesc0 == "Edited First Server", "First server should be edited"); + + m_coreController->m_serversController->removeServer(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->setDefaultServerIndex(0); + + QVERIFY2(defaultServerChangedSpy.count() == 4, "defaultServerChanged should be emitted again"); + QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default should be index 0"); + + ServerConfig server0After = m_coreController->m_serversController->getServerConfig(0); + server0After.visit([](auto& arg) { + arg.description = "Final Edited Server"; + }); + m_coreController->m_serversController->editServer(0, server0After); + + QVERIFY2(serverEditedSpy.count() == 2, "serverEdited should be emitted again"); + QString finalDesc0 = m_coreController->m_serversRepository->server(0).description(); + 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" + diff --git a/client/tests/testDefaultServerChange.cpp b/client/tests/testDefaultServerChange.cpp new file mode 100644 index 000000000..adffde62f --- /dev/null +++ b/client/tests/testDefaultServerChange.cpp @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.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::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(), -1, false); + } + } + + 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->setDefaultServerIndex(0); + QVERIFY2(defaultServerChangedSpy.count() == 1, "defaultServerChanged signal should be emitted"); + QVERIFY2(defaultServerChangedSpy.at(0).at(0).toInt() == 0, "defaultServerChanged should emit index 0"); + 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->setDefaultServerIndex(2); + QVERIFY2(defaultServerChangedSpy.count() == 2, "defaultServerChanged signal should be emitted again"); + QVERIFY2(defaultServerChangedSpy.at(1).at(0).toInt() == 2, "defaultServerChanged should emit index 2"); + 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(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)"); + + ServerConfig remainingServer1 = m_coreController->m_serversRepository->server(0); + ServerConfig remainingServer2 = m_coreController->m_serversRepository->server(1); + QString desc1 = remainingServer1.description(); + QString desc2 = remainingServer2.description(); + 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(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)"); + + ServerConfig lastServer = m_coreController->m_serversRepository->server(0); + QString lastDesc = lastServer.description(); + QVERIFY2(lastDesc == "WireGuard Server", "Last server should be WireGuard"); + } +}; + +QTEST_MAIN(TestDefaultServerChange) +#include "testDefaultServerChange.moc" + diff --git a/client/tests/testGatewayStacks.cpp b/client/tests/testGatewayStacks.cpp new file mode 100644 index 000000000..acffe39bb --- /dev/null +++ b/client/tests/testGatewayStacks.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.h" +#include "vpnConnection.h" +#include "secureQSettings.h" + +using namespace amnezia; + +class TestGatewayStacks : 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::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(), -1, false); + } + } + + void testGatewayStacksRecomputeOnServerOperations() { + QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA"; + + QSignalSpy gatewayStacksExpandedSpy(m_coreController->m_serversController, &ServersController::gatewayStacksExpanded); + QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded); + QSignalSpy serverEditedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited); + QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved); + + auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey); + m_coreController->m_importCoreController->importConfig(importResult.config); + + QVERIFY2(serverAddedSpy.count() == 1, "serverAdded signal should be emitted"); + QVERIFY2(m_coreController->m_serversController->gatewayStacks().isEmpty(), "Gateway stacks should be empty for self-hosted servers"); + + ServerConfig serverConfig = m_coreController->m_serversController->getServerConfig(0); + serverConfig.visit([](auto& arg) { + arg.description = "Edited Server"; + }); + m_coreController->m_serversController->editServer(0, serverConfig); + + QVERIFY2(serverEditedSpy.count() == 1, "serverEdited signal should be emitted"); + + m_coreController->m_serversController->removeServer(0); + + QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted"); + QVERIFY2(m_coreController->m_serversController->gatewayStacks().isEmpty(), "Gateway stacks should remain empty"); + } +}; + +QTEST_MAIN(TestGatewayStacks) +#include "testGatewayStacks.moc" + diff --git a/client/tests/testMultipleImports.cpp b/client/tests/testMultipleImports.cpp new file mode 100644 index 000000000..44a0acd3c --- /dev/null +++ b/client/tests/testMultipleImports.cpp @@ -0,0 +1,194 @@ +#include +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.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::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(), -1, false); + } + } + + 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"); + + ServerConfig server1 = m_coreController->m_serversRepository->server(0); + QString desc1 = server1.description(); + 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"); + + ServerConfig server2 = m_coreController->m_serversRepository->server(1); + QString desc2 = server2.description(); + 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"); + + ServerConfig server3 = m_coreController->m_serversRepository->server(2); + QString desc3 = server3.description(); + 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"); + + ServerConfig server0 = m_coreController->m_serversRepository->server(0); + ServerConfig server1 = m_coreController->m_serversRepository->server(1); + QString desc0 = server0.description(); + QString desc1 = server1.description(); + 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(0); + + QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted"); + QVERIFY2(serverRemovedSpy.at(0).at(0).toInt() == 0, "serverRemoved should emit 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"); + + ServerConfig remainingServer = m_coreController->m_serversRepository->server(0); + QString remainingDesc = remainingServer.description(); + 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(0); + + QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted"); + QVERIFY2(serverRemovedSpy.at(0).at(0).toInt() == 0, "serverRemoved should emit 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" diff --git a/client/tests/testSelfHostedServerSetup.cpp b/client/tests/testSelfHostedServerSetup.cpp new file mode 100644 index 000000000..a44725556 --- /dev/null +++ b/client/tests/testSelfHostedServerSetup.cpp @@ -0,0 +1,373 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.h" +#include "core/models/selfhosted/selfHostedServerConfig.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(sshError)) + .arg(sshOutput) + .toUtf8().constData()); + qDebug() << "SSH connection successful. Output:" << sshOutput; + } + + void verifyAdminAccess(int serverIndex) { + ServerConfig server = m_coreController->m_serversRepository->server(serverIndex); + const SelfHostedServerConfig* selfHosted = server.as(); + QVERIFY2(selfHosted != nullptr, "Server config should be SelfHostedServerConfig"); + + QVERIFY2(selfHosted->hasCredentials(), + "Server should have credentials (admin access)"); + + QVERIFY2(selfHosted->userName.has_value() && !selfHosted->userName.value().isEmpty(), + "Server should have userName for admin access"); + + QVERIFY2(selfHosted->password.has_value() && !selfHosted->password.value().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(); + QVERIFY2(awgProtocolConfig != nullptr, "Protocol config should be AwgProtocolConfig"); + QVERIFY2(awgProtocolConfig->hasClientConfig(), "AwgProtocolConfig should have client config"); + const std::optional& 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::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(), -1, false); + } + } + + 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(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; + + ServerConfig serverAfterAwg = m_coreController->m_serversRepository->server(serverIndex); + QVERIFY2(serverAfterAwg.isSelfHosted(), "Server should be self-hosted"); + const SelfHostedServerConfig* selfHostedAfterAwg = serverAfterAwg.as(); + QVERIFY2(selfHostedAfterAwg != nullptr, "Server config should be SelfHostedServerConfig"); + 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; + + ErrorCode installContainerError = m_coreController->m_installController->installContainer( + serverIndex, DockerContainer::Dns, dnsPort, dnsTransportProto, wasDnsInstalled); + + QVERIFY2(installContainerError == ErrorCode::NoError, + QString("installContainer for Dns should succeed. Error: %1") + .arg(static_cast(installContainerError)) + .toUtf8().constData()); + qDebug() << "Dns container installed:" << wasDnsInstalled; + + ServerConfig serverAfterDns = m_coreController->m_serversRepository->server(serverIndex); + const SelfHostedServerConfig* selfHostedAfterDns = serverAfterDns.as(); + QVERIFY2(selfHostedAfterDns != nullptr, "Server config should be SelfHostedServerConfig"); + 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(); + 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); + + SelfHostedServerConfig 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.defaultContainer = DockerContainer::None; + + QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded); + m_coreController->m_serversController->addServer(ServerConfig(serverConfig)); + + 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; + + ServerConfig addedServer = m_coreController->m_serversRepository->server(serverIndex); + QVERIFY2(addedServer.isSelfHosted(), "Added server should be self-hosted"); + const SelfHostedServerConfig* selfHosted = addedServer.as(); + QVERIFY2(selfHosted != nullptr, "Server config should be SelfHostedServerConfig"); + QVERIFY2(selfHosted->containers.isEmpty(), "Server should have no containers initially"); + QVERIFY2(selfHosted->defaultContainer == DockerContainer::None, "Default container should be None"); + + ErrorCode scanError = m_coreController->m_installController->scanServerForInstalledContainers(serverIndex); + QVERIFY2(scanError == ErrorCode::NoError, + QString("Server scan should succeed. Error: %1") + .arg(static_cast(scanError)) + .toUtf8().constData()); + qDebug() << "Server scan completed successfully"; + + ServerConfig scannedServer = m_coreController->m_serversRepository->server(serverIndex); + const SelfHostedServerConfig* scannedSelfHosted = scannedServer.as(); + QVERIFY2(scannedSelfHosted != nullptr, "Scanned server config should be SelfHostedServerConfig"); + + QMap 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(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; + + ServerConfig serverBeforeRemoval = m_coreController->m_serversRepository->server(serverIndex); + const SelfHostedServerConfig* selfHostedBeforeRemoval = serverBeforeRemoval.as(); + QVERIFY2(selfHostedBeforeRemoval != nullptr, "Server config should be SelfHostedServerConfig"); + 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(); + + ErrorCode removeError = m_coreController->m_installController->removeAllContainers(serverIndex); + QVERIFY2(removeError == ErrorCode::NoError, + QString("removeAllContainers should succeed. Error: %1") + .arg(static_cast(removeError)) + .toUtf8().constData()); + qDebug() << "All containers removed successfully"; + + ServerConfig serverAfterRemoval = m_coreController->m_serversRepository->server(serverIndex); + const SelfHostedServerConfig* selfHostedAfterRemoval = serverAfterRemoval.as(); + QVERIFY2(selfHostedAfterRemoval != nullptr, "Server config should be SelfHostedServerConfig"); + + 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" + diff --git a/client/tests/testServerEdgeCases.cpp b/client/tests/testServerEdgeCases.cpp new file mode 100644 index 000000000..53c358d20 --- /dev/null +++ b/client/tests/testServerEdgeCases.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.h" +#include "vpnConnection.h" +#include "secureQSettings.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::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(), -1, false); + } + } + + 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(-1); + QVERIFY2(serverRemovedSpy.count() == 0, "serverRemoved should NOT be emitted for invalid index"); + + m_coreController->m_serversController->removeServer(10); + QVERIFY2(serverRemovedSpy.count() == 0, "serverRemoved should NOT be emitted for invalid index"); + + m_coreController->m_serversController->removeServer(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"); + + ServerConfig serverConfig = m_coreController->m_serversController->getServerConfig(0); + m_coreController->m_serversController->editServer(-1, serverConfig); + QVERIFY2(serverEditedSpy.count() == 0, "serverEdited should NOT be emitted for invalid index"); + + m_coreController->m_serversController->editServer(10, serverConfig); + QVERIFY2(serverEditedSpy.count() == 0, "serverEdited should NOT be emitted for invalid index"); + + m_coreController->m_serversController->setDefaultServerIndex(-1); + QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged should NOT be emitted for invalid index"); + + m_coreController->m_serversController->setDefaultServerIndex(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"); + + ServerConfig emptyConfig = SelfHostedServerConfig{}; + m_coreController->m_serversController->removeServer(0); + QVERIFY2(serverRemovedSpy.count() == 0, "serverRemoved should NOT be emitted for empty repository"); + + m_coreController->m_serversController->editServer(0, emptyConfig); + QVERIFY2(serverEditedSpy.count() == 0, "serverEdited should NOT be emitted for empty repository"); + + m_coreController->m_serversController->setDefaultServerIndex(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" + diff --git a/client/tests/testServerEdit.cpp b/client/tests/testServerEdit.cpp new file mode 100644 index 000000000..57f18ef12 --- /dev/null +++ b/client/tests/testServerEdit.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.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::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(), -1, false); + } + } + + 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); + QSignalSpy gatewayStacksExpandedSpy(m_coreController->m_serversController, &ServersController::gatewayStacksExpanded); + + ServerConfig serverConfig = m_coreController->m_serversController->getServerConfig(0); + serverConfig.visit([](auto& arg) { + arg.description = "Edited AWG Server"; + }); + + m_coreController->m_serversController->editServer(0, serverConfig); + + QVERIFY2(serverEditedSpy.count() == 1, "serverEdited signal should be emitted"); + QVERIFY2(serverEditedSpy.at(0).at(0).toInt() == 0, "serverEdited should emit index 0"); + + ServerConfig editedServer = m_coreController->m_serversRepository->server(0); + QString editedDesc = editedServer.description(); + 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); + + ServerConfig defaultServerConfig = m_coreController->m_serversController->getServerConfig(1); + defaultServerConfig.visit([](auto& arg) { + arg.description = "Edited Default Server"; + }); + m_coreController->m_serversController->editServer(1, defaultServerConfig); + + 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"); + + ServerConfig nonDefaultServerConfig = m_coreController->m_serversController->getServerConfig(0); + nonDefaultServerConfig.visit([](auto& arg) { + arg.description = "Edited Non-Default Server"; + }); + m_coreController->m_serversController->editServer(0, nonDefaultServerConfig); + + 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" + diff --git a/client/tests/testServersModelSync.cpp b/client/tests/testServersModelSync.cpp new file mode 100644 index 000000000..a1e63ccce --- /dev/null +++ b/client/tests/testServersModelSync.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.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::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(), -1, false); + } + } + + 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"); + + ServerConfig serverConfig = m_coreController->m_serversController->getServerConfig(0); + serverConfig.visit([](auto& arg) { + arg.description = "Edited AWG Server"; + }); + m_coreController->m_serversController->editServer(0, serverConfig); + + 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(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->setDefaultServerIndex(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" + diff --git a/client/tests/testSettingsSignals.cpp b/client/tests/testSettingsSignals.cpp new file mode 100644 index 000000000..e0308a56c --- /dev/null +++ b/client/tests/testSettingsSignals.cpp @@ -0,0 +1,266 @@ +#include +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.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::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() == 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 sitesMap{{"example.com", "1.2.3.4"}}; + m_coreController->m_ipSplitTunnelingController->addSites(sitesMap, true); + QVERIFY2(sitesChangedSpy.count() >= 1, "sitesChanged signal should be emitted"); + QVector> 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" + diff --git a/client/tests/testSignalOrder.cpp b/client/tests/testSignalOrder.cpp new file mode 100644 index 000000000..97b7cb29e --- /dev/null +++ b/client/tests/testSignalOrder.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.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::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(), -1, false); + } + } + + 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(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).toInt() == 0, "defaultServerChanged should emit new default index 0"); + QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default server index should be 0"); + } +}; + +QTEST_MAIN(TestSignalOrder) +#include "testSignalOrder.moc" + diff --git a/client/tests/testUiServersModelAndController.cpp b/client/tests/testUiServersModelAndController.cpp new file mode 100644 index 000000000..16039e988 --- /dev/null +++ b/client/tests/testUiServersModelAndController.cpp @@ -0,0 +1,294 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/controllers/coreController.h" +#include "core/models/serverConfig.h" +#include "core/controllers/selfhosted/importController.h" +#include "ui/models/serversModel.h" +#include "ui/models/containersModel.h" +#include "core/utils/constants/configKeys.h" + +using namespace amnezia; +#include "core/utils/constants/protocolConstants.h" +#include "core/utils/containerEnum.h" +#include "core/utils/protocolEnum.h" +#include "vpnConnection.h" +#include "secureQSettings.h" + +using namespace amnezia; + +class TestUiServersModelAndController : public QObject +{ + Q_OBJECT + +private: + CoreController* m_coreController; + SecureQSettings* m_settings; + + QJsonObject createAwg2Config() + { + QJsonObject clientConfig; + clientConfig[configKey::mtu] = protocols::awg::defaultMtu; + clientConfig[configKey::junkPacketCount] = protocols::awg::defaultJunkPacketCount; + clientConfig[configKey::junkPacketMinSize] = protocols::awg::defaultJunkPacketMinSize; + clientConfig[configKey::junkPacketMaxSize] = protocols::awg::defaultJunkPacketMaxSize; + clientConfig[configKey::specialJunk1] = protocols::awg::defaultSpecialJunk1; + clientConfig[configKey::specialJunk2] = protocols::awg::defaultSpecialJunk2; + clientConfig[configKey::specialJunk3] = protocols::awg::defaultSpecialJunk3; + clientConfig[configKey::specialJunk4] = protocols::awg::defaultSpecialJunk4; + clientConfig[configKey::specialJunk5] = protocols::awg::defaultSpecialJunk5; + clientConfig[configKey::clientPrivKey] = "test_client_private_key"; + clientConfig[configKey::clientPubKey] = "test_client_public_key"; + clientConfig[configKey::serverPubKey] = "test_server_public_key"; + clientConfig[configKey::pskKey] = "test_psk_key"; + clientConfig[configKey::clientIp] = "10.8.1.2"; + clientConfig[configKey::allowedIps] = QJsonArray::fromStringList({"0.0.0.0/0"}); + + QJsonObject awgConfig; + awgConfig[configKey::lastConfig] = QString(QJsonDocument(clientConfig).toJson()); + awgConfig[configKey::port] = protocols::awg::defaultPort; + awgConfig[configKey::transportProto] = "udp"; + awgConfig[configKey::protocolVersion] = protocols::awg::awgV2; + awgConfig[configKey::subnetAddress] = protocols::wireguard::defaultSubnetAddress; + awgConfig[configKey::junkPacketCount] = protocols::awg::defaultJunkPacketCount; + awgConfig[configKey::junkPacketMinSize] = protocols::awg::defaultJunkPacketMinSize; + awgConfig[configKey::junkPacketMaxSize] = protocols::awg::defaultJunkPacketMaxSize; + awgConfig[configKey::initPacketJunkSize] = protocols::awg::defaultInitPacketJunkSize; + awgConfig[configKey::responsePacketJunkSize] = protocols::awg::defaultResponsePacketJunkSize; + awgConfig[configKey::cookieReplyPacketJunkSize] = protocols::awg::defaultCookieReplyPacketJunkSize; + awgConfig[configKey::transportPacketJunkSize] = protocols::awg::defaultTransportPacketJunkSize; + awgConfig[configKey::initPacketMagicHeader] = protocols::awg::defaultInitPacketMagicHeader; + awgConfig[configKey::responsePacketMagicHeader] = protocols::awg::defaultResponsePacketMagicHeader; + awgConfig[configKey::underloadPacketMagicHeader] = protocols::awg::defaultUnderloadPacketMagicHeader; + awgConfig[configKey::transportPacketMagicHeader] = protocols::awg::defaultTransportPacketMagicHeader; + awgConfig[configKey::specialJunk1] = protocols::awg::defaultSpecialJunk1; + awgConfig[configKey::specialJunk2] = protocols::awg::defaultSpecialJunk2; + awgConfig[configKey::specialJunk3] = protocols::awg::defaultSpecialJunk3; + awgConfig[configKey::specialJunk4] = protocols::awg::defaultSpecialJunk4; + awgConfig[configKey::specialJunk5] = protocols::awg::defaultSpecialJunk5; + awgConfig[configKey::isThirdPartyConfig] = true; + + QJsonObject container; + container[configKey::container] = "amnezia-awg"; + container[configKey::awg] = awgConfig; + + QJsonArray containers; + containers.append(container); + + QJsonObject config; + config[configKey::containers] = containers; + config[configKey::defaultContainer] = "amnezia-awg"; + config[configKey::description] = "AWG2 Test Server"; + config[configKey::hostName] = "test.example.com"; + + return config; + } + + QJsonObject createServerDescriptionTestConfig(bool withAmneziaDns) + { + QJsonObject config = createAwg2Config(); + config[configKey::description] = "Server 1"; + if (withAmneziaDns) { + config[configKey::dns1] = protocols::dns::amneziaDnsIp; + } + return config; + } + +private slots: + void initTestCase() { + QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString(); + m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false); + + auto vpnConnection = QSharedPointer::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(), -1, false); + } + } + + 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(expectedContainer), "Default container should be Awg"); + } + + if (m_coreController->m_serversUiController) { + m_coreController->m_serversUiController->setProcessedServerIndex(serverIndex); + + ServerConfig serverConfig = m_coreController->m_serversRepository->server(serverIndex); + QString actualServerName = serverConfig.description(); + QString containerName = ContainerUtils::containerHumanNames().value(DockerContainer::Awg); + 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(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(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); + m_coreController->m_serversModel->updateModel( + m_coreController->m_serversRepository->servers(), + m_coreController->m_serversRepository->defaultServerIndex(), + m_coreController->m_appSettingsRepository->useAmneziaDns()); + + 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->setServersArray(QJsonArray()); + m_coreController->m_serversRepository->setDefaultServer(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); + m_coreController->m_serversModel->updateModel( + m_coreController->m_serversRepository->servers(), + m_coreController->m_serversRepository->defaultServerIndex(), + m_coreController->m_appSettingsRepository->useAmneziaDns()); + + 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" diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index d3b2138bf..16ddc2a1a 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -2,138 +2,268 @@ - AdLabel + AllowedDnsController - - Amnezia Premium - for access to all websites and online resources + Can't open file: %1 + 无法打开文件: %1 + + + Failed to parse JSON data from file: %1 + JSON解析失败,文件: %1 + + + The JSON data is not an array in file: %1 + 文件中的JSON数据不是一个数组,文件: %1 + + + Import completed + 完成导入 + + + Export completed + 完成导出 + + + + AllowedDnsUiController + + + The address does not look like a valid IP address + + + New DNS server added: %1 + + + + + DNS server already exists: %1 + + + + + DNS server removed: %1 + + + + + Can't open file: %1 + 无法打开文件: %1 + + + + Failed to parse JSON data from file: %1 + JSON解析失败,文件: %1 + + + + The JSON data is not an array in file: %1 + 文件中的JSON数据不是一个数组,文件: %1 + + + + Import completed + 完成导入 + + + + Export completed + 完成导出 + ApiAccountInfoModel - + Active - Inactive + <p><a style="color: #EB5757;">Inactive</a> - + %1 out of %2 - - - Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. Speeds up to 200 Mbps - - - - - Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and more. YouTube is not included in the free plan. - - - - - amnezia_free_support_bot - - - - - amnezia_premium_support_bot - - ApiConfigsController - + + %1 installed successfully. - + + Subscription restored successfully. + + + + API config reloaded - + Successfully changed the country of connection to %1 + + ApiPremV1MigrationDrawer + + + Switch to the new Amnezia Premium subscription + + + + + We'll preserve all remaining days of your current subscription and give you an extra month as a thank you. + + + + + This new subscription type will be actively developed with more locations and features added regularly. Currently available: + + + + + <li>20 locations (with more coming soon)</li> + + + + + <li>Easier switching between countries in the app</li> + + + + + <li>Personal dashboard to manage your subscription</li> + + + + + Old keys will be deactivated after switching. + + + + + Email + + + + + mail@example.com + + + + + No old format subscriptions for a given email + + + + + Enter the email you used for your current subscription + + + + + + Continue + 继续 + + + + Remind me later + + + + + Don't remind me again + + + + + No more reminders? You can always switch to the new format in the server settings + + + + + Cancel + 取消 + + + + ApiPremV1SubListDrawer + + + Choose Subscription + + + + + Order ID: + + + + + Purchase Date: + + + ApiServicesModel - + <p><a style="color: #EB5757;">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a> - - Amnezia Premium is classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. Speeds up to %1 Mbps. - - - - - - AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan. - - - - - Amnezia Premium is classic VPN for for seamless work, downloading large files, and watching videos. Access all websites and online resources. - - - - + %1 MBit/s - + %1 days - VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. Other sites will be opened from your real IP address, <a href="%1/free" style="color: #FBB26A;">more details on the website.</a> - - - - Free - + %1 $/month - AppSplitTunnelingController + AppSplitTunnelingUiController - + Application added: %1 - + The application has already been added - + The selected applications have been added - + Application removed: %1 @@ -149,10 +279,10 @@ ConnectionController - - - - + + + + Connect 连接 @@ -162,37 +292,37 @@ 请先安装VPN协议 - + Connecting... 连接中 - + Connected 已连接 - + Reconnecting... 重连中 - + Disconnecting... 断开中 - + Preparing... - + Settings updated successfully, reconnnection... 配置已更新, 重连中... - + Settings updated successfully 配置更新成功 @@ -222,7 +352,7 @@ ContextMenuType - + C&ut 剪切 @@ -232,12 +362,12 @@ 拷贝 - + &Paste 粘贴 - + &SelectAll 全选 @@ -252,7 +382,7 @@ HomeContainersListView - + Unable change protocol while there is an active connection 已建立连接时无法更改服务器配置 @@ -311,17 +441,17 @@ Can't be disabled for current server ImportController - + Scanned %1 of %2. 扫描 %1 of %2. - + This configuration contains an OpenVPN setup. OpenVPN configurations can include malicious scripts, so only add it if you fully trust the provider of this config. - + <br>In the imported configuration, potentially dangerous lines were found: @@ -337,62 +467,43 @@ Can't be disabled for current server 已安装在服务器上 - %1 installed successfully. - %1 安装成功。 + %1 安装成功。 - %1 is already installed on the server. - 服务器上已经安装 %1。 + 服务器上已经安装 %1。 - Added containers that were already installed on the server - 添加已安装在服务器上的容器 + 添加已安装在服务器上的容器 - Already installed containers were found on the server. All installed containers have been added to the application - + 在服务上发现已经安装协议并添加至应用 - Settings updated successfully - 配置更新成功 + 配置更新成功 - Server '%1' was rebooted - 服务器 '%1' 已重新启动 + 服务器 '%1' 已重新启动 - Server '%1' was removed - 已移除服务器 '%1' + 已移除服务器 '%1' - All containers from server '%1' have been removed - 服务器 '%1' 的所有容器已移除 + 服务器 '%1' 的所有容器已移除 - %1 has been removed from the server '%2' - %1 已从服务器 '%2' 上移除 - - - - Api config removed - - - - - %1 cached profile cleared - + %1 已从服务器 '%2' 上移除 1% has been removed from the server '%2' @@ -411,14 +522,85 @@ Already installed containers were found on the server. All installed containers 协议已从 - Please login as the user - 请以用户身份登录 + 请以用户身份登录 - Server added successfully - 增加服务器成功 + 增加服务器成功 + + + + InstallUiController + + + + %1 installed successfully. + %1 安装成功。 + + + + + %1 is already installed on the server. + 服务器上已经安装 %1。 + + + + +Added containers that were already installed on the server + 添加已安装在服务器上的容器 + + + + +Already installed containers were found on the server. All installed containers have been added to the application + +在服务上发现已经安装协议并添加至应用 + + + + Settings updated successfully + 配置更新成功 + + + + Server '%1' was rebooted + 服务器 '%1' 已重新启动 + + + + Server '%1' was removed + 已移除服务器 '%1' + + + + All containers from server '%1' have been removed + 服务器 '%1' 的所有容器已移除 + + + + %1 has been removed from the server '%2' + %1 已从服务器 '%2' 上移除 + + + + Api config removed + + + + + %1 cached profile cleared + + + + + Please login as the user + 请以用户身份登录 + + + + Server added successfully + 增加服务器成功 @@ -429,12 +611,12 @@ Already installed containers were found on the server. All installed containers - + application name - + Add selected @@ -460,41 +642,59 @@ Already installed containers were found on the server. All installed containers NotificationHandler - - + + AmneziaVPN - + VPN Connected 已连接到VPN - + VPN Disconnected 已从VPN断开 - + AmneziaVPN notification AmneziaVPN 提示 - + Unsecured network detected: 发现不安全网络 + + OtpCodeDrawer + + + OTP code was sent to your email + + + + + OTP Code + + + + + Continue + 继续 + + PageDeinstalling - + Removing services from %1 正从 %1 移除服务 - + Usually it takes no more than 5 minutes 大约5分钟之内完成 @@ -502,12 +702,12 @@ Already installed containers were found on the server. All installed containers PageDevMenu - + Gateway endpoint - + Dev gateway environment @@ -515,27 +715,48 @@ Already installed containers were found on the server. All installed containers PageHome - + + You've successfully switched to the new Amnezia Premium subscription! + + + + + Old keys will no longer work. Please use your new subscription key to connect. +Thank you for staying with us! + + + + + Continue + 继续 + + + Logging enabled - + + Dev gateway enabled + + + + Split tunneling enabled 用户分隔隧道已启用 - + Split tunneling disabled 分隔隧道已禁用 - + VPN protocol VPN协议 - + Servers 服务器 @@ -547,52 +768,77 @@ Already installed containers were found on the server. All installed containers PageProtocolAwgClientSettings - + AmneziaWG settings AmneziaWG 配置 - + MTU - + + I1 - First special junk packet + + + + + I2 - Second special junk packet + + + + + I3 - Third special junk packet + + + + + I4 - Fourth special junk packet + + + + + I5 - Fifth special junk packet + + + + Server settings - + Port 端口 - + Save 保存 - + Save settings? 保存设置? - + Only the settings for this device will be changed - + Continue 继续 - + Cancel 取消 - + Unable change settings while there is an active connection @@ -600,12 +846,12 @@ Already installed containers were found on the server. All installed containers PageProtocolAwgSettings - + AmneziaWG settings AmneziaWG 配置 - + Port 端口 @@ -618,92 +864,127 @@ Already installed containers were found on the server. All installed containers 从服务上移除AmneziaWG? - + All users with whom you shared a connection with will no longer be able to connect to it. 与您共享连接的所有用户将无法再连接到该连接。 - + Save 保存 - + VPN address subnet VPN 地址子网 - + Jc - Junk packet count - + Jmin - Junk packet minimum size - + Jmax - Junk packet maximum size - + S1 - Init packet junk size - + S2 - Response packet junk size - + + S3 - Cookie reply packet junk size + + + + + S4 - Transport packet junk size + + + + H1 - Init packet magic header - + H2 - Response packet magic header - + H4 - Transport packet magic header - + + The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92) + S3 + cookie reply size (64) + S4 + transport packet size (32) + + + + H3 - Underload packet magic header - + + I1 - Special junk 1 + + + + + I2 - Special junk 2 + + + + + I3 - Special junk 3 + + + + + I4 - Special junk 4 + + + + + I5 - Special junk 5 + + + + The values of the H1-H4 fields must be unique - - The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92) - - - - + Save settings? 保存设置? - + Continue 继续 - + Cancel 取消 - + Unable change settings while there is an active connection @@ -711,33 +992,53 @@ Already installed containers were found on the server. All installed containers PageProtocolCloakSettings - + Cloak settings Cloak 配置 - + Disguised as traffic from 伪装流量为 - + Port 端口 - - + + Cipher 加密算法 - + Save 保存 + + + Save settings? + 保存设置? + + + + All users with whom you shared a connection with will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + + + Continue + 继续 + + Cancel + 取消 + + + Unable change settings while there is an active connection @@ -745,170 +1046,179 @@ Already installed containers were found on the server. All installed containers PageProtocolOpenVpnSettings - OpenVPN settings - OpenVPN 配置 + OpenVPN 配置 - + + OpenVPN Settings + + + + VPN address subnet VPN 地址子网 - + Network protocol 网络协议 - + Port 端口 - + Auto-negotiate encryption 自定义加密方式 - - + + Hash - + SHA512 - + SHA384 - + SHA256 - + SHA3-512 - + SHA3-384 - + SHA3-256 - + whirlpool - + BLAKE2b512 - + BLAKE2s256 - + SHA1 + - Cipher - + AES-256-GCM - + AES-192-GCM - + AES-128-GCM - + AES-256-CBC - + AES-192-CBC - + AES-128-CBC - + ChaCha20-Poly1305 - + ARIA-256-CBC - + CAMELLIA-256-CBC - + none - + TLS auth TLS认证 - + Block DNS requests outside of VPN 阻止VPN外的DNS请求 - + Additional client configuration commands 附加客户端配置命令 - - + + Commands: 命令: - + Additional server configuration commands 附加服务器端配置命令 - + + Save settings? + 保存设置? + + + Unable change settings while there is an active connection @@ -921,11 +1231,12 @@ Already installed containers were found on the server. All installed containers 从服务器移除OpenVPN吗? + All users with whom you shared a connection with will no longer be able to connect to it. - 与您共享连接的所有用户将无法再连接到该连接。 + 与您共享连接的所有用户将无法再连接到该连接。 - + Save 保存 @@ -934,23 +1245,25 @@ Already installed containers were found on the server. All installed containers 与您共享连接的所有用户将无法再连接到此链接 + Continue - 继续 + 继续 + Cancel - 取消 + 取消 PageProtocolRaw - + settings 配置 - + Show connection options 显示连接选项 @@ -959,22 +1272,22 @@ Already installed containers were found on the server. All installed containers 连接选项 - + Connection options %1 %1 连接选项 - + Remove 移除 - + Remove %1 from server? 从服务器移除 %1 ? - + All users with whom you shared a connection with will no longer be able to connect to it. 与您共享连接的所有用户将无法再连接到该连接。 @@ -987,12 +1300,12 @@ Already installed containers were found on the server. All installed containers 与您共享连接的所有用户将无法再连接到此链接 - + Continue 继续 - + Cancel 取消 @@ -1000,28 +1313,48 @@ Already installed containers were found on the server. All installed containers PageProtocolShadowSocksSettings - + Shadowsocks settings Shadowsocks 配置 - + Port 端口 - - + + Cipher 加密算法 - + Save 保存 - + + Save settings? + 保存设置? + + + + All users with whom you shared a connection with will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + + + Continue + 继续 + + + + Cancel + 取消 + + + Unable change settings while there is an active connection @@ -1029,52 +1362,52 @@ Already installed containers were found on the server. All installed containers PageProtocolWireGuardClientSettings - + WG settings - + MTU - + Server settings - + Port 端口 - + Save 保存 - + Save settings? 保存设置? - + Only the settings for this device will be changed - + Continue 继续 - + Cancel 取消 - + Unable change settings while there is an active connection @@ -1082,32 +1415,32 @@ Already installed containers were found on the server. All installed containers PageProtocolWireGuardSettings - + WG settings - + VPN address subnet VPN 地址子网 - + Port 端口 - + Save settings? 保存设置? - + All users with whom you shared a connection with will no longer be able to connect to it. 与您共享连接的所有用户将无法再连接到该连接。 - + Unable change settings while there is an active connection @@ -1116,17 +1449,17 @@ Already installed containers were found on the server. All installed containers 与您共享连接的所有用户将无法再连接到该连接。 - + Continue 继续 - + Cancel 取消 - + Save 保存 @@ -1134,22 +1467,47 @@ Already installed containers were found on the server. All installed containers PageProtocolXraySettings - + XRay settings - + Disguised as traffic from 伪装流量为 - + + Port + 端口 + + + Save 保存 - + + Save settings? + 保存设置? + + + + All users with whom you shared a connection with will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + + + Continue + 继续 + + + + Cancel + 取消 + + + Unable change settings while there is an active connection @@ -1157,29 +1515,29 @@ Already installed containers were found on the server. All installed containers PageServiceDnsSettings - + A DNS service is installed on your server, and it is only accessible via VPN. 您的服务器已安装DNS服务,仅能通过VPN访问。 - + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. 其地址与您的服务器地址相同。您可以在 设置 连接 中进行配置。 - + Remove 移除 - + Remove %1 from server? 从服务器移除 %1 ? - + Cannot remove AmneziaDNS from running server @@ -1188,12 +1546,12 @@ Already installed containers were found on the server. All installed containers 从服务器 - + Continue 继续 - + Cancel 取消 @@ -1206,62 +1564,62 @@ Already installed containers were found on the server. All installed containers 配置更新成功 - + SFTP settings SFTP 配置 - + Host 主机 - - - - + + + + Copied 拷贝 - + Port 端口 - + User name 用户名 - + Password 密码 - + Mount folder on device 挂载文件夹 - + In order to mount remote SFTP folder as local drive, perform following steps: <br> 为将远程 SFTP 文件夹挂载到本地,请执行以下步骤: <br> - - + + <br>1. Install the latest version of <br>1. 安装最新版的 - - + + <br>2. Install the latest version of <br>2. 安装最新版的 - + Detailed instructions 详细说明 @@ -1290,64 +1648,64 @@ Already installed containers were found on the server. All installed containers 配置更新成功 - - + + SOCKS5 settings - + Host 主机 - - - - + + + + Copied - - + + Port 端口 - + User name 用户名 - - + + Password 密码 - + Username - - + + Change connection settings - + The port must be in the range of 1 to 65535 - + Password cannot be empty - + Username cannot be empty @@ -1360,32 +1718,32 @@ Already installed containers were found on the server. All installed containers 配置更新成功 - + Tor website settings Tor网站配置 - + Website address 网址 - + Copied 已拷贝 - + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this URL. 用 <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> 打开上面网址. - + After creating your onion site, it takes a few minutes for the Tor network to make it available for use. 创建您的洋葱网站后,需要几分钟时间,才能使其在Tor网络上可用 - + When configuring WordPress set the this onion address as domain. 配置 WordPress 时,将此洋葱地址设置为域。 @@ -1409,42 +1767,47 @@ Already installed containers were found on the server. All installed containers PageSettings - + Settings 设置 - + Servers 服务器 - + Connection 连接 - + Application 应用 - + + News & Notifications + + + + Backup 备份 - + About AmneziaVPN 关于 - + Dev console - + Close application 关闭应用 @@ -1458,32 +1821,32 @@ And if you don't like the app, all the more support it - the donation will 如果您不喜欢,请捐助支持我们改进它。 - + Support Amnezia 支持Amnezia - + Amnezia is a free and open-source application. You can support the developers if you like it. Amnezia 是一款免费的开源应用程序。 如果您喜欢的话可以支持开发者。 - + Contacts 联系方式 - + Telegram group 电报群 - + To discuss features 用于功能讨论 - + https://t.me/amnezia_vpn_en @@ -1492,57 +1855,57 @@ And if you don't like the app, all the more support it - the donation will 邮件 - + support@amnezia.org - + For reviews and bug reports 用于评论和提交软件的缺陷 - + mailto:support@amnezia.org - + GitHub GitHub - + Discover the source code - + https://github.com/amnezia-vpn/amnezia-client https://github.com/amnezia-vpn/amnezia-client - + Website 官网 - + Visit official website - + Software version: %1 软件版本: %1 - + Check for updates 检查更新 - + Privacy Policy 隐私政策 @@ -1556,6 +1919,11 @@ And if you don't like the app, all the more support it - the donation will + Unable change server location while trying to make an active connection + + + + Unable change server location while there is an active connection @@ -1604,7 +1972,7 @@ And if you don't like the app, all the more support it - the donation will - This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect. + This will unlink the device from your subscription. You can reconnect it anytime by pressing "Reload API config" in subscription settings on device. @@ -1625,69 +1993,69 @@ And if you don't like the app, all the more support it - the donation will Windows - - - https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#windows - - macOS - - - https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#macos - - Android - - - https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#android - - AndroidTV - - - https://docs.amnezia.org/ru/documentation/instructions/android_tv_connect/ - - iOS - - - https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#ios - - Linux - - - https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#linux - - Routers + + + documentation/instructions/connect-amnezia-premium#windows + + + + + documentation/instructions/connect-amnezia-premium#macos + + + + + documentation/instructions/connect-amnezia-premium#android + + + + + documentation/instructions/android_tv_connect/ + + + + + documentation/instructions/connect-amnezia-premium#ios + + + + + documentation/instructions/connect-amnezia-premium#linux + + - https://docs.amnezia.org/documentation/instructions/connect-amnezia-premium#routers + documentation/instructions/connect-amnezia-premium#routers @@ -1709,77 +2077,77 @@ And if you don't like the app, all the more support it - the donation will 保存配置 - + Configuration Files - + For router setup or the AmneziaWG app - + The configuration needs to be reissued - + configuration file - + Generate a new configuration file - + The previously created one will stop working - + Revoke the current configuration file - + Config file saved - + The config has been revoked - + Generate a new %1 configuration file? - + Revoke the current %1 configuration file? - + Your previous configuration file will no longer work, and it will not be possible to connect using it - + Download - + Continue 继续 - + Cancel 取消 @@ -1792,135 +2160,168 @@ And if you don't like the app, all the more support it - the donation will - + Valid Until - + Active Connections - - Configurations have been updated for some countries. Download and install the updated configuration files + + Use VLESS protocol - - Subscription Key + + Cannot change protocol during active connection - Amnezia Premium subscription key + Configurations have been updated for some countries. Download and install the updated configuration files - - Save VPN key as a file + + Subscription Key - - Copy VPN key - - - - + Configuration Files - + Manage configuration files - + Active Devices - + Manage currently connected devices - + Support - + How to connect on another device - + Reload API config - + Reload API config? - - - + + + Continue 继续 - - - + + + Cancel 取消 - + Cannot reload API config during active connection - + Unlink this device - + Are you sure you want to unlink this device? - - This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect. + + This will unlink the device from your subscription. You can reconnect it anytime by pressing "Reload API config" in subscription settings on device. - + Cannot unlink device during active connection - + Remove from application - + Remove from application? - + Cannot remove server during active connection + + PageSettingsApiSubscriptionKey + + + Copy key + + + + + Copied + + + + + Save key as a file + + + + + Save AmneziaVPN config + 保存配置 + + + + Config files (*.vpn) + + + + + Show key text + + + + + To read the QR code in the Amnezia app, tap + in the main menu → 'QR code' + + + PageSettingsApiSupport @@ -1933,31 +2334,16 @@ And if you don't like the app, all the more support it - the donation will Email - - - support@amnezia.org - - Email Billing & Orders - - - help@vpnpay.io - - Website 官网 - - - amnezia.org - - Support @@ -1969,12 +2355,12 @@ And if you don't like the app, all the more support it - the donation will - + Support tag - + Copied @@ -1992,47 +2378,52 @@ And if you don't like the app, all the more support it - the donation will - + Apps from the list should not have access via VPN - + App split tunneling - + Mode 规则 - + + Only "Apps from the list should not have access via VPN" mode is available on Windows + + + + Remove - + Continue 继续 - + Cancel 取消 - + application name - + Open executable file - + Executable files (*.*) @@ -2040,27 +2431,27 @@ And if you don't like the app, all the more support it - the donation will PageSettingsApplication - + Application 应用 - + Allow application screenshots 允许截屏 - + Enable notifications - + Enable notifications to show the VPN state in the status bar - + Auto start 自动运行 @@ -2073,77 +2464,81 @@ And if you don't like the app, all the more support it - the donation will 启动时自动运行运用程序 - + Launch the application every time the device is starts 每次设备启动时启动应用程序 - + Auto connect 自动连接 - + Connect to VPN on app start 应用开启时连接VPN - + Start minimized 最小化 - Launch application minimized - 开启应用软件时窗口最小化 + 开启应用软件时窗口最小化 - + + Launch application minimized (works with autostart option turned on) + + + + Language 语言 - + Logging 日志 - + Enabled 开启 - + Disabled 禁用 - + Reset settings and remove all data from the application 重置并清理应用的所有数据 - + Reset settings and remove all data from the application? 重置并清理应用的所有数据? - + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. 所有配置恢复为默认值。服务器已安装的AmneziaVPN服务将被保留。 - + Continue 继续 - + Cancel 取消 - + Cannot reset settings during active connection @@ -2160,73 +2555,73 @@ And if you don't like the app, all the more support it - the donation will 帮助您在下次安装时立即恢复连接设置 - + Back up your configuration 备份您的配置 - + You can save your settings to a backup file to restore them the next time you install the application. 您可以将配置信息备份到文件中,以便在下次安装应用软件时恢复配置 - + The backup will contain your passwords and private keys for all servers added to AmneziaVPN. Keep this information in a secure place. 备份将包含您添加到 AmneziaVPN 的所有服务器的密码和私钥。请将这些信息保存在安全的地方。 - + Make a backup 进行备份 - + Save backup file 保存备份 - - + + Backup files (*.backup) - + Backup file saved 备份文件已保存 - + Restore from backup 从备份还原 - + Open backup file 打开备份文件 - + Import settings from a backup file? 从备份文件导入设置? - + All current settings will be reset 当前所有设置将重置 - + Continue 继续 - + Cancel 取消 - + Cannot restore backup settings during active connection @@ -2234,33 +2629,28 @@ And if you don't like the app, all the more support it - the donation will PageSettingsConnection - + Connection 连接 - + When AmneziaDNS is not used or installed 当未使用或未安装AmneziaDNS时 - + Allows you to use the VPN only for certain Apps 只允许在某些应用程序中使用 VPN - + KillSwitch - - - Disables your internet if your encrypted VPN connection drops out for any reason. - - - Cannot change KillSwitch settings during active connection + Blocks network connections without VPN @@ -2268,17 +2658,17 @@ And if you don't like the app, all the more support it - the donation will 使用AmneziaDNS,如其已安装在服务器上 - + Use AmneziaDNS 使用AmneziaDNS - + If AmneziaDNS is installed on the server 如果已在服务器安装AmneziaDNS - + DNS servers DNS服务器 @@ -2287,17 +2677,17 @@ And if you don't like the app, all the more support it - the donation will 如果未使用或未安装AmneziaDNS - + Site-based split tunneling 基于网站的隧道分离 - + Allows you to select which sites you want to access through the VPN 配置想要通过VPN访问网站 - + App-based split tunneling 基于应用的隧道分离 @@ -2321,75 +2711,224 @@ And if you don't like the app, all the more support it - the donation will PageSettingsDns - + Default server does not support custom DNS 默认服务器不支持自定义 DNS - + DNS servers DNS服务器 - + If AmneziaDNS is not used or installed 如果未使用或未安装AmneziaDNS - + Primary DNS 首选 DNS - + Secondary DNS 备用 DNS - + Restore default 恢复默认配置 - + Restore default DNS settings? 是否恢复默认DNS配置? - + Continue 继续 - + Cancel 取消 - + Settings have been reset 已重置 - + Save 保存 - + Settings saved 配置已保存 + + PageSettingsKillSwitch + + + KillSwitch + + + + + Enable to ensure network traffic goes through a secure VPN tunnel, preventing accidental exposure of your IP and DNS queries if the connection drops + + + + + KillSwitch settings cannot be changed during an active connection + + + + + Soft KillSwitch + + + + + Internet access is blocked if the VPN disconnects unexpectedly + + + + + Strict KillSwitch + + + + + Internet connection is blocked even when VPN is turned off manually or hasn't started + + + + + Just a little heads-up + + + + + If the VPN disconnects or drops while Strict KillSwitch is enabled, internet access will be blocked. To restore access, reconnect VPN or disable/change the KillSwitch. + + + + + Continue + 继续 + + + + Cancel + 取消 + + + + DNS Exceptions + + + + + DNS servers listed here will remain accessible when KillSwitch is active. + + + + + PageSettingsKillSwitchExceptions + + + DNS Exceptions + + + + + DNS servers listed here will remain accessible when KillSwitch is active + + + + + Delete + + + + + Continue + 继续 + + + + Cancel + 取消 + + + + IPv4 address + + + + + Import / Export addresses + + + + + Import + 导入 + + + + Save address list + + + + + Save addresses + + + + + + + Address files (*.json) + + + + + Import address list + + + + + Replace address list + + + + + + Open address file + + + + + Add imported addresses to existing ones + + + PageSettingsLogging - + Logging 日志 - + Enabling this function will save application's logs automatically. By default, logging functionality is disabled. Enable log saving in case of application malfunction. 默认情况下,日志功能是禁用的。如果应用程序出现故障,则启用日志保存功能。 @@ -2402,20 +2941,20 @@ And if you don't like the app, all the more support it - the donation will 打开日志文件夹 - - + + Save 保存 - - + + Logs files (*.log) - - + + Logs file saved 日志文件已保存 @@ -2424,75 +2963,83 @@ And if you don't like the app, all the more support it - the donation will 保存日志到文件 - + Enable logs - + Clear logs? 清理日志? - + Continue 继续 - + Cancel 取消 - + Logs have been cleaned up 日志已清理 - + Client logs - + AmneziaVPN logs - + Open logs folder - + Export logs - + Service logs - + AmneziaVPN-service logs - + Clear logs 清理日志 + + PageSettingsNewsNotifications + + + News & Notifications + + + PageSettingsServerData - + All installed containers have been added to the application 所有已安装的容器,已被添加到应用软件 - + No new installed containers found 未发现新安装的容器 @@ -2509,112 +3056,112 @@ And if you don't like the app, all the more support it - the donation will 清除缓存? - + Do you want to reboot the server? 您想重新启动服务器吗? - + Do you want to clear server from Amnezia software? 您要清除服务器上的Amnezia软件吗? - - - - - - - - - + + + + Continue 继续 - - - - + + + + Cancel 取消 - + Check the server for previously installed Amnezia services 检查服务器上,是否存在之前安装的 Amnezia 服务 - + Add them to the application if they were not displayed 如果存在且未显示,则添加到应用软件 - + Reboot server 重新启动服务器 - + The reboot process may take approximately 30 seconds. Are you sure you wish to proceed? 重新启动过程可能需要大约30秒。您确定要继续吗? - + Cannot reboot server during active connection - + Remove server from application 移除本地服务器信息 - + Do you want to remove the server from application? 您想要从应用程序中移除服务器吗? - + Cannot remove server during active connection - + All users whom you shared a connection with will no longer be able to connect to it. 与您共享连接的所有用户将无法再连接到该连接。 - + Cannot clear server from Amnezia software during active connection - + Reset API config 重置 API 配置 - + Do you want to reset API config? 您想重置 API 配置吗? - + Cannot reset API config during active connection + + + Switch to the new Amnezia Premium subscription + + Remove server? 移除本地服务器信息? - + All installed AmneziaVPN services will still remain on the server. 所有已安装的 AmneziaVPN 服务仍将保留在服务器上。 - + Clear server from Amnezia software 清理Amnezia中服务器信息 @@ -2660,62 +3207,57 @@ And if you don't like the app, all the more support it - the donation will PageSettingsServerProtocol - + settings 配置 - + Clear %1 profile? - - - - - - + connection settings - + Click the "connect" button to create a connection configuration - + server settings - + Clear profile - + The connection configuration will be deleted for this device only - + Unable to clear %1 profile while there is an active connection - + Remove 移除 - + All users with whom you shared a connection will no longer be able to connect to it. 与您共享连接的所有用户将无法再连接到该连接。 - + Cannot remove active container @@ -2728,7 +3270,7 @@ And if you don't like the app, all the more support it - the donation will 从服务器 - + Remove %1 from server? 从服务器移除 %1 ? @@ -2737,14 +3279,14 @@ And if you don't like the app, all the more support it - the donation will 与您共享连接的所有用户将无法再连接到此链接 - - + + Continue 继续 - - + + Cancel 取消 @@ -2786,27 +3328,29 @@ And if you don't like the app, all the more support it - the donation will 不使用VPN访问 - + Split tunneling 隧道分离 - + Mode 规则 - + Remove 移除 - + + Continue 继续 - + + Cancel 取消 @@ -2829,55 +3373,74 @@ And if you don't like the app, all the more support it - the donation will 只有这里列出的网站将通过VPN访问 - + website or IP 网站或IP - Import / Export Sites - 导入/导出网站 + 导入/导出网站 - + + Additional options + + + + Import 导入 - + Save site list 保存网址 - + Save sites 保存网址 - - - + + + Sites files (*.json) - + + Clear site list + + + + + Clear site list? + + + + + All sites will be removed from list. + + + + Import a list of sites 导入网址列表 - + Replace site list 替换网址列表 - - + + Open sites file 打开网址文件 - + Add imported sites to existing ones 将导入的网址添加到现有网址中 @@ -2885,32 +3448,32 @@ And if you don't like the app, all the more support it - the donation will PageSetupWizardApiServiceInfo - + For the region - + Price - + Work period - + Speed - + Features - + Connect 连接 @@ -2918,12 +3481,12 @@ And if you don't like the app, all the more support it - the donation will PageSetupWizardApiServicesList - + VPN by Amnezia - + Choose a VPN service that suits your needs. @@ -2954,122 +3517,146 @@ It's okay as long as it's from someone you trust. 包含连接配置或备份的文件 - + Connection 连接 - + Settings 设置 - + Enable logs + + + Export client logs + + + Save + 保存 + + + + Logs files (*.log) + + + + + Logs file saved + 日志文件已保存 + + + Support tag - + Copied - + Insert the key, add a configuration file or scan the QR-code - + Insert key - + Insert 插入 - + Continue 继续 - + Other connection options - + Site Amnezia - - Restore purchases - 恢复购买 - - - + VPN by Amnezia - + Connect to classic paid and free VPN services from Amnezia - + Self-hosted VPN - + Configure Amnezia VPN on your own server - + Restore from backup 从备份还原 - + + + + + - + Open backup file 打开备份文件 - + Backup files (*.backup) - + File with connection settings 包含连接配置的文件 - + Open config file 打开配置文件 - + QR code 二维码 - + + Restore purchases + + + + I have nothing 我没有 @@ -3085,12 +3672,12 @@ It's okay as long as it's from someone you trust. 连接服务器 - + Configure your server 配置服务器 - + Server IP address [:port] 服务器IP [:端口] @@ -3103,12 +3690,12 @@ It's okay as long as it's from someone you trust. 密码 或 私钥 - + Continue 继续 - + All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties 您输入的所有数据将严格保密,不会与 Amnezia 或任何第三方共享或披露 @@ -3119,47 +3706,48 @@ and will not be shared or disclosed to the Amnezia or any third parties 不会向 Amnezia 或任何第三方分享或披露 - + 255.255.255.255:22 - + SSH Username SSH 用户名 - + + Password or SSH private key 密码或 SSH 私钥 - + How to run your VPN server - + Where to get connection data, step-by-step instructions for buying a VPS - + Ip address cannot be empty IP不能为空 - + Enter the address in the format 255.255.255.255:88 按照这种格式输入 255.255.255.255:88 - + Login cannot be empty 账号不能为空 - + Password/private key cannot be empty 密码或私钥不能为空 @@ -3171,22 +3759,22 @@ and will not be shared or disclosed to the Amnezia or any third parties 您所在地区的互联网管控力度如何? - + Choose Installation Type - + Manual - + Choose a VPN protocol 选择 VPN 协议 - + Skip setup 跳过设置 @@ -3199,7 +3787,7 @@ and will not be shared or disclosed to the Amnezia or any third parties 我想选择VPN协议 - + Continue 继续 @@ -3212,27 +3800,27 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardInstalling - + Usually it takes no more than 5 minutes 通常不超过5分钟 - + The server has already been added to the application 服务器已添加到应用软件中 - + Amnezia has detected that your server is currently Amnezia 检测到您的服务器当前 - + busy installing other software. Amnezia installation 正安装其他软件。Amnezia安装 - + Cancel installation 取消安装 @@ -3245,12 +3833,12 @@ and will not be shared or disclosed to the Amnezia or any third parties 正安装其他软件。Amnezia安装 - + will pause until the server finishes installing other software 将暂停,直到其他软件安装完成。 - + Installing 安装中 @@ -3258,37 +3846,37 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardProtocolSettings - + Installing %1 正在安装 %1 - + More detailed 更多细节 - + Close 关闭 - + Network protocol 网络协议 - + Port 端口 - + Install 安装 - + The port must be in the range of 1 to 65535 @@ -3296,12 +3884,12 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardProtocols - + VPN protocol VPN 协议 - + Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. 选择你认为优先级最高的一项。稍后,您可以安装其他协议和附加服务,例如 DNS 代理和 SFTP。 @@ -3345,27 +3933,27 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardTextKey - + Connection key 连接授权码 - + A line that starts with vpn://... 以 vpn://... 开始的行 - + Key 授权码 - + Insert 插入 - + Continue 继续 @@ -3373,7 +3961,7 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardViewConfig - + New connection 新连接 @@ -3382,27 +3970,27 @@ and will not be shared or disclosed to the Amnezia or any third parties 请勿使用公共来源的连接码。它可以被创建来拦截您的数据。 - + Collapse content 折叠内容 - + Show content 显示内容 - + Enable WireGuard obfuscation. It may be useful if WireGuard is blocked on your provider. - + Use connection codes only from sources you trust. Codes from public sources may have been created to intercept your data. 只使用您信任的来源提供的连接代码。公共来源的代码可能是为了拦截您的数据而创建的。 - + Connect 连接 @@ -3410,123 +3998,123 @@ and will not be shared or disclosed to the Amnezia or any third parties PageShare - + Save OpenVPN config 保存OpenVPN配置 - + Save WireGuard config 保存WireGuard配置 - + Save AmneziaWG config 保存 AmneziaWG 配置 - + Save Shadowsocks config 保存 Shadowsocks 配置 - + Save Cloak config 保存斗篷配置 - + Save XRay config - + For the AmneziaVPN app AmneziaVPN 应用 - + OpenVPN native format OpenVPN原生格式 - + WireGuard native format WireGuard原生格式 - + AmneziaWG native format AmneziaWG 本地格式 - + Shadowsocks native format Shadowsocks原生格式 - + Cloak native format Cloak原生格式 - + XRay native format - + Share VPN Access 共享 VPN 访问 - + Share full access to the server and VPN 共享服务器和VPN的完全访问权限 - + Use for your own devices, or share with those you trust to manage the server. 用于您自己的设备,或与您信任的人共享以管理服务器. - - + + Users 用户 - + Share VPN access without the ability to manage the server 共享 VPN 访问,无需管理服务器 - + Search 搜索 - + Creation date: %1 - + Latest handshake: %1 - + Data received: %1 - + Data sent: %1 - + Allowed IPs: %1 @@ -3535,42 +4123,42 @@ and will not be shared or disclosed to the Amnezia or any third parties 创建日期: - + Rename 重新命名 - + Client name 客户名称 - + Save 保存 - + Revoke 撤销 - + Revoke the config for a user - %1? 撤销用户的配置- %1? - + The user will no longer be able to connect to your server. 该用户将无法再连接到您的服务器. - + Continue 继续 - + Cancel 取消 @@ -3583,7 +4171,7 @@ and will not be shared or disclosed to the Amnezia or any third parties 访问VPN - + Connection 连接 @@ -3612,8 +4200,8 @@ and will not be shared or disclosed to the Amnezia or any third parties 服务器 - - + + Server 服务器 @@ -3626,7 +4214,7 @@ and will not be shared or disclosed to the Amnezia or any third parties 访问配置文件的内容为: - + File with connection settings to 连接配置文件的内容为: @@ -3635,109 +4223,151 @@ and will not be shared or disclosed to the Amnezia or any third parties 协议 - - + + Protocol 协议 - + Connection to 连接到 - + Config revoked 配置已撤销 - + + Save AmneziaVPN config + 保存配置 + + + User name 用户名 - - + + Connection format 连接格式 - - + + Share 共享 + + PageShareConnection + + + Share + 共享 + + + + Copy + 拷贝 + + + + Save AmneziaVPN config + 保存配置 + + + + Copy config string + 复制配置字符串 + + + + Show connection settings + 显示连接配置 + + + + + Copied + + + + + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" + 要应用二维码到 Amnezia,请底部工具栏点击“+”→“连接方式”→“二维码、授权码或配置文件” + + PageShareFullAccess - + Full access to the server and VPN 对服务器和VPN的完全访问权限 - + We recommend that you use full access to the server only for your own additional devices. 我们建议您仅为自己的附加设备使用服务器的完全访问权限. - + If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. 如果您与其他人共享完全访问权限,他们可以从服务器中删除和添加协议和服务,这将导致VPN对所有用户的工作出现问题。 - - + + Server 服务器 - + Accessing 访问 - + File with accessing settings to 访问配置文件的内容为 - + Share 共享 - + Access error! 访问错误 - Connection to - 连接到 + 连接到 - File with connection settings to - 连接配置文件的内容为 + 连接配置文件的内容为 PageStart - + Logging was disabled after 14 days, log files were deleted - + Settings restored from backup file 从备份文件还原配置 - + Logging is enabled. Note that logs will be automaticallydisabled after 14 days, and all log files will be deleted. @@ -3988,17 +4618,27 @@ and will not be shared or disclosed to the Amnezia or any third parties QObject - + SFTP service SFTP 服务 - - - + + + SOCKS5 proxy server + + + (version 2) + + + + + (version 1.5) + + No error @@ -4080,48 +4720,63 @@ and will not be shared or disclosed to the Amnezia or any third parties The user's password is required + + + Docker error: runc doesn't work on cgroups v2 + + + + + Server error: cgroup mountpoint does not exist + + + Docker error: The pull rate limit has been reached + + + + SSH request was denied SSH请求被拒绝 - + SSH request was interrupted SSH请求中断 - + SSH internal error SSH内部错误 - + Invalid private key or invalid passphrase entered 输入的私钥或密码无效 - + The selected private key format is not supported, use openssh ED25519 key types or PEM key types 不支持所选私钥格式,请使用 openssh ED25519 密钥类型或 PEM 密钥类型 - + Timeout connecting to server 连接服务器超时 - + SCP error: Generic failure - + Unable to open config file - + VPN Protocols is not installed. Please install VPN container at first 请先安装VPN协议 @@ -4179,88 +4834,110 @@ and will not be shared or disclosed to the Amnezia or any third parties Sftp 错误: 远程驱动器中没有媒介 - + VPN connection error VPN 连接错误 - - + + Error when retrieving configuration from API 从 API 检索配置时出错 - + This config has already been added to the application 该配置已添加到应用程序中 - + In the response from the server, an empty config was received - + SSL error occurred - + Server response timeout on api request - + Missing AGW public key - + Failed to decrypt response payload - + Missing list of available services - + The limit of allowed configurations per subscription has been exceeded - - - QFile error: The file could not be opened - - - QFile error: An error occurred when reading from the file + A migration error has occurred. Please contact our technical support - QFile error: The file could not be accessed + Please update the application to use this feature - QFile error: An unspecified error occurred + Your Amnezia Premium subscription has expired. + Please check your email for renewal instructions. + If you haven't received an email, please contact our support. + Unable to process purchase + + + + + QFile error: The file could not be opened + + + + + QFile error: An error occurred when reading from the file + + + + + QFile error: The file could not be accessed + + + + + QFile error: An unspecified error occurred + + + + QFile error: A fatal error occurred - + QFile error: The operation was aborted - + ErrorCode: %1. 错误代码: %1. @@ -4269,57 +4946,57 @@ and will not be shared or disclosed to the Amnezia or any third parties 配置保存到磁盘失败 - + OpenVPN config missing OpenVPN配置丢失 - + OpenVPN management server error OpenVPN 管理服务器错误 - + OpenVPN executable missing OpenVPN 可执行文件丢失 - + Shadowsocks (ss-local) executable missing Shadowsocks (ss-local) 执行文件丢失 - + Cloak (ck-client) executable missing Cloak (ck-client) 执行文件丢失 - + Amnezia helper service error Amnezia 服务连接失败 - + OpenSSL failed OpenSSL错误 - + Can't connect: another VPN connection is active 无法连接:另一个VPN连接处于活跃状态 - + Can't setup OpenVPN TAP network adapter 无法设置 OpenVPN TAP 网络适配器 - + VPN pool error: no available addresses VPN 池错误:没有可用地址 - + The config does not contain any containers and credentials for connecting to the server 配置不包含任何用于连接服务器的容器和凭据 @@ -4328,138 +5005,169 @@ and will not be shared or disclosed to the Amnezia or any third parties 该配置不包含任何用于连接到服务器的容器和凭据。 - + Internal error - + IPsec - - + + Website in Tor network 在 Tor 网络中架设网站 - + AmneziaDNS AmneziaDNS - + SFTP file sharing service SFTP文件共享服务 - + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. OpenVPN 是最流行的 VPN 协议,具有灵活的配置选项。它使用自己的安全协议与 SSL/TLS 进行密钥交换。 - + Shadowsocks masks VPN traffic, making it resemble normal web traffic, but it may still be detected by certain analysis systems. - + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probing detection. It is very resistant to detection, but offers low speed. - + WireGuard - popular VPN protocol with high performance, high speed and low power consumption. - + AmneziaWG is a special protocol from Amnezia based on WireGuard. It provides high connection speed and ensures stable operation even in the most challenging network conditions. - + XRay with REALITY masks VPN traffic as web traffic and protects against active probing. It is highly resistant to detection and offers high speed. - - - OpenVPN stands as one of the most popular and time-tested VPN protocols available. -It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. - -* Available in the AmneziaVPN across all platforms -* Normal power consumption on mobile devices -* Flexible customisation to suit user needs to work with different operating systems and devices -* Recognised by DPI systems and therefore susceptible to blocking -* Can operate over both TCP and UDP network protocols. - - - - - This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for protecting against detection. - -OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client and the server. - -Cloak protects OpenVPN from detection. - -Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected - -Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. - -* Available in the AmneziaVPN across all platforms -* High power consumption on mobile devices -* Flexible settings -* Not recognised by detection systems -* Works over TCP network protocol, 443 port. - - - - - - A relatively new popular VPN protocol with a simplified architecture. -WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. -WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. - -* Available in the AmneziaVPN across all platforms -* Low power consumption -* Minimum number of settings -* Easily recognised by DPI analysis systems, susceptible to blocking -* Works over UDP network protocol. - - - - - A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. -While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. -This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. - -* Available in the AmneziaVPN across all platforms -* Low power consumption -* Minimum number of settings -* Not recognised by traffic analysis systems -* Works over UDP network protocol. - - - - - The REALITY protocol, a pioneering development by the creators of XRay, is designed to provide the highest level of protection against detection through its innovative approach to security and privacy. -It uniquely identifies attackers during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting attackers to genuine websites, thus presenting an authentic TLS certificate and data. -This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, legitimate sites without the need for specific configurations. -Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, REALITY's innovative "friend or foe" recognition at the TLS handshake enhances security. This makes REALITY a robust solution for maintaining internet freedom. - - Shadowsocks - masks VPN traffic, making it similar to normal web traffic, but it may be recognized by analysis systems in some highly censored regions. Shadowsocks - 掩盖VPN流量,使其类似于正常的网络流量,但在一些高度审查的地区可能会被分析系统识别. - + IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. - + + + + + + + OpenVPN is one of the most popular and reliable VPN protocols. It uses SSL/TLS encryption, supports a wide variety of devices and operating systems, and is continuously improved by the community due to its open-source nature. It provides a good balance between speed and security but is easily recognized by DPI systems, making it susceptible to blocking. + +Features: +* Available on all AmneziaVPN platforms +* Normal battery consumption on mobile devices +* Flexible customization for various devices and OS +* Operates over both TCP and UDP protocols + + + + + Shadowsocks is based on the SOCKS5 protocol and encrypts connections using AEAD cipher. Although designed to be discreet, it doesn't mimic a standard HTTPS connection and can be detected by some DPI systems. Due to limited support in Amnezia, we recommend using the AmneziaWG protocol. + +Features: +* Available in AmneziaVPN only on desktop platforms +* Customizable encryption protocol +* Detectable by some DPI systems +* Operates over TCP protocol + + + + + + This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking. + +OpenVPN securely encrypts all internet traffic between your device and the server. + +The Cloak plugin further protects the connection from DPI detection. It modifies traffic metadata to disguise VPN traffic as regular web traffic and prevents detection through active probing. If an incoming connection fails authentication, Cloak serves a fake website, making your VPN invisible to traffic analysis systems. + +In regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection. + +Features: +* Available on all AmneziaVPN platforms +* High power consumption on mobile devices +* Flexible configuration options +* Undetectable by DPI systems +* Operates over TCP protocol on port 443 + + + + + WireGuard is a modern, streamlined VPN protocol offering stable connectivity and excellent performance across all devices. It uses fixed encryption settings, delivering lower latency and higher data transfer speeds compared to OpenVPN. However, WireGuard is easily identifiable by DPI systems due to its distinctive packet signatures, making it susceptible to blocking. + +Features: +* Available on all AmneziaVPN platforms +* Low power consumption on mobile devices +* Minimal configuration required +* Easily detected by DPI systems (susceptible to blocking) +* Operates over UDP protocol + + + + + AmneziaWG is a modern VPN protocol based on WireGuard, combining simplified architecture with high performance across all devices. It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, making VPN traffic indistinguishable from regular internet traffic. + +AmneziaWG is an excellent choice for those seeking a fast, stealthy VPN connection. + +Features: +* Available on all AmneziaVPN platforms +* Low battery consumption on mobile devices +* Minimal settings required +* Undetectable by traffic analysis systems (DPI) +* Operates over UDP protocol + + + + + REALITY is an innovative protocol developed by the creators of XRay, designed specifically to combat high levels of internet censorship. REALITY identifies censorship systems during the TLS handshake, redirecting suspicious traffic seamlessly to legitimate websites like google.com while providing genuine TLS certificates. This allows VPN traffic to blend indistinguishably with regular web traffic without special configuration. +Unlike older protocols such as VMess, VLESS, and XTLS-Vision, REALITY incorporates an advanced built-in "friend-or-foe" detection mechanism, effectively protecting against DPI and other traffic analysis methods. + +Features: +* Resistant to active probing and DPI detection +* No special configuration required to disguise traffic +* Highly effective in heavily censored regions +* Minimal battery consumption on devices +* Operates over TCP protocol + + + + + IKEv2, combined with IPSec encryption, is a modern and reliable VPN protocol. It reconnects quickly when switching networks or devices, making it ideal for dynamic network environments. While it provides good security and speed, it's easily recognized by DPI systems and susceptible to blocking. + +Features: +* Available in AmneziaVPN only on Windows +* Low battery consumption on mobile devices +* Minimal configuration required +* Detectable by DPI analysis systems(easily blocked) +* Operates over UDP protocol(ports 500 and 4500) + + + + After installation, Amnezia will create a file storage on your server. You will be able to access it using @@ -4482,7 +5190,7 @@ For more detailed information, you can OpenVPN over Cloak - OpenVPN与VPN结合,伪装成Web流量,并保护免受主动探测的侦测。非常适合在具有最高审查水平的地区绕过封锁 - + Create a file vault on your server to securely store and transfer files. 在您的服务器上创建一个文件保险库,用于安全存储和传输文件。 @@ -4527,12 +5235,12 @@ WireGuard非常容易被阻挡,因为其独特的数据包签名。与一些 IKEv2/IPsec - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。 - + Deploy a WordPress site on the Tor network in two clicks. 只需点击两次即可架设 WordPress 网站到 Tor 网络. - + Replace the current DNS server with your own. This will increase your privacy level. 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私保护级别。 @@ -4559,14 +5267,13 @@ It employs its unique security protocol, leveraging the strength of SSL/TLS for * 可以通过 TCP 和 UDP 网络协议运行. - Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. * Available in the AmneziaVPN only on desktop platforms * Configurable encryption protocol * Detectable by some DPI systems * Works over TCP network protocol. - Shadowsocks 受到 SOCKS5 协议的启发,使用 AEAD 密码保护连接。尽管 Shadowsocks 设计得谨慎且难以识别,但它与标准 HTTPS 连接并不相同。但是,某些流量分析系统可能仍会检测到 Shadowsocks 连接。由于Amnezia支持有限,建议使用AmneziaWG协议。 + Shadowsocks 受到 SOCKS5 协议的启发,使用 AEAD 密码保护连接。尽管 Shadowsocks 设计得谨慎且难以识别,但它与标准 HTTPS 连接并不相同。但是,某些流量分析系统可能仍会检测到 Shadowsocks 连接。由于Amnezia支持有限,建议使用AmneziaWG协议。 * 仅在桌面平台上的 AmneziaVPN 中可用 * 可配置的加密协议 @@ -4651,7 +5358,6 @@ This means that AmneziaWG keeps the fast performance of the original while addin * 通过 UDP 网络协议工作。 - IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol. One of its distinguishing features is its ability to swiftly switch between networks and devices, making it particularly adaptive in dynamic network environments. While it offers a blend of security, stability, and speed, it's essential to note that IKEv2 can be easily detected and is susceptible to blocking. @@ -4661,7 +5367,7 @@ While it offers a blend of security, stability, and speed, it's essential t * Minimal configuration * Recognised by DPI analysis systems * Works over UDP network protocol, ports 500 and 4500. - IKEv2 与 IPSec 加密层配合使用,是一种现代且稳定的 VPN 协议。 + IKEv2 与 IPSec 加密层配合使用,是一种现代且稳定的 VPN 协议。 其显着特征之一是能够在网络和设备之间快速切换,使其特别适应动态网络环境。 虽然 IKEv2 兼具安全性、稳定性和速度,但必须注意的是,IKEv2 很容易被检测到,并且容易受到阻止。 @@ -4692,7 +5398,7 @@ While it offers a blend of security, stability, and speed, it's essential t IPsec 容器 - + DNS Service DNS 服务 @@ -4886,6 +5592,12 @@ While it offers a blend of security, stability, and speed, it's essential t Can't find the colon separator between hostname and port + + + + AmneziaWG Legacy is a outdated version of AmneziaWG protocol. To upgrade, install AmneziaWG and recreate users. + + RenameServerDrawer @@ -4911,7 +5623,7 @@ While it offers a blend of security, stability, and speed, it's essential t ServersListView - + Unable change server while there is an active connection 已建立连接时无法更改服务器配置 @@ -4933,12 +5645,12 @@ While it offers a blend of security, stability, and speed, it's essential t SettingsController - + Backup file is corrupted 备份文件已损坏 - + All settings have been reset to default values 所配置恢复为默认值 @@ -4950,119 +5662,150 @@ While it offers a blend of security, stability, and speed, it's essential t ShareConnectionDrawer - - Save AmneziaVPN config - 保存配置 + 保存配置 - Share - 共享 + 共享 - Copy - 拷贝 + 拷贝 - - Copied - 已拷贝 + 已拷贝 - Copy config string - 复制配置字符串 + 复制配置字符串 - Show connection settings - 显示连接配置 + 显示连接配置 Show content 展示内容 - To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" - 要应用二维码到 Amnezia,请底部工具栏点击“+”→“连接方式”→“二维码、授权码或配置文件” + 要应用二维码到 Amnezia,请底部工具栏点击“+”→“连接方式”→“二维码、授权码或配置文件” SitesController - Hostname not look like ip adress or domain name - 请输入有效的域名或IP地址 + 请输入有效的域名或IP地址 - New site added: %1 - 已经添加新网站: %1 + 已经添加新网站: %1 - Site removed: %1 - 已移除网站: %1 + 已移除网站: %1 - Can't open file: %1 - 无法打开文件: %1 + 无法打开文件: %1 - Failed to parse JSON data from file: %1 - JSON解析失败,文件: %1 + JSON解析失败,文件: %1 - The JSON data is not an array in file: %1 - 文件中的JSON数据不是一个数组,文件: %1 + 文件中的JSON数据不是一个数组,文件: %1 - Import completed - 完成导入 + 完成导入 - Export completed - 完成导出 + 完成导出 + + + + SitesUiController + + + Hostname not look like ip adress or domain name + 请输入有效的域名或IP地址 + + + + New site added: %1 + 已经添加新网站: %1 + + + + Site removed: %1 + 已移除网站: %1 + + + + Site list cleared! + + + + + Can't open file: %1 + 无法打开文件: %1 + + + + Failed to parse JSON data from file: %1 + JSON解析失败,文件: %1 + + + + The JSON data is not an array in file: %1 + 文件中的JSON数据不是一个数组,文件: %1 + + + + Import completed + 完成导入 + + + + Export completed + 完成导出 SystemTrayNotificationHandler - + Show 显示 - + Connect 连接 - + Disconnect 断开 - + Visit Website 官网 - - + + Quit 退出 @@ -5070,7 +5813,7 @@ While it offers a blend of security, stability, and speed, it's essential t TextFieldWithHeaderType - + The field can't be empty 输入不能为空 @@ -5078,7 +5821,7 @@ While it offers a blend of security, stability, and speed, it's essential t VpnConnection - + Mbps @@ -5086,42 +5829,42 @@ While it offers a blend of security, stability, and speed, it's essential t VpnProtocol - + Unknown 未知 - + Disconnected 连接已断开 - + Preparing 准备中 - + Connecting... 连接中 - + Connected 已连接 - + Disconnecting... 断开中 - + Reconnecting... 重连中 - + Error 错误 @@ -5169,12 +5912,12 @@ While it offers a blend of security, stability, and speed, it's essential t 一些国外网站被屏蔽,但VPN提供商未被屏蔽 - + Automatic - + AmneziaWG protocol will be installed. It provides high connection speed and ensures stable operation even in the most challenging network conditions. @@ -5182,12 +5925,12 @@ While it offers a blend of security, stability, and speed, it's essential t main2 - + Private key passphrase 私钥密码 - + Save 保存 diff --git a/client/ui/controllers/allowedDnsController.h b/client/ui/controllers/allowedDnsController.h deleted file mode 100644 index 5509a036e..000000000 --- a/client/ui/controllers/allowedDnsController.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef ALLOWEDDNSCONTROLLER_H -#define ALLOWEDDNSCONTROLLER_H - -#include - -#include "settings.h" -#include "ui/models/allowed_dns_model.h" - -class AllowedDnsController : public QObject -{ - Q_OBJECT -public: - explicit AllowedDnsController(const std::shared_ptr &settings, - const QSharedPointer &allowedDnsModel, - QObject *parent = nullptr); - -public slots: - void addDns(QString ip); - void removeDns(int index); - - void importDns(const QString &fileName, bool replaceExisting); - void exportDns(const QString &fileName); - -signals: - void errorOccurred(const QString &errorMessage); - void finished(const QString &message); - - void saveFile(const QString &fileName, const QString &data); - -private: - std::shared_ptr m_settings; - QSharedPointer m_allowedDnsModel; -}; - -#endif // ALLOWEDDNSCONTROLLER_H diff --git a/client/ui/controllers/allowedDnsController.cpp b/client/ui/controllers/allowedDnsUiController.cpp similarity index 61% rename from client/ui/controllers/allowedDnsController.cpp rename to client/ui/controllers/allowedDnsUiController.cpp index 12e8b599f..f9d62a5a6 100644 --- a/client/ui/controllers/allowedDnsController.cpp +++ b/client/ui/controllers/allowedDnsUiController.cpp @@ -1,4 +1,4 @@ -#include "allowedDnsController.h" +#include "allowedDnsUiController.h" #include #include @@ -7,17 +7,22 @@ #include #include "systemController.h" -#include "core/networkUtilities.h" -#include "core/defs.h" +#include "core/utils/networkUtilities.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" -AllowedDnsController::AllowedDnsController(const std::shared_ptr &settings, - const QSharedPointer &allowedDnsModel, - QObject *parent) - : QObject(parent), m_settings(settings), m_allowedDnsModel(allowedDnsModel) +AllowedDnsUiController::AllowedDnsUiController(AllowedDnsController* allowedDnsController, + AllowedDnsModel* allowedDnsModel, + QObject *parent) + : QObject(parent), + m_allowedDnsController(allowedDnsController), + m_allowedDnsModel(allowedDnsModel) { + m_allowedDnsModel->updateModel(m_allowedDnsController->getCurrentDnsServers()); } -void AllowedDnsController::addDns(QString ip) +void AllowedDnsUiController::addDns(QString ip) { if (ip.isEmpty()) { return; @@ -28,23 +33,22 @@ void AllowedDnsController::addDns(QString ip) return; } - if (m_allowedDnsModel->addDns(ip)) { + if (m_allowedDnsController->addDns(ip)) { emit finished(tr("New DNS server added: %1").arg(ip)); } else { emit errorOccurred(tr("DNS server already exists: %1").arg(ip)); } } -void AllowedDnsController::removeDns(int index) +void AllowedDnsUiController::removeDns(int index) { auto modelIndex = m_allowedDnsModel->index(index); auto ip = m_allowedDnsModel->data(modelIndex, AllowedDnsModel::Roles::IpRole).toString(); - m_allowedDnsModel->removeDns(modelIndex); - + m_allowedDnsController->removeDns(index); emit finished(tr("DNS server removed: %1").arg(ip)); } -void AllowedDnsController::importDns(const QString &fileName, bool replaceExisting) +void AllowedDnsUiController::importDns(const QString &fileName, bool replaceExisting) { QByteArray jsonData; if (!SystemController::readFile(fileName, jsonData)) { @@ -77,14 +81,13 @@ void AllowedDnsController::importDns(const QString &fileName, bool replaceExisti dnsServers.append(ip); } - m_allowedDnsModel->addDnsList(dnsServers, replaceExisting); - + m_allowedDnsController->addDnsList(dnsServers, replaceExisting); emit finished(tr("Import completed")); } -void AllowedDnsController::exportDns(const QString &fileName) +void AllowedDnsUiController::exportDns(const QString &fileName) { - auto dnsServers = m_allowedDnsModel->getCurrentDnsServers(); + auto dnsServers = m_allowedDnsController->getCurrentDnsServers(); QJsonArray jsonArray; @@ -98,4 +101,9 @@ void AllowedDnsController::exportDns(const QString &fileName) SystemController::saveFile(fileName, jsonData); emit finished(tr("Export completed")); +} + +void AllowedDnsUiController::updateModel() +{ + m_allowedDnsModel->updateModel(m_allowedDnsController->getCurrentDnsServers()); } diff --git a/client/ui/controllers/allowedDnsUiController.h b/client/ui/controllers/allowedDnsUiController.h new file mode 100644 index 000000000..f1a30b967 --- /dev/null +++ b/client/ui/controllers/allowedDnsUiController.h @@ -0,0 +1,37 @@ +#ifndef ALLOWEDDNSUICONTROLLER_H +#define ALLOWEDDNSUICONTROLLER_H + +#include + +#include "core/controllers/allowedDnsController.h" +#include "ui/models/allowedDnsModel.h" + +class AllowedDnsUiController : public QObject +{ + Q_OBJECT +public: + explicit AllowedDnsUiController(AllowedDnsController* allowedDnsController, + AllowedDnsModel* allowedDnsModel, + QObject *parent = nullptr); + +public slots: + void addDns(QString ip); + void removeDns(int index); + + void importDns(const QString &fileName, bool replaceExisting); + void exportDns(const QString &fileName); + + void updateModel(); + +signals: + void errorOccurred(const QString &errorMessage); + void finished(const QString &message); + + void saveFile(const QString &fileName, const QString &data); + +private: + AllowedDnsController* m_allowedDnsController; + AllowedDnsModel* m_allowedDnsModel; +}; + +#endif // ALLOWEDDNSUICONTROLLER_H diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp deleted file mode 100644 index d55a74dbd..000000000 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ /dev/null @@ -1,1278 +0,0 @@ -#include "apiConfigsController.h" - -#include "amnezia_application.h" -#include "configurators/wireguard_configurator.h" -#include "core/api/apiDefs.h" -#include "core/api/apiUtils.h" -#include "core/controllers/gatewayController.h" -#include "core/qrCodeUtils.h" -#include "ui/controllers/systemController.h" -#include "version.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "platforms/ios/ios_controller.h" - -namespace -{ - namespace configKey - { - constexpr char cloak[] = "cloak"; - constexpr char awg[] = "awg"; - constexpr char vless[] = "vless"; - - constexpr char apiEndpoint[] = "api_endpoint"; - constexpr char accessToken[] = "api_key"; - constexpr char certificate[] = "certificate"; - constexpr char publicKey[] = "public_key"; - constexpr char protocol[] = "protocol"; - - constexpr char uuid[] = "installation_uuid"; - constexpr char osVersion[] = "os_version"; - constexpr char appVersion[] = "app_version"; - - constexpr char userCountryCode[] = "user_country_code"; - constexpr char serverCountryCode[] = "server_country_code"; - constexpr char serviceType[] = "service_type"; - constexpr char serviceInfo[] = "service_info"; - constexpr char serviceProtocol[] = "service_protocol"; - - constexpr char services[] = "services"; - constexpr char serviceDescription[] = "service_description"; - constexpr char subscriptionPlans[] = "subscription_plans"; - constexpr char storeProductId[] = "store_product_id"; - constexpr char priceLabel[] = "price_label"; - constexpr char subtitle[] = "subtitle"; - constexpr char isTrial[] = "is_trial"; - constexpr char minPriceLabel[] = "min_price_label"; - - constexpr char apiPayload[] = "api_payload"; - constexpr char keyPayload[] = "key_payload"; - - constexpr char apiConfig[] = "api_config"; - constexpr char authData[] = "auth_data"; - - constexpr char config[] = "config"; - - constexpr char isConnectEvent[] = "is_connect_event"; - } - - namespace serviceType - { - constexpr char amneziaFree[] = "amnezia-free"; - constexpr char amneziaPremium[] = "amnezia-premium"; - } - - struct ProtocolData - { - OpenVpnConfigurator::ConnectionData certRequest; - - QString wireGuardClientPrivKey; - QString wireGuardClientPubKey; - - QString xrayUuid; - }; - - struct GatewayRequestData - { - QString osVersion; - QString appVersion; - QString appLanguage; - - QString installationUuid; - - QString userCountryCode; - QString serverCountryCode; - QString serviceType; - QString serviceProtocol; - - QJsonObject authData; - - QJsonObject toJsonObject() const - { - QJsonObject obj; - if (!osVersion.isEmpty()) { - obj[configKey::osVersion] = osVersion; - } - if (!appVersion.isEmpty()) { - obj[configKey::appVersion] = appVersion; - } - if (!appLanguage.isEmpty()) { - obj[apiDefs::key::appLanguage] = appLanguage; - } - if (!installationUuid.isEmpty()) { - obj[configKey::uuid] = installationUuid; - } - if (!userCountryCode.isEmpty()) { - obj[configKey::userCountryCode] = userCountryCode; - } - if (!serverCountryCode.isEmpty()) { - obj[configKey::serverCountryCode] = serverCountryCode; - } - if (!serviceType.isEmpty()) { - obj[configKey::serviceType] = serviceType; - } - if (!serviceProtocol.isEmpty()) { - obj[configKey::serviceProtocol] = serviceProtocol; - } - if (!authData.isEmpty()) { - obj[configKey::authData] = authData; - } - return obj; - } - }; - - ProtocolData generateProtocolData(const QString &protocol) - { - ProtocolData protocolData; - if (protocol == configKey::cloak) { - protocolData.certRequest = OpenVpnConfigurator::createCertRequest(); - } else if (protocol == configKey::awg) { - auto connData = WireguardConfigurator::genClientKeys(); - protocolData.wireGuardClientPubKey = connData.clientPubKey; - protocolData.wireGuardClientPrivKey = connData.clientPrivKey; - } else if (protocol == configKey::vless) { - protocolData.xrayUuid = QUuid::createUuid().toString(QUuid::WithoutBraces); - } - - return protocolData; - } - - void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload) - { - if (protocol == configKey::cloak) { - apiPayload[configKey::certificate] = protocolData.certRequest.request; - } else if (protocol == configKey::awg) { - apiPayload[configKey::publicKey] = protocolData.wireGuardClientPubKey; - } else if (protocol == configKey::vless) { - apiPayload[configKey::publicKey] = protocolData.xrayUuid; - } - } - - ErrorCode fillServerConfig(const QString &protocol, const ProtocolData &apiPayloadData, const QByteArray &apiResponseBody, - QJsonObject &serverConfig) - { - QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString(); - - data.replace("vpn://", ""); - QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - - if (ba.isEmpty()) { - qDebug() << "empty vpn key"; - return ErrorCode::ApiConfigEmptyError; - } - - QByteArray ba_uncompressed = qUncompress(ba); - if (!ba_uncompressed.isEmpty()) { - ba = ba_uncompressed; - } - - QString configStr = ba; - if (protocol == configKey::cloak) { - configStr.replace("", "\n"); - configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); - } else if (protocol == configKey::awg) { - configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); - auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - auto containers = newServerConfig.value(config_key::containers).toArray(); - if (containers.isEmpty()) { - qDebug() << "missing containers field"; - return ErrorCode::ApiConfigEmptyError; - } - auto containerObject = containers.at(0).toObject(); - auto containerType = ContainerProps::containerFromString(containerObject.value(config_key::container).toString()); - QString containerName = ContainerProps::containerTypeToString(containerType); - auto serverProtocolConfig = containerObject.value(containerName).toObject(); - auto clientProtocolConfig = - QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object(); - - // TODO looks like this block can be removed after v1 configs EOL - - serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount); - serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize); - serverProtocolConfig[config_key::junkPacketMaxSize] = clientProtocolConfig.value(config_key::junkPacketMaxSize); - serverProtocolConfig[config_key::initPacketJunkSize] = clientProtocolConfig.value(config_key::initPacketJunkSize); - serverProtocolConfig[config_key::responsePacketJunkSize] = clientProtocolConfig.value(config_key::responsePacketJunkSize); - serverProtocolConfig[config_key::initPacketMagicHeader] = clientProtocolConfig.value(config_key::initPacketMagicHeader); - serverProtocolConfig[config_key::responsePacketMagicHeader] = clientProtocolConfig.value(config_key::responsePacketMagicHeader); - serverProtocolConfig[config_key::underloadPacketMagicHeader] = clientProtocolConfig.value(config_key::underloadPacketMagicHeader); - serverProtocolConfig[config_key::transportPacketMagicHeader] = clientProtocolConfig.value(config_key::transportPacketMagicHeader); - - serverProtocolConfig[config_key::cookieReplyPacketJunkSize] = clientProtocolConfig.value(config_key::cookieReplyPacketJunkSize); - serverProtocolConfig[config_key::transportPacketJunkSize] = clientProtocolConfig.value(config_key::transportPacketJunkSize); - serverProtocolConfig[config_key::specialJunk1] = clientProtocolConfig.value(config_key::specialJunk1); - serverProtocolConfig[config_key::specialJunk2] = clientProtocolConfig.value(config_key::specialJunk2); - serverProtocolConfig[config_key::specialJunk3] = clientProtocolConfig.value(config_key::specialJunk3); - serverProtocolConfig[config_key::specialJunk4] = clientProtocolConfig.value(config_key::specialJunk4); - serverProtocolConfig[config_key::specialJunk5] = clientProtocolConfig.value(config_key::specialJunk5); - - // - - containerObject[containerName] = serverProtocolConfig; - containers.replace(0, containerObject); - newServerConfig[config_key::containers] = containers; - configStr = QString(QJsonDocument(newServerConfig).toJson()); - } - - QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1); - serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2); - serverConfig[config_key::containers] = newServerConfig.value(config_key::containers); - serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); - - if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { - serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion); - serverConfig[config_key::description] = newServerConfig.value(config_key::description); - serverConfig[config_key::name] = newServerConfig.value(config_key::name); - } - - auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString(); - serverConfig[config_key::defaultContainer] = defaultContainer; - - QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap(); - map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap()); - auto apiConfig = QJsonObject::fromVariantMap(map); - - if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { - apiConfig.insert(apiDefs::key::supportedProtocols, - QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::supportedProtocols).toArray()); - - apiConfig.insert(apiDefs::key::serviceInfo, - QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::serviceInfo).toObject()); - } - - serverConfig[configKey::apiConfig] = apiConfig; - - return ErrorCode::NoError; - } - -#if defined(Q_OS_IOS) || defined(MACOS_NE) - struct StoreKitPlanQuote { - QString displayPrice; - double priceAmount = 0.0; - double subscriptionBillingMonths = 0.0; - QString displayPricePerMonth; - }; - - constexpr double kOneMonthThreshold = 1.0 + 1e-6; - constexpr double kMonthsFallbackThreshold = 1e-6; - constexpr double kMonthlyPriceEpsilon = 1e-9; - - QStringList collectPremiumStoreProductIds(const QJsonArray &services) - { - QStringList productIds; - QSet seenProductIds; - for (const QJsonValue &serviceValue : services) { - const QJsonObject serviceObject = serviceValue.toObject(); - if (serviceObject.value(configKey::serviceType).toString() != serviceType::amneziaPremium) { - continue; - } - const QJsonArray subscriptionPlans = - serviceObject.value(configKey::serviceDescription).toObject().value(configKey::subscriptionPlans).toArray(); - for (const QJsonValue &planValue : subscriptionPlans) { - if (!planValue.isObject()) { - continue; - } - const QString storeProductId = planValue.toObject().value(configKey::storeProductId).toString(); - if (storeProductId.isEmpty() || seenProductIds.contains(storeProductId)) { - continue; - } - seenProductIds.insert(storeProductId); - productIds.append(storeProductId); - } - } - return productIds; - } - - QHash buildStoreKitQuoteMap(const QList &fetchedProducts) - { - QHash quotesByProductId; - quotesByProductId.reserve(fetchedProducts.size()); - - for (const QVariantMap &productInfo : fetchedProducts) { - const QString productId = productInfo.value(QStringLiteral("productId")).toString(); - if (productId.isEmpty()) { - continue; - } - - QString displayPrice = productInfo.value(QStringLiteral("displayPrice")).toString(); - if (displayPrice.isEmpty()) { - const QString price = productInfo.value(QStringLiteral("price")).toString(); - const QString currencyCode = productInfo.value(QStringLiteral("currencyCode")).toString(); - displayPrice = currencyCode.isEmpty() ? price : (price + QLatin1Char(' ') + currencyCode); - } - - StoreKitPlanQuote quote; - quote.displayPrice = displayPrice; - quote.priceAmount = productInfo.value(QStringLiteral("priceAmount")).toDouble(); - quote.subscriptionBillingMonths = productInfo.value(QStringLiteral("subscriptionBillingMonths")).toDouble(); - quote.displayPricePerMonth = productInfo.value(QStringLiteral("displayPricePerMonth")).toString(); - quotesByProductId.insert(productId, quote); - } - - return quotesByProductId; - } - - void mergeStoreKitPricesIntoPremiumPlans(QJsonObject &data) - { - QJsonArray services = data.value(configKey::services).toArray(); - if (services.isEmpty()) { - return; - } - - const QStringList productIds = collectPremiumStoreProductIds(services); - if (productIds.isEmpty()) { - qInfo().noquote() << "[IAP] No store_product_id in premium plans; skip StoreKit merge into services payload"; - return; - } - - QList fetchedProducts; - QEventLoop loop; - IosController::Instance()->fetchProducts(productIds, - [&](const QList &products, const QStringList &invalidIds, - const QString &errorString) { - if (!errorString.isEmpty()) { - qWarning().noquote() << "[IAP] StoreKit merge fetch:" << errorString; - } - if (!invalidIds.isEmpty()) { - qWarning().noquote() << "[IAP] Unknown App Store product ids:" << invalidIds; - } - fetchedProducts = products; - loop.quit(); - }); - loop.exec(); - - const QHash quotesByProductId = buildStoreKitQuoteMap(fetchedProducts); - - for (int serviceIndex = 0; serviceIndex < services.size(); ++serviceIndex) { - QJsonObject serviceObject = services.at(serviceIndex).toObject(); - if (serviceObject.value(configKey::serviceType).toString() != serviceType::amneziaPremium) { - continue; - } - - QJsonObject descriptionObject = serviceObject.value(configKey::serviceDescription).toObject(); - const QJsonArray sourcePlans = descriptionObject.value(configKey::subscriptionPlans).toArray(); - - QJsonArray mergedPlans; - double minMonthlyAmount = std::numeric_limits::infinity(); - QString minMonthlyDisplay; - - for (const QJsonValue &planValue : sourcePlans) { - if (!planValue.isObject()) { - continue; - } - - QJsonObject planObject = planValue.toObject(); - const QString storeProductId = planObject.value(configKey::storeProductId).toString(); - if (storeProductId.isEmpty()) { - continue; - } - - const auto quoteIterator = quotesByProductId.constFind(storeProductId); - if (quoteIterator == quotesByProductId.cend()) { - continue; - } - - const bool isTrialPlan = planObject.value(configKey::isTrial).toBool(); - const StoreKitPlanQuote "e = *quoteIterator; - planObject.insert(configKey::priceLabel, quote.displayPrice); - - const double months = quote.subscriptionBillingMonths; - if (!isTrialPlan && months > kOneMonthThreshold && !quote.displayPricePerMonth.isEmpty()) { - planObject.insert( - configKey::subtitle, - QCoreApplication::translate("ApiConfigsController", "%1/mo", "IAP: price per month in plan subtitle") - .arg(quote.displayPricePerMonth)); - } - - if (!isTrialPlan && quote.priceAmount > 0.0) { - const double monthsForMin = months > kMonthsFallbackThreshold ? months : 1.0; - const double monthly = quote.priceAmount / monthsForMin; - if (monthly < minMonthlyAmount - kMonthlyPriceEpsilon) { - minMonthlyAmount = monthly; - minMonthlyDisplay = !quote.displayPricePerMonth.isEmpty() ? quote.displayPricePerMonth : quote.displayPrice; - } - } - - mergedPlans.append(planObject); - } - - descriptionObject.insert(configKey::subscriptionPlans, mergedPlans); - if (minMonthlyAmount < std::numeric_limits::infinity() && !minMonthlyDisplay.isEmpty()) { - descriptionObject.insert(configKey::minPriceLabel, - QCoreApplication::translate("ApiConfigsController", "from %1 per month", - "IAP: card footer minimum monthly price from StoreKit") - .arg(minMonthlyDisplay)); - } - serviceObject.insert(configKey::serviceDescription, descriptionObject); - services.replace(serviceIndex, serviceObject); - } - data.insert(configKey::services, services); - } -#endif -} - -ApiConfigsController::ApiConfigsController(const QSharedPointer &serversModel, - const QSharedPointer &apiServicesModel, - const QSharedPointer &subscriptionPlansModel, - const QSharedPointer &benefitsModel, - const std::shared_ptr &settings, QObject *parent) - : QObject(parent) - , m_serversModel(serversModel) - , m_apiServicesModel(apiServicesModel) - , m_subscriptionPlansModel(subscriptionPlansModel) - , m_benefitsModel(benefitsModel) - , m_settings(settings) -{ - connect(m_apiServicesModel.data(), &ApiServicesModel::serviceSelectionChanged, this, [this]() { - const ApiServicesModel::ApiServicesData serviceData = m_apiServicesModel->selectedServiceData(); - m_subscriptionPlansModel->updateModel(serviceData.subscriptionPlansJson); - m_benefitsModel->updateModel(serviceData.benefits); - }); -} - -bool ApiConfigsController::exportVpnKey(const QString &fileName) -{ - if (fileName.isEmpty()) { - emit errorOccurred(ErrorCode::PermissionsError); - return false; - } - - prepareVpnKeyExport(); - if (m_vpnKey.isEmpty()) { - emit errorOccurred(ErrorCode::ApiConfigEmptyError); - return false; - } - - SystemController::saveFile(fileName, m_vpnKey); - return true; -} - -bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName) -{ - if (fileName.isEmpty()) { - emit errorOccurred(ErrorCode::PermissionsError); - return false; - } - - auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); - auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); - - GatewayRequestData gatewayRequestData { QSysInfo::productType(), - QString(APP_VERSION), - m_settings->getAppLanguage().name().split("_").first(), - m_settings->getInstallationUuid(true), - apiConfigObject.value(configKey::userCountryCode).toString(), - serverCountryCode, - apiConfigObject.value(configKey::serviceType).toString(), - configKey::awg, // apiConfigObject.value(configKey::serviceProtocol).toString(), - serverConfigObject.value(configKey::authData).toObject() }; - - QString protocol = gatewayRequestData.serviceProtocol; - ProtocolData protocolData = generateProtocolData(protocol); - - QJsonObject apiPayload = gatewayRequestData.toJsonObject(); - appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); - bool isTestPurchase = apiConfigObject.value(apiDefs::key::isTestPurchase).toBool(false); - QByteArray responseBody; - ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody, isTestPurchase); - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode); - return false; - } - - QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object(); - QString nativeConfig = jsonConfig.value(configKey::config).toString(); - nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", protocolData.wireGuardClientPrivKey); - - SystemController::saveFile(fileName, nativeConfig); - return true; -} - -bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode) -{ - auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); - auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); - - GatewayRequestData gatewayRequestData { QSysInfo::productType(), - QString(APP_VERSION), - m_settings->getAppLanguage().name().split("_").first(), - m_settings->getInstallationUuid(true), - apiConfigObject.value(configKey::userCountryCode).toString(), - serverCountryCode, - apiConfigObject.value(configKey::serviceType).toString(), - configKey::awg, // apiConfigObject.value(configKey::serviceProtocol).toString(), - serverConfigObject.value(configKey::authData).toObject() }; - - QJsonObject apiPayload = gatewayRequestData.toJsonObject(); - bool isTestPurchase = apiConfigObject.value(apiDefs::key::isTestPurchase).toBool(false); - QByteArray responseBody; - ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody, isTestPurchase); - if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { - emit errorOccurred(errorCode); - return false; - } - return true; -} - -void ApiConfigsController::prepareVpnKeyExport() -{ - auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); - auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); - - auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString(); - if (vpnKey.isEmpty()) { - vpnKey = apiUtils::getPremiumV2VpnKey(serverConfigObject); - apiConfigObject.insert(apiDefs::key::vpnKey, vpnKey); - serverConfigObject.insert(configKey::apiConfig, apiConfigObject); - m_serversModel->editServer(serverConfigObject, m_serversModel->getProcessedServerIndex()); - } - - m_vpnKey = vpnKey; - - vpnKey.replace("vpn://", ""); - - m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(vpnKey.toUtf8()); - - emit vpnKeyExportReady(); -} - -void ApiConfigsController::copyVpnKeyToClipboard() -{ - auto clipboard = amnApp->getClipboard(); - clipboard->setText(m_vpnKey); -} - -bool ApiConfigsController::fillAvailableServices() -{ - QJsonObject apiPayload; - apiPayload[configKey::osVersion] = QSysInfo::productType(); - apiPayload[configKey::appVersion] = QString(APP_VERSION); - apiPayload[apiDefs::key::cliName] = QString(APPLICATION_NAME); - apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first(); - - QByteArray responseBody; - ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody); - if (errorCode == ErrorCode::NoError) { - if (!responseBody.contains("services")) { - errorCode = ErrorCode::ApiServicesMissingError; - } - } - - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode); - return false; - } - - QJsonObject data = QJsonDocument::fromJson(responseBody).object(); - -#if defined(Q_OS_IOS) || defined(MACOS_NE) - mergeStoreKitPricesIntoPremiumPlans(data); -#endif - - m_apiServicesModel->updateModel(data); - if (m_apiServicesModel->rowCount() > 0) { - m_apiServicesModel->setServiceIndex(0); - } - return true; -} - -bool ApiConfigsController::importService() -{ -#if defined(Q_OS_IOS) || defined(MACOS_NE) - const bool isIosOrMacOsNe = true; -#else - const bool isIosOrMacOsNe = false; -#endif - - if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaPremium) { - if (isIosOrMacOsNe) { - return importPremiumFromAppStore(QString()); - } - } else if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaFree) { - return importFreeFromGateway(); - } - return false; -} - -bool ApiConfigsController::importPremiumFromAppStore(const QString &storeProductId) -{ -#if defined(Q_OS_IOS) || defined(MACOS_NE) - QString productId = storeProductId.trimmed(); - if (productId.isEmpty()) { - productId = QStringLiteral("amnezia_premium_6_month"); - } - - bool purchaseOk = false; - QString originalTransactionId; - QString storeTransactionId; - QString purchasedStoreProductId; - QString purchaseError; - QEventLoop waitPurchase; - IosController::Instance()->purchaseProduct(productId, - [&](bool success, const QString &transactionId, const QString &purchasedProductId, - const QString &originalTransactionIdResponse, const QString &errorString) { - purchaseOk = success; - originalTransactionId = originalTransactionIdResponse; - storeTransactionId = transactionId; - purchasedStoreProductId = purchasedProductId; - purchaseError = errorString; - waitPurchase.quit(); - }); - waitPurchase.exec(); - - if (!purchaseOk || originalTransactionId.isEmpty()) { - qDebug() << "IAP purchase failed:" << purchaseError; - emit errorOccurred(ErrorCode::ApiPurchaseError); - return false; - } - qInfo().noquote() << "[IAP] Purchase success. transactionId =" << storeTransactionId - << "originalTransactionId =" << originalTransactionId << "productId =" << purchasedStoreProductId; - - GatewayRequestData gatewayRequestData { QSysInfo::productType(), - QString(APP_VERSION), - m_settings->getAppLanguage().name().split("_").first(), - m_settings->getInstallationUuid(true), - m_apiServicesModel->getCountryCode(), - "", - m_apiServicesModel->getSelectedServiceType(), - m_apiServicesModel->getSelectedServiceProtocol(), - QJsonObject() }; - - QJsonObject apiPayload = gatewayRequestData.toJsonObject(); - apiPayload[apiDefs::key::transactionId] = originalTransactionId; - auto isTestPurchase = IosController::Instance()->isTestFlight(); - - ErrorCode errorCode; - QByteArray responseBody; - errorCode = executeRequest(QString("%1v1/subscriptions"), apiPayload, responseBody, isTestPurchase); - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode); - return false; - } - - int duplicateServerIndex = -1; - errorCode = importServiceFromBilling(responseBody, isTestPurchase, duplicateServerIndex); - if (errorCode == ErrorCode::ApiConfigAlreadyAdded) { - emit installServerFromApiFinished(tr("This subscription has already been added"), duplicateServerIndex); - return true; - } - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode); - return false; - } - emit installServerFromApiFinished( - tr("%1 has been added to the app").arg(m_apiServicesModel->getSelectedServiceName())); - return true; -#else - Q_UNUSED(storeProductId); - return false; -#endif -} - -bool ApiConfigsController::restoreServiceFromAppStore() -{ -#if defined(Q_OS_IOS) || defined(MACOS_NE) - const QString premiumServiceType = QStringLiteral("amnezia-premium"); - - if (!fillAvailableServices()) { - qWarning().noquote() << "[IAP] Unable to fetch services list before restore"; - emit errorOccurred(ErrorCode::ApiServicesMissingError); - return false; - } - - if (m_apiServicesModel->rowCount() <= 0) { - emit errorOccurred(ErrorCode::ApiServicesMissingError); - return false; - } - - const int premiumServiceIndex = m_apiServicesModel->serviceIndexForType(premiumServiceType); - if (premiumServiceIndex < 0) { - emit errorOccurred(ErrorCode::ApiServicesMissingError); - return false; - } - m_apiServicesModel->setServiceIndex(premiumServiceIndex); - - bool restoreSuccess = false; - QList restoredTransactions; - QString restoreError; - QEventLoop waitRestore; - - IosController::Instance()->restorePurchases([&](bool success, const QList &transactions, const QString &errorString) { - restoreSuccess = success; - restoredTransactions = transactions; - restoreError = errorString; - waitRestore.quit(); - }); - waitRestore.exec(); - - if (!restoreSuccess) { - qWarning().noquote() << "[IAP] Restore failed:" << restoreError; - emit errorOccurred(ErrorCode::ApiPurchaseError); - return false; - } - - if (restoredTransactions.isEmpty()) { - qInfo().noquote() << "[IAP] Restore completed, but no active entitlements found"; - emit errorOccurred(ErrorCode::ApiNoPurchasedSubscriptionsError); - return false; - } - - const bool isTestPurchase = IosController::Instance()->isTestFlight(); - const QString serviceType = m_apiServicesModel->getSelectedServiceType(); - const QString serviceProtocol = m_apiServicesModel->getSelectedServiceProtocol(); - const QString countryCode = m_apiServicesModel->getCountryCode(); - const QString appLanguage = m_settings->getAppLanguage().name().split("_").first(); - const QString installationUuid = m_settings->getInstallationUuid(true); - - bool hasInstalledConfig = false; - bool duplicateConfigAlreadyPresent = false; - int duplicateServerIndex = -1; - QSet processedOriginalTransactionIds; - - for (const QVariantMap &transaction : restoredTransactions) { - const QString originalTransactionId = transaction.value(QStringLiteral("originalTransactionId")).toString(); - const QString transactionId = transaction.value(QStringLiteral("transactionId")).toString(); - const QString productId = transaction.value(QStringLiteral("productId")).toString(); - - if (originalTransactionId.isEmpty()) { - qWarning().noquote() << "[IAP] Skipping restored transaction without originalTransactionId" << transactionId; - continue; - } - - if (processedOriginalTransactionIds.contains(originalTransactionId)) { - qInfo().noquote() << "[IAP] Skipping duplicate restored transaction" << originalTransactionId; - continue; - } - processedOriginalTransactionIds.insert(originalTransactionId); - - qInfo().noquote() << "[IAP] Restoring subscription. transactionId =" << transactionId - << "originalTransactionId =" << originalTransactionId << "productId =" << productId; - - GatewayRequestData gatewayRequestData { QSysInfo::productType(), - QString(APP_VERSION), - appLanguage, - installationUuid, - countryCode, - "", - serviceType, - serviceProtocol, - QJsonObject() }; - - QJsonObject apiPayload = gatewayRequestData.toJsonObject(); - apiPayload[apiDefs::key::transactionId] = originalTransactionId; - - QByteArray responseBody; - ErrorCode errorCode = executeRequest(QString("%1v1/subscriptions"), apiPayload, responseBody, isTestPurchase); - if (errorCode != ErrorCode::NoError) { - qWarning().noquote() << "[IAP] Failed to restore transaction" << originalTransactionId - << "errorCode =" << static_cast(errorCode); - continue; - } - - int currentDuplicateServerIndex = -1; - errorCode = importServiceFromBilling(responseBody, isTestPurchase, currentDuplicateServerIndex); - if (errorCode == ErrorCode::ApiConfigAlreadyAdded) { - duplicateConfigAlreadyPresent = true; - if (duplicateServerIndex < 0) { - duplicateServerIndex = currentDuplicateServerIndex; - } - qInfo().noquote() << "[IAP] Subscription config with the same vpn_key already exists" << originalTransactionId; - continue; - } - - if (errorCode != ErrorCode::NoError) { - qWarning().noquote() << "[IAP] Failed to process restored subscription response for transaction" << originalTransactionId - << "errorCode =" << static_cast(errorCode); - continue; - } - - hasInstalledConfig = true; - } - - if (!hasInstalledConfig) { - if (duplicateConfigAlreadyPresent) { - emit installServerFromApiFinished(tr("This subscription has already been added"), duplicateServerIndex); - return true; - } - - emit errorOccurred(ErrorCode::ApiPurchaseError); - return false; - } - - emit installServerFromApiFinished(tr("Subscription restored successfully.")); -#endif - return true; -} - -bool ApiConfigsController::importFreeFromGateway() -{ - GatewayRequestData gatewayRequestData { QSysInfo::productType(), - QString(APP_VERSION), - m_settings->getAppLanguage().name().split("_").first(), - m_settings->getInstallationUuid(true), - m_apiServicesModel->getCountryCode(), - "", - m_apiServicesModel->getSelectedServiceType(), - m_apiServicesModel->getSelectedServiceProtocol(), - QJsonObject() }; - - if (m_serversModel->isServerFromApiAlreadyExists(gatewayRequestData.userCountryCode, gatewayRequestData.serviceType, - gatewayRequestData.serviceProtocol)) { - emit errorOccurred(ErrorCode::ApiConfigAlreadyAdded); - return false; - } - - ProtocolData protocolData = generateProtocolData(gatewayRequestData.serviceProtocol); - - QJsonObject apiPayload = gatewayRequestData.toJsonObject(); - appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); - - ErrorCode errorCode; - QByteArray responseBody; - - errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody); - - QJsonObject serverConfig; - if (errorCode == ErrorCode::NoError) { - errorCode = fillServerConfig(gatewayRequestData.serviceProtocol, protocolData, responseBody, serverConfig); - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode); - return false; - } - - QJsonObject apiConfig = serverConfig.value(configKey::apiConfig).toObject(); - apiConfig.insert(configKey::userCountryCode, m_apiServicesModel->getCountryCode()); - apiConfig.insert(configKey::serviceType, m_apiServicesModel->getSelectedServiceType()); - apiConfig.insert(configKey::serviceProtocol, m_apiServicesModel->getSelectedServiceProtocol()); - - serverConfig.insert(configKey::apiConfig, apiConfig); - - m_serversModel->addServer(serverConfig); - emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName())); - return true; - } else { - emit errorOccurred(errorCode); - return false; - } -} - -bool ApiConfigsController::importTrialFromGateway(const QString &email) -{ - emit trialEmailError(QString()); - - const QString trimmedEmail = email.trimmed(); - if (trimmedEmail.isEmpty()) { - emit errorOccurred(ErrorCode::ApiConfigEmptyError); - return false; - } - - GatewayRequestData gatewayRequestData { QSysInfo::productType(), - QString(APP_VERSION), - m_settings->getAppLanguage().name().split("_").first(), - m_settings->getInstallationUuid(true), - m_apiServicesModel->getCountryCode(), - "", - m_apiServicesModel->getSelectedServiceType(), - m_apiServicesModel->getSelectedServiceProtocol(), - QJsonObject() }; - - ProtocolData protocolData = generateProtocolData(gatewayRequestData.serviceProtocol); - - QJsonObject apiPayload = gatewayRequestData.toJsonObject(); - appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); - apiPayload.insert(apiDefs::key::email, trimmedEmail); - - QByteArray responseBody; - ErrorCode errorCode = executeRequest(QString("%1v1/trial"), apiPayload, responseBody); - if (errorCode != ErrorCode::NoError) { - if (errorCode == ErrorCode::ApiTrialAlreadyUsedError) { - emit trialEmailError(tr("This email address has already been used to activate a trial. If you like the service, you can upgrade to Premium")); - return false; - } - emit errorOccurred(errorCode); - return false; - } - - QJsonObject responseObject = QJsonDocument::fromJson(responseBody).object(); - QString key = responseObject.value(apiDefs::key::config).toString(); - if (key.isEmpty()) { - qWarning().noquote() << "[Trial] trial response does not contain config field"; - emit errorOccurred(ErrorCode::ApiConfigEmptyError); - return false; - } - - key.replace(QStringLiteral("vpn://"), QString()); - QByteArray configBytes = QByteArray::fromBase64(key.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QByteArray uncompressed = qUncompress(configBytes); - if (!uncompressed.isEmpty()) { - configBytes = uncompressed; - } - if (configBytes.isEmpty()) { - qWarning().noquote() << "[Trial] trial response config payload is empty"; - emit errorOccurred(ErrorCode::ApiConfigEmptyError); - return false; - } - - QJsonObject configObject = QJsonDocument::fromJson(configBytes).object(); - quint16 crc = qChecksum(QJsonDocument(configObject).toJson()); - configObject.insert(config_key::crc, crc); - m_serversModel->addServer(configObject); - - emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName())); - return true; -} - -bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, - bool reloadServiceConfig) -{ - auto serverConfig = m_serversModel->getServerConfig(serverIndex); - auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); - - GatewayRequestData gatewayRequestData { QSysInfo::productType(), - QString(APP_VERSION), - m_settings->getAppLanguage().name().split("_").first(), - m_settings->getInstallationUuid(true), - apiConfig.value(configKey::userCountryCode).toString(), - newCountryCode, - apiConfig.value(configKey::serviceType).toString(), - apiConfig.value(configKey::serviceProtocol).toString(), - serverConfig.value(configKey::authData).toObject() }; - - ProtocolData protocolData = generateProtocolData(gatewayRequestData.serviceProtocol); - - QJsonObject apiPayload = gatewayRequestData.toJsonObject(); - appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); - - if (newCountryCode.isEmpty() && newCountryName.isEmpty() && !reloadServiceConfig) { - apiPayload.insert(configKey::isConnectEvent, true); - } - - bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false); - bool wasSubscriptionExpired = m_serversModel->data(serverIndex, ServersModel::IsSubscriptionExpiredRole).toBool(); - QByteArray responseBody; - ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase); - - QJsonObject newServerConfig; - if (errorCode == ErrorCode::NoError) { - errorCode = fillServerConfig(gatewayRequestData.serviceProtocol, protocolData, responseBody, newServerConfig); - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode); - return false; - } - - QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject(); - newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode)); - newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType)); - newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol)); - newApiConfig.insert(apiDefs::key::vpnKey, apiConfig.value(apiDefs::key::vpnKey)); - if (apiConfig.contains(apiDefs::key::isInAppPurchase)) { - newApiConfig.insert(apiDefs::key::isInAppPurchase, apiConfig.value(apiDefs::key::isInAppPurchase)); - } - if (apiConfig.contains(apiDefs::key::isTestPurchase)) { - newApiConfig.insert(apiDefs::key::isTestPurchase, apiConfig.value(apiDefs::key::isTestPurchase)); - } - - newServerConfig.insert(configKey::apiConfig, newApiConfig); - newServerConfig.insert(configKey::authData, gatewayRequestData.authData); - newServerConfig.insert(config_key::crc, serverConfig.value(config_key::crc)); - - if (serverConfig.value(config_key::nameOverriddenByUser).toBool()) { - newServerConfig.insert(config_key::name, serverConfig.value(config_key::name)); - newServerConfig.insert(config_key::nameOverriddenByUser, true); - } - m_serversModel->editServer(newServerConfig, serverIndex); - - if (wasSubscriptionExpired) { - emit subscriptionRefreshNeeded(); - } - - if (reloadServiceConfig) { - emit reloadServerFromApiFinished(tr("API config reloaded")); - } else if (newCountryName.isEmpty()) { - emit updateServerFromApiFinished(); - } else { - emit changeApiCountryFinished(tr("Successfully changed the country of connection to %1").arg(newCountryName)); - } - return true; - } else { - if (errorCode == ErrorCode::ApiSubscriptionExpiredError) { - if (!apiConfig.value(apiDefs::key::isInAppPurchase).toBool(false)) { - apiConfig.insert(apiDefs::key::subscriptionExpiredByServer, true); - serverConfig.insert(configKey::apiConfig, apiConfig); - m_serversModel->editServer(serverConfig, serverIndex); - emit subscriptionExpiredOnServer(); - } else { - emit errorOccurred(errorCode); - } - } else { - emit errorOccurred(errorCode); - } - return false; - } -} - -bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) -{ -#ifdef Q_OS_IOS - IosController::Instance()->requestInetAccess(); - QThread::msleep(10); -#endif - - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - - auto serverConfig = m_serversModel->getServerConfig(serverIndex); - auto installationUuid = m_settings->getInstallationUuid(true); - - QString serviceProtocol = serverConfig.value(configKey::protocol).toString(); - ProtocolData protocolData = generateProtocolData(serviceProtocol); - - QJsonObject apiPayload; - appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload); - apiPayload[configKey::uuid] = installationUuid; - apiPayload[configKey::osVersion] = QSysInfo::productType(); - apiPayload[configKey::appVersion] = QString(APP_VERSION); - apiPayload[configKey::accessToken] = serverConfig.value(configKey::accessToken).toString(); - apiPayload[configKey::apiEndpoint] = serverConfig.value(configKey::apiEndpoint).toString(); - - QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/proxy_config"), apiPayload, responseBody); - - if (errorCode == ErrorCode::NoError) { - errorCode = fillServerConfig(serviceProtocol, protocolData, responseBody, serverConfig); - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode); - return false; - } - - m_serversModel->editServer(serverConfig, serverIndex); - emit updateServerFromApiFinished(); - return true; - } else { - emit errorOccurred(errorCode); - return false; - } -} - -bool ApiConfigsController::deactivateDevice(const bool isRemoveEvent) -{ - auto serverIndex = m_serversModel->getProcessedServerIndex(); - auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); - auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); - - if (!apiUtils::isPremiumServer(serverConfigObject)) { - return true; - } - - GatewayRequestData gatewayRequestData { QSysInfo::productType(), - QString(APP_VERSION), - m_settings->getAppLanguage().name().split("_").first(), - m_settings->getInstallationUuid(true), - apiConfigObject.value(configKey::userCountryCode).toString(), - apiConfigObject.value(configKey::serverCountryCode).toString(), - apiConfigObject.value(configKey::serviceType).toString(), - "", - serverConfigObject.value(configKey::authData).toObject() }; - - QJsonObject apiPayload = gatewayRequestData.toJsonObject(); - bool isTestPurchase = apiConfigObject.value(apiDefs::key::isTestPurchase).toBool(false); - QByteArray responseBody; - ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody, isTestPurchase); - - if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { - emit errorOccurred(errorCode); - return false; - } - - serverConfigObject.remove(config_key::containers); - m_serversModel->editServer(serverConfigObject, serverIndex); - - return true; -} - -bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode) -{ - auto serverIndex = m_serversModel->getProcessedServerIndex(); - auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); - auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); - - if (!apiUtils::isPremiumServer(serverConfigObject)) { - return true; - } - - GatewayRequestData gatewayRequestData { QSysInfo::productType(), - QString(APP_VERSION), - m_settings->getAppLanguage().name().split("_").first(), - uuid, - apiConfigObject.value(configKey::userCountryCode).toString(), - serverCountryCode, - apiConfigObject.value(configKey::serviceType).toString(), - "", - serverConfigObject.value(configKey::authData).toObject() }; - - QJsonObject apiPayload = gatewayRequestData.toJsonObject(); - bool isTestPurchase = apiConfigObject.value(apiDefs::key::isTestPurchase).toBool(false); - QByteArray responseBody; - ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody, isTestPurchase); - if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { - emit errorOccurred(errorCode); - return false; - } - - if (uuid == m_settings->getInstallationUuid(true)) { - serverConfigObject.remove(config_key::containers); - m_serversModel->editServer(serverConfigObject, serverIndex); - } - - return true; -} - -bool ApiConfigsController::isConfigValid() -{ - int serverIndex = m_serversModel->getDefaultServerIndex(); - QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex); - auto configSource = apiUtils::getConfigSource(serverConfigObject); - - if (configSource == apiDefs::ConfigSource::Telegram - && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { - m_serversModel->removeApiConfig(serverIndex); - return updateServiceFromTelegram(serverIndex); - } else if (configSource == apiDefs::ConfigSource::AmneziaGateway - && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { - return updateServiceFromGateway(serverIndex, "", ""); - } else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) { - qDebug() << "attempt to update api config by expires_at event"; - if (configSource == apiDefs::ConfigSource::AmneziaGateway) { - return updateServiceFromGateway(serverIndex, "", ""); - } else { - m_serversModel->removeApiConfig(serverIndex); - return updateServiceFromTelegram(serverIndex); - } - } - return true; -} - -void ApiConfigsController::setCurrentProtocol(const QString &protocolName) -{ - auto serverIndex = m_serversModel->getProcessedServerIndex(); - auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); - auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); - - apiConfigObject[configKey::serviceProtocol] = protocolName; - - serverConfigObject.insert(configKey::apiConfig, apiConfigObject); - - m_serversModel->editServer(serverConfigObject, serverIndex); -} - -bool ApiConfigsController::isVlessProtocol() -{ - auto serverIndex = m_serversModel->getProcessedServerIndex(); - auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); - auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); - - if (apiConfigObject[configKey::serviceProtocol].toString() == "vless") { - return true; - } - return false; -} - -QList ApiConfigsController::getQrCodes() -{ - return m_qrCodes; -} - -int ApiConfigsController::getQrCodesCount() -{ - return static_cast(m_qrCodes.size()); -} - -QString ApiConfigsController::getVpnKey() -{ - return m_vpnKey; -} - -ErrorCode ApiConfigsController::importServiceFromBilling(const QByteArray &responseBody, const bool isTestPurchase, - int &duplicateServerIndex) -{ -#if defined(Q_OS_IOS) || defined(MACOS_NE) - duplicateServerIndex = -1; - QJsonObject responseObject = QJsonDocument::fromJson(responseBody).object(); - const QString rawVpnKey = responseObject.value(QStringLiteral("key")).toString(); - if (rawVpnKey.isEmpty()) { - qWarning().noquote() << "[IAP] Subscription response does not contain a key field"; - return ErrorCode::ApiPurchaseError; - } - - QString normalizedVpnKey = rawVpnKey; - normalizedVpnKey.replace(QStringLiteral("vpn://"), QString()); - - duplicateServerIndex = m_serversModel->indexOfServerWithVpnKey(normalizedVpnKey); - if (duplicateServerIndex >= 0) { - qInfo().noquote() << "[IAP] Subscription config with the same vpn_key already exists"; - return ErrorCode::ApiConfigAlreadyAdded; - } - - QByteArray configPayload = - QByteArray::fromBase64(normalizedVpnKey.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QByteArray configUncompressed = qUncompress(configPayload); - const bool payloadWasCompressed = !configUncompressed.isEmpty(); - if (payloadWasCompressed) { - configPayload = configUncompressed; - } - - if (configPayload.isEmpty()) { - qWarning().noquote() << "[IAP] Subscription response config payload is empty"; - return ErrorCode::ApiPurchaseError; - } - - QJsonObject configObject = QJsonDocument::fromJson(configPayload).object(); - - auto apiConfig = configObject.value(apiDefs::key::apiConfig).toObject(); - apiConfig.insert(apiDefs::key::isTestPurchase, isTestPurchase); - apiConfig.insert(apiDefs::key::isInAppPurchase, true); - configObject.insert(apiDefs::key::apiConfig, apiConfig); - - configPayload = QJsonDocument(configObject).toJson(); - if (payloadWasCompressed) { - configPayload = qCompress(configPayload, 8); - } - normalizedVpnKey = QString(configPayload.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); - - duplicateServerIndex = m_serversModel->indexOfServerWithVpnKey(normalizedVpnKey); - if (duplicateServerIndex >= 0) { - qInfo().noquote() << "[IAP] Subscription config with the same vpn_key already exists"; - return ErrorCode::ApiConfigAlreadyAdded; - } - - apiConfig.insert(apiDefs::key::vpnKey, normalizedVpnKey); - configObject.insert(apiDefs::key::apiConfig, apiConfig); - - quint16 crc = qChecksum(QJsonDocument(configObject).toJson()); - configObject.insert(config_key::crc, crc); - m_serversModel->addServer(configObject); - - return ErrorCode::NoError; -#else - Q_UNUSED(responseBody) - Q_UNUSED(isTestPurchase) - duplicateServerIndex = -1; - return ErrorCode::NoError; -#endif -} - -ErrorCode ApiConfigsController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, - bool isTestPurchase) -{ - GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase), - apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled()); - return gatewayController.post(endpoint, apiPayload, responseBody); -} diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h deleted file mode 100644 index 68f6565d0..000000000 --- a/client/ui/controllers/api/apiConfigsController.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef APICONFIGSCONTROLLER_H -#define APICONFIGSCONTROLLER_H - -#include -#include - -#include "configurators/openvpn_configurator.h" -#include "ui/models/api/apiBenefitsModel.h" -#include "ui/models/api/apiServicesModel.h" -#include "ui/models/api/apiSubscriptionPlansModel.h" -#include "ui/models/servers_model.h" - -class ApiConfigsController : public QObject -{ - Q_OBJECT -public: - ApiConfigsController(const QSharedPointer &serversModel, const QSharedPointer &apiServicesModel, - const QSharedPointer &subscriptionPlansModel, - const QSharedPointer &benefitsModel, const std::shared_ptr &settings, - QObject *parent = nullptr); - - Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY vpnKeyExportReady) - Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY vpnKeyExportReady) - Q_PROPERTY(QString vpnKey READ getVpnKey NOTIFY vpnKeyExportReady) - -public slots: - bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName); - bool revokeNativeConfig(const QString &serverCountryCode); - bool exportVpnKey(const QString &fileName); - void prepareVpnKeyExport(); - void copyVpnKeyToClipboard(); - - bool fillAvailableServices(); - bool importService(); - bool importPremiumFromAppStore(const QString &storeProductId); - bool restoreServiceFromAppStore(); - bool importFreeFromGateway(); - bool importTrialFromGateway(const QString &email); - bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, - bool reloadServiceConfig = false); - bool updateServiceFromTelegram(const int serverIndex); - bool deactivateDevice(const bool isRemoveEvent); - bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode); - - bool isConfigValid(); - - void setCurrentProtocol(const QString &protocolName); - bool isVlessProtocol(); - -signals: - void errorOccurred(ErrorCode errorCode); - void trialEmailError(const QString &message); - void subscriptionExpiredOnServer(); - void subscriptionRefreshNeeded(); - - void installServerFromApiFinished(const QString &message, int preferredDefaultServerIndex = -1); - void changeApiCountryFinished(const QString &message); - void reloadServerFromApiFinished(const QString &message); - void updateServerFromApiFinished(); - - void vpnKeyExportReady(); - -private: - QList getQrCodes(); - int getQrCodesCount(); - QString getVpnKey(); - - ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false); - ErrorCode importServiceFromBilling(const QByteArray &responseBody, const bool isTestPurchase, int &duplicateServerIndex); - - QList m_qrCodes; - QString m_vpnKey; - - QSharedPointer m_serversModel; - QSharedPointer m_apiServicesModel; - std::shared_ptr m_settings; - - QSharedPointer m_subscriptionPlansModel; - QSharedPointer m_benefitsModel; -}; - -#endif diff --git a/client/ui/controllers/api/apiNewsController.cpp b/client/ui/controllers/api/apiNewsController.cpp deleted file mode 100644 index 9e294f11f..000000000 --- a/client/ui/controllers/api/apiNewsController.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "apiNewsController.h" - -#include "core/api/apiUtils.h" -#include -#include - -namespace -{ - namespace configKey - { - constexpr char userCountryCode[] = "user_country_code"; - constexpr char serviceType[] = "service_type"; - } -} - -ApiNewsController::ApiNewsController(const QSharedPointer &newsModel, const std::shared_ptr &settings, - const QSharedPointer &serversModel, QObject *parent) - : QObject(parent), m_newsModel(newsModel), m_settings(settings), m_serversModel(serversModel) -{ -} - -void ApiNewsController::fetchNews(bool showError) -{ - if (m_serversModel.isNull()) { - qWarning() << "ServersModel is null, skip fetchNews"; - return; - } - const auto stacks = m_serversModel->gatewayStacks(); - if (stacks.isEmpty()) { - qDebug() << "No Gateway stacks, skip fetchNews"; - return; - } - - auto gatewayController = QSharedPointer::create(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), - apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled()); - QJsonObject payload; - payload.insert("locale", m_settings->getAppLanguage().name().split("_").first()); - - const QJsonObject stacksJson = stacks.toJson(); - if (stacksJson.contains(configKey::userCountryCode)) { - payload.insert(configKey::userCountryCode, stacksJson.value(configKey::userCountryCode)); - } - if (stacksJson.contains(configKey::serviceType)) { - payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType)); - } - - auto future = gatewayController->postAsync(QString("%1v1/news"), payload); - future.then(this, [this, showError, gatewayController](QPair result) { - auto [errorCode, responseBody] = result; - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode, showError); - return; - } - - QJsonDocument doc = QJsonDocument::fromJson(responseBody); - QJsonArray newsArray; - if (doc.isArray()) { - newsArray = doc.array(); - } else if (doc.isObject()) { - QJsonObject obj = doc.object(); - if (obj.value("news").isArray()) { - newsArray = obj.value("news").toArray(); - } - } - - m_newsModel->updateModel(newsArray); - emit fetchNewsFinished(); - }); -} diff --git a/client/ui/controllers/api/apiNewsController.h b/client/ui/controllers/api/apiNewsController.h deleted file mode 100644 index 43008af7c..000000000 --- a/client/ui/controllers/api/apiNewsController.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef APINEWSCONTROLLER_H -#define APINEWSCONTROLLER_H - -#include -#include -#include -#include - -#include "core/api/apiDefs.h" -#include "core/controllers/gatewayController.h" -#include "settings.h" -#include "ui/models/newsModel.h" -#include "ui/models/servers_model.h" - -class ApiNewsController : public QObject -{ - Q_OBJECT -public: - explicit ApiNewsController(const QSharedPointer &newsModel, const std::shared_ptr &settings, - const QSharedPointer &serversModel, QObject *parent = nullptr); - - Q_INVOKABLE void fetchNews(bool showError); - -signals: - void errorOccurred(ErrorCode errorCode, bool showError); - void fetchNewsFinished(); - -private: - QSharedPointer m_newsModel; - std::shared_ptr m_settings; - QSharedPointer m_serversModel; -}; - -#endif // APINEWSCONTROLLER_H diff --git a/client/ui/controllers/api/apiNewsUiController.cpp b/client/ui/controllers/api/apiNewsUiController.cpp new file mode 100644 index 000000000..899032e33 --- /dev/null +++ b/client/ui/controllers/api/apiNewsUiController.cpp @@ -0,0 +1,28 @@ +#include "apiNewsUiController.h" + +ApiNewsUiController::ApiNewsUiController(NewsModel* newsModel, + NewsController* newsController, + QObject *parent) + : QObject(parent), m_newsModel(newsModel), m_newsController(newsController) +{ +} + +void ApiNewsUiController::fetchNews(bool showError) +{ + if (!m_newsController) { + qWarning() << "NewsController is null, skip fetchNews"; + return; + } + + auto future = m_newsController->fetchNews(); + future.then(this, [this, showError](QPair result) { + auto [errorCode, newsArray] = result; + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode, showError); + return; + } + + m_newsModel->updateModel(newsArray); + emit fetchNewsFinished(); + }); +} diff --git a/client/ui/controllers/api/apiNewsUiController.h b/client/ui/controllers/api/apiNewsUiController.h new file mode 100644 index 000000000..55e140c75 --- /dev/null +++ b/client/ui/controllers/api/apiNewsUiController.h @@ -0,0 +1,32 @@ +#ifndef APINEWSUICONTROLLER_H +#define APINEWSUICONTROLLER_H + +#include +#include + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/controllers/api/newsController.h" +#include "ui/models/newsModel.h" + +class ApiNewsUiController : public QObject +{ + Q_OBJECT +public: + explicit ApiNewsUiController(NewsModel* newsModel, + NewsController* newsController, + QObject *parent = nullptr); + + Q_INVOKABLE void fetchNews(bool showError); + +signals: + void errorOccurred(ErrorCode errorCode, bool showError); + void fetchNewsFinished(); + +private: + NewsModel* m_newsModel; + NewsController* m_newsController; +}; + +#endif // APINEWSUICONTROLLER_H diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp deleted file mode 100644 index 9ba2262fc..000000000 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include "apiSettingsController.h" - -#include -#include -#include - -#include "core/api/apiUtils.h" -#include "core/controllers/gatewayController.h" -#include "platforms/ios/ios_controller.h" -#include "version.h" - -namespace -{ - namespace configKey - { - constexpr char userCountryCode[] = "user_country_code"; - constexpr char serverCountryCode[] = "server_country_code"; - constexpr char serviceType[] = "service_type"; - constexpr char serviceInfo[] = "service_info"; - - constexpr char apiConfig[] = "api_config"; - constexpr char authData[] = "auth_data"; - } - - const int requestTimeoutMsecs = 12 * 1000; // 12 secs - - QString getSubscriptionStatusForRenewal(const QSharedPointer &accountInfoModel) - { - if (!accountInfoModel.isNull() && accountInfoModel->data(QStringLiteral("isSubscriptionExpired")).toBool()) { - return QStringLiteral("expired"); - } - - if (!accountInfoModel.isNull() && accountInfoModel->data(QStringLiteral("isSubscriptionExpiringSoon")).toBool()) { - return QStringLiteral("expire_soon"); - } - - return QStringLiteral("active"); - } -} - -ApiSettingsController::ApiSettingsController(const QSharedPointer &serversModel, - const QSharedPointer &apiAccountInfoModel, - const QSharedPointer &apiCountryModel, - const QSharedPointer &apiDevicesModel, - const std::shared_ptr &settings, QObject *parent) - : QObject(parent), - m_serversModel(serversModel), - m_apiAccountInfoModel(apiAccountInfoModel), - m_apiCountryModel(apiCountryModel), - m_apiDevicesModel(apiDevicesModel), - m_settings(settings) -{ -} - -ApiSettingsController::~ApiSettingsController() -{ -} - -bool ApiSettingsController::getAccountInfo(bool reload) -{ - if (reload) { - QEventLoop wait; - QTimer::singleShot(1000, &wait, &QEventLoop::quit); - wait.exec(QEventLoop::ExcludeUserInputEvents); - } - - auto processedIndex = m_serversModel->getProcessedServerIndex(); - auto serverConfig = m_serversModel->getServerConfig(processedIndex); - auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); - auto authData = serverConfig.value(configKey::authData).toObject(); - - bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false); - GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase), - requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled()); - - QJsonObject apiPayload; - apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString(); - apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString(); - apiPayload[configKey::authData] = authData; - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); - apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first(); - - QByteArray responseBody; - - ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody); - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode); - return false; - } - - QJsonObject accountInfo = QJsonDocument::fromJson(responseBody).object(); - m_apiAccountInfoModel->updateModel(accountInfo, serverConfig); - - if (reload) { - updateApiCountryModel(); - updateApiDevicesModel(); - } - - return true; -} - -void ApiSettingsController::getRenewalLink() -{ - auto processedIndex = m_serversModel->getProcessedServerIndex(); - auto serverConfig = m_serversModel->getServerConfig(processedIndex); - auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); - auto authData = serverConfig.value(configKey::authData).toObject(); - - bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false); - auto gatewayController = QSharedPointer::create(m_settings->getGatewayEndpoint(isTestPurchase), - m_settings->isDevGatewayEnv(isTestPurchase), - requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - - QJsonObject apiPayload; - apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString(); - apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString(); - apiPayload[configKey::authData] = authData; - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); - apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first(); - apiPayload[apiDefs::key::subscriptionStatus] = getSubscriptionStatusForRenewal(m_apiAccountInfoModel); - - auto future = gatewayController->postAsync(QString("%1v1/renewal_link"), apiPayload); - future.then(this, [this, gatewayController](QPair result) { - auto [errorCode, responseBody] = result; - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode); - return; - } - - QJsonObject responseJson = QJsonDocument::fromJson(responseBody).object(); - QString url = responseJson.value("renewal_url").toString(); - if (!url.isEmpty()) { - emit renewalLinkReceived(url); - } - }); -} - -void ApiSettingsController::updateApiCountryModel() -{ - m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), ""); - m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo()); -} - -void ApiSettingsController::updateApiDevicesModel() -{ - m_apiDevicesModel->updateModel(m_apiAccountInfoModel->getIssuedConfigsInfo()); -} diff --git a/client/ui/controllers/api/apiSettingsController.h b/client/ui/controllers/api/apiSettingsController.h deleted file mode 100644 index 5853fbd87..000000000 --- a/client/ui/controllers/api/apiSettingsController.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef APISETTINGSCONTROLLER_H -#define APISETTINGSCONTROLLER_H - -#include - -#include "ui/models/api/apiAccountInfoModel.h" -#include "ui/models/api/apiCountryModel.h" -#include "ui/models/api/apiDevicesModel.h" -#include "ui/models/servers_model.h" - -class ApiSettingsController : public QObject -{ - Q_OBJECT -public: - ApiSettingsController(const QSharedPointer &serversModel, const QSharedPointer &apiAccountInfoModel, - const QSharedPointer &apiCountryModel, const QSharedPointer &apiDevicesModel, - const std::shared_ptr &settings, QObject *parent = nullptr); - ~ApiSettingsController(); - -public slots: - bool getAccountInfo(bool reload); - void updateApiCountryModel(); - void updateApiDevicesModel(); - void getRenewalLink(); - -signals: - void errorOccurred(ErrorCode errorCode); - void renewalLinkReceived(const QString &url); - -private: - QSharedPointer m_serversModel; - QSharedPointer m_apiAccountInfoModel; - QSharedPointer m_apiCountryModel; - QSharedPointer m_apiDevicesModel; - - std::shared_ptr m_settings; -}; - -#endif // APISETTINGSCONTROLLER_H diff --git a/client/ui/controllers/api/servicesCatalogUiController.cpp b/client/ui/controllers/api/servicesCatalogUiController.cpp new file mode 100644 index 000000000..734bdd9ac --- /dev/null +++ b/client/ui/controllers/api/servicesCatalogUiController.cpp @@ -0,0 +1,68 @@ +#include "servicesCatalogUiController.h" + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" + +ServicesCatalogUiController::ServicesCatalogUiController(ServicesCatalogController* servicesCatalogController, + ApiServicesModel* apiServicesModel, + QObject *parent) + : QObject(parent), + m_servicesCatalogController(servicesCatalogController), + m_apiServicesModel(apiServicesModel) +{ +} + +bool ServicesCatalogUiController::fillAvailableServices() +{ + QJsonObject servicesData; + ErrorCode errorCode = m_servicesCatalogController->fillAvailableServices(servicesData); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } + + m_apiServicesModel->updateModel(servicesData); + return true; +} + +QJsonObject ServicesCatalogUiController::getSelectedServiceInfo() +{ + return m_apiServicesModel->getSelectedServiceInfo(); +} + +QString ServicesCatalogUiController::getSelectedServiceType() +{ + return m_apiServicesModel->getSelectedServiceType(); +} + +QString ServicesCatalogUiController::getSelectedServiceProtocol() +{ + return m_apiServicesModel->getSelectedServiceProtocol(); +} + +QString ServicesCatalogUiController::getSelectedServiceName() +{ + return m_apiServicesModel->getSelectedServiceName(); +} + +QJsonArray ServicesCatalogUiController::getSelectedServiceCountries() +{ + return m_apiServicesModel->getSelectedServiceCountries(); +} + +QString ServicesCatalogUiController::getCountryCode() +{ + return m_apiServicesModel->getCountryCode(); +} + +QString ServicesCatalogUiController::getStoreEndpoint() +{ + return m_apiServicesModel->getStoreEndpoint(); +} + +QVariant ServicesCatalogUiController::getSelectedServiceData(const QString &roleString) +{ + return m_apiServicesModel->getSelectedServiceData(roleString); +} + diff --git a/client/ui/controllers/api/servicesCatalogUiController.h b/client/ui/controllers/api/servicesCatalogUiController.h new file mode 100644 index 000000000..fa25387f0 --- /dev/null +++ b/client/ui/controllers/api/servicesCatalogUiController.h @@ -0,0 +1,44 @@ +#ifndef SERVICESCATALOGUICONTROLLER_H +#define SERVICESCATALOGUICONTROLLER_H + +#include +#include +#include + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/controllers/api/servicesCatalogController.h" +#include "ui/models/api/apiServicesModel.h" + +class ServicesCatalogUiController : public QObject +{ + Q_OBJECT + +public: + explicit ServicesCatalogUiController(ServicesCatalogController* servicesCatalogController, + ApiServicesModel* apiServicesModel, + QObject *parent = nullptr); + +public slots: + bool fillAvailableServices(); + + QJsonObject getSelectedServiceInfo(); + QString getSelectedServiceType(); + QString getSelectedServiceProtocol(); + QString getSelectedServiceName(); + QJsonArray getSelectedServiceCountries(); + QString getCountryCode(); + QString getStoreEndpoint(); + QVariant getSelectedServiceData(const QString &roleString); + +signals: + void errorOccurred(ErrorCode errorCode); + +private: + ServicesCatalogController* m_servicesCatalogController; + ApiServicesModel* m_apiServicesModel; +}; + +#endif // SERVICESCATALOGUICONTROLLER_H + diff --git a/client/ui/controllers/api/subscriptionUiController.cpp b/client/ui/controllers/api/subscriptionUiController.cpp new file mode 100644 index 000000000..370599a24 --- /dev/null +++ b/client/ui/controllers/api/subscriptionUiController.cpp @@ -0,0 +1,483 @@ +#include "subscriptionUiController.h" + +#include "amneziaApplication.h" +#include "core/configurators/wireguardConfigurator.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" +#include "core/utils/api/apiUtils.h" +#include "core/utils/qrCodeUtils.h" +#include "ui/controllers/systemController.h" +#include "version.h" +#include "core/models/serverConfig.h" +#include +#include +#include +#include +#include +#include + +namespace +{ + namespace configKey + { + constexpr char awg[] = "awg"; + constexpr char vless[] = "vless"; + + constexpr char apiEndpoint[] = "api_endpoint"; + constexpr char accessToken[] = "api_key"; + constexpr char certificate[] = "certificate"; + constexpr char publicKey[] = "public_key"; + constexpr char protocol[] = "protocol"; + + constexpr char uuid[] = "installation_uuid"; + constexpr char osVersion[] = "os_version"; + constexpr char appVersion[] = "app_version"; + + constexpr char userCountryCode[] = "user_country_code"; + constexpr char serverCountryCode[] = "server_country_code"; + constexpr char serviceType[] = "service_type"; + constexpr char serviceInfo[] = "service_info"; + constexpr char serviceProtocol[] = "service_protocol"; + + constexpr char apiPayload[] = "api_payload"; + constexpr char keyPayload[] = "key_payload"; + + constexpr char apiConfig[] = "api_config"; + constexpr char authData[] = "auth_data"; + + constexpr char config[] = "config"; + + constexpr char subscription[] = "subscription"; + constexpr char endDate[] = "end_date"; + + constexpr char isConnectEvent[] = "is_connect_event"; + } + +} + +SubscriptionUiController::SubscriptionUiController(ServersController* serversController, + ApiServicesModel* apiServicesModel, + ServicesCatalogController* servicesCatalogController, + SubscriptionController* subscriptionController, + ApiSubscriptionPlansModel* apiSubscriptionPlansModel, + ApiBenefitsModel* apiBenefitsModel, + ApiAccountInfoModel* apiAccountInfoModel, + ApiCountryModel* apiCountryModel, + ApiDevicesModel* apiDevicesModel, + SettingsController* settingsController, + QObject *parent) + : QObject(parent), m_serversController(serversController), m_apiServicesModel(apiServicesModel), m_servicesCatalogController(servicesCatalogController), m_subscriptionController(subscriptionController), m_apiSubscriptionPlansModel(apiSubscriptionPlansModel), m_apiBenefitsModel(apiBenefitsModel), m_apiAccountInfoModel(apiAccountInfoModel), m_apiCountryModel(apiCountryModel), m_apiDevicesModel(apiDevicesModel), m_settingsController(settingsController) +{ + connect(m_apiServicesModel, &ApiServicesModel::serviceSelectionChanged, this, [this]() { + ApiServicesModel::ApiServicesData selectedServiceData = m_apiServicesModel->selectedServiceData(); + m_apiSubscriptionPlansModel->updateModel(selectedServiceData.subscriptionPlansJson); + m_apiBenefitsModel->updateModel(selectedServiceData.benefits); + }); +} + +bool SubscriptionUiController::exportVpnKey(int serverIndex, const QString &fileName) +{ + if (fileName.isEmpty()) { + emit errorOccurred(ErrorCode::PermissionsError); + return false; + } + + prepareVpnKeyExport(serverIndex); + if (m_vpnKey.isEmpty()) { + emit errorOccurred(ErrorCode::ApiConfigEmptyError); + return false; + } + + SystemController::saveFile(fileName, m_vpnKey); + return true; +} + +bool SubscriptionUiController::exportNativeConfig(int serverIndex, const QString &serverCountryCode, const QString &fileName) +{ + if (fileName.isEmpty()) { + emit errorOccurred(ErrorCode::PermissionsError); + return false; + } + + QString nativeConfig; + ErrorCode errorCode = m_subscriptionController->exportNativeConfig(serverIndex, serverCountryCode, nativeConfig); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } + + SystemController::saveFile(fileName, nativeConfig); + return true; +} + +bool SubscriptionUiController::revokeNativeConfig(int serverIndex, const QString &serverCountryCode) +{ + ErrorCode errorCode = m_subscriptionController->revokeNativeConfig(serverIndex, serverCountryCode); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } + return true; +} + +void SubscriptionUiController::prepareVpnKeyExport(int serverIndex) +{ + QString vpnKey; + ErrorCode errorCode = m_subscriptionController->prepareVpnKeyExport(serverIndex, vpnKey); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return; + } + + m_vpnKey = vpnKey; + + QString vpnKeyForQr = vpnKey; + vpnKeyForQr.replace("vpn://", ""); + + m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(vpnKeyForQr.toUtf8()); + + emit vpnKeyExportReady(); +} + +void SubscriptionUiController::copyVpnKeyToClipboard() +{ + auto clipboard = amnApp->getClipboard(); + clipboard->setText(m_vpnKey); +} + +bool SubscriptionUiController::fillAvailableServices() +{ + QJsonObject servicesData; + ErrorCode errorCode = m_servicesCatalogController->fillAvailableServices(servicesData); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } + + m_apiServicesModel->updateModel(servicesData); + if (m_apiServicesModel->rowCount() > 0) { + m_apiServicesModel->setServiceIndex(0); + } + return true; +} + +bool SubscriptionUiController::importPremiumFromAppStore(const QString &storeProductId) +{ +#if defined(Q_OS_IOS) || defined(MACOS_NE) + QString productId = storeProductId.trimmed(); + if (productId.isEmpty()) { + productId = QStringLiteral("amnezia_premium_6_month"); + } + + ServerConfig serverConfig; + int duplicateServerIndex = -1; + ErrorCode errorCode = m_subscriptionController->processAppStorePurchase( + m_apiServicesModel->getCountryCode(), + m_apiServicesModel->getSelectedServiceType(), + m_apiServicesModel->getSelectedServiceProtocol(), + productId, + serverConfig, + &duplicateServerIndex); + + if (errorCode != ErrorCode::NoError) { + if (errorCode == ErrorCode::ApiConfigAlreadyAdded) { + emit installServerFromApiFinished(tr("This subscription has already been added"), duplicateServerIndex); + return true; + } + emit errorOccurred(errorCode); + return false; + } + + emit installServerFromApiFinished(tr("%1 has been added to the app").arg(m_apiServicesModel->getSelectedServiceName())); +#endif + return true; +} + +bool SubscriptionUiController::restoreServiceFromAppStore() +{ +#if defined(Q_OS_IOS) || defined(MACOS_NE) + const QString premiumServiceType = QStringLiteral("amnezia-premium"); + + if (!fillAvailableServices()) { + qWarning().noquote() << "[IAP] Unable to fetch services list before restore"; + emit errorOccurred(ErrorCode::ApiServicesMissingError); + return false; + } + + if (m_apiServicesModel->rowCount() <= 0) { + emit errorOccurred(ErrorCode::ApiServicesMissingError); + return false; + } + + // Ensure we have a valid premium selection for gateway requests + bool premiumSelected = false; + for (int i = 0; i < m_apiServicesModel->rowCount(); ++i) { + m_apiServicesModel->setServiceIndex(i); + if (m_apiServicesModel->getSelectedServiceType() == premiumServiceType) { + premiumSelected = true; + break; + } + } + + if (!premiumSelected) { + emit errorOccurred(ErrorCode::ApiServicesMissingError); + return false; + } + + SubscriptionController::AppStoreRestoreResult result = m_subscriptionController->processAppStoreRestore( + m_apiServicesModel->getCountryCode(), + m_apiServicesModel->getSelectedServiceType(), + m_apiServicesModel->getSelectedServiceProtocol()); + + if (!result.hasInstalledConfig) { + if (result.duplicateConfigAlreadyPresent) { + emit installServerFromApiFinished(tr("This subscription has already been added"), result.duplicateServerIndex); + return true; + } + emit errorOccurred(result.errorCode); + return false; + } + + emit installServerFromApiFinished(tr("Subscription restored successfully.")); + if (result.duplicateCount > 0) { + qInfo().noquote() << "[IAP] Skipped" << result.duplicateCount + << "duplicate restored transactions for original transaction IDs already processed"; + } +#endif + return true; +} + +bool SubscriptionUiController::importFreeFromGateway() +{ + QString userCountryCode = m_apiServicesModel->getCountryCode(); + QString serviceType = m_apiServicesModel->getSelectedServiceType(); + QString serviceProtocol = m_apiServicesModel->getSelectedServiceProtocol(); + + if (m_serversController->isServerFromApiAlreadyExists(userCountryCode, serviceType, serviceProtocol)) { + emit errorOccurred(ErrorCode::ApiConfigAlreadyAdded); + return false; + } + + SubscriptionController::ProtocolData protocolData = m_subscriptionController->generateProtocolData(serviceProtocol); + + ServerConfig serverConfig; + ErrorCode errorCode = m_subscriptionController->importServiceFromGateway(userCountryCode, serviceType, + serviceProtocol, protocolData, + serverConfig); + + if (errorCode == ErrorCode::NoError) { + emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName())); + return true; + } else { + emit errorOccurred(errorCode); + return false; + } +} + +bool SubscriptionUiController::importTrialFromGateway(const QString &email) +{ + emit trialEmailError(QString()); + ServerConfig serverConfig; + ErrorCode errorCode = m_subscriptionController->importTrialFromGateway(m_apiServicesModel->getCountryCode(), + m_apiServicesModel->getSelectedServiceType(), + m_apiServicesModel->getSelectedServiceProtocol(), + email, + serverConfig); + if (errorCode != ErrorCode::NoError) { + if (errorCode == ErrorCode::ApiTrialAlreadyUsedError) { + emit trialEmailError( + tr("This email address has already been used to activate a trial. If you like the service, you can upgrade to Premium")); + } else { + emit errorOccurred(errorCode); + } + return false; + } + + emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName())); + return true; +} + +bool SubscriptionUiController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, + bool reloadServiceConfig) +{ + bool isConnectEvent = newCountryCode.isEmpty() && newCountryName.isEmpty() && !reloadServiceConfig; + bool wasSubscriptionExpired = false; + ServerConfig oldServerConfig = m_serversController->getServerConfig(serverIndex); + if (oldServerConfig.isApiV2()) { + const ApiV2ServerConfig *oldApiV2 = oldServerConfig.as(); + if (oldApiV2) { + wasSubscriptionExpired = oldApiV2->apiConfig.isSubscriptionExpired(); + } + } + + ErrorCode errorCode = m_subscriptionController->updateServiceFromGateway(serverIndex, newCountryCode, isConnectEvent); + + if (errorCode == ErrorCode::NoError) { + if (wasSubscriptionExpired) { + emit subscriptionRefreshNeeded(); + } + if (reloadServiceConfig) { + emit reloadServerFromApiFinished(tr("API config reloaded")); + } else if (newCountryName.isEmpty()) { + emit updateServerFromApiFinished(); + } else { + emit changeApiCountryFinished(tr("Successfully changed the country of connection to %1").arg(newCountryName)); + } + return true; + } else { + if (errorCode == ErrorCode::ApiSubscriptionExpiredError) { + emit subscriptionExpiredOnServer(); + } + emit errorOccurred(errorCode); + return false; + } +} + +bool SubscriptionUiController::updateServiceFromTelegram(const int serverIndex) +{ +#ifdef Q_OS_IOS + IosController::Instance()->requestInetAccess(); + QThread::msleep(10); +#endif + + ErrorCode errorCode = m_subscriptionController->updateServiceFromTelegram(serverIndex); + + if (errorCode == ErrorCode::NoError) { + emit updateServerFromApiFinished(); + return true; + } else { + emit errorOccurred(errorCode); + return false; + } +} + +bool SubscriptionUiController::deactivateDevice(int serverIndex, const bool isRemoveEvent) +{ + + ErrorCode errorCode = m_subscriptionController->deactivateDevice(serverIndex, isRemoveEvent); + if (errorCode != ErrorCode::NoError) { + if (errorCode == ErrorCode::ApiSubscriptionExpiredError && isRemoveEvent) { + return true; + } + emit errorOccurred(errorCode); + return false; + } + + return true; +} + +bool SubscriptionUiController::deactivateExternalDevice(int serverIndex, const QString &uuid, const QString &serverCountryCode) +{ + ErrorCode errorCode = m_subscriptionController->deactivateExternalDevice(serverIndex, uuid, serverCountryCode); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } + + return true; +} + +void SubscriptionUiController::validateConfig() +{ + int serverIndex = m_serversController->getDefaultServerIndex(); + bool hasInstalledContainers = m_serversController->hasInstalledContainers(serverIndex); + + ErrorCode errorCode = m_subscriptionController->validateAndUpdateConfig(serverIndex, hasInstalledContainers); + + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + emit configValidated(false); + return; + } + emit configValidated(true); +} + +void SubscriptionUiController::setCurrentProtocol(int serverIndex, const QString &protocolName) +{ + m_subscriptionController->setCurrentProtocol(serverIndex, protocolName); +} + +bool SubscriptionUiController::isVlessProtocol(int serverIndex) +{ + return m_subscriptionController->isVlessProtocol(serverIndex); +} + +void SubscriptionUiController::removeApiConfig(int serverIndex) +{ + m_subscriptionController->removeApiConfig(serverIndex); + emit apiConfigRemoved(tr("Api config removed")); +} + +QList SubscriptionUiController::getQrCodes() +{ + return m_qrCodes; +} + +int SubscriptionUiController::getQrCodesCount() +{ + return static_cast(m_qrCodes.size()); +} + +QString SubscriptionUiController::getVpnKey() +{ + return m_vpnKey; +} + +bool SubscriptionUiController::getAccountInfo(int serverIndex, bool reload) +{ + if (reload) { + QEventLoop wait; + QTimer::singleShot(1000, &wait, &QEventLoop::quit); + wait.exec(QEventLoop::ExcludeUserInputEvents); + } + QJsonObject accountInfo; + ErrorCode errorCode = m_subscriptionController->getAccountInfo(serverIndex, accountInfo); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } + + ServerConfig serverConfig = m_serversController->getServerConfig(serverIndex); + QJsonObject serverConfigJson = serverConfig.toJson(); + m_apiAccountInfoModel->updateModel(accountInfo, serverConfigJson); + + if (reload) { + updateApiCountryModel(); + updateApiDevicesModel(); + } + + return true; +} + +void SubscriptionUiController::updateApiCountryModel() +{ + m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), ""); + m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo()); +} + +void SubscriptionUiController::updateApiDevicesModel() +{ + m_apiDevicesModel->updateModel(m_apiAccountInfoModel->getIssuedConfigsInfo(), m_settingsController->getInstallationUuid(false)); +} + +void SubscriptionUiController::getRenewalLink(int serverIndex) +{ + if (serverIndex < 0) { + emit errorOccurred(ErrorCode::InternalError); + return; + } + + auto *watcher = new QFutureWatcher>(this); + connect(watcher, &QFutureWatcher>::finished, this, [this, watcher]() { + const auto [errorCode, url] = watcher->result(); + watcher->deleteLater(); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return; + } + emit renewalLinkReceived(url); + }); + watcher->setFuture(m_subscriptionController->getRenewalLink(serverIndex)); +} + diff --git a/client/ui/controllers/api/subscriptionUiController.h b/client/ui/controllers/api/subscriptionUiController.h new file mode 100644 index 000000000..73e37593f --- /dev/null +++ b/client/ui/controllers/api/subscriptionUiController.h @@ -0,0 +1,103 @@ +#ifndef SUBSCRIPTIONUICONTROLLER_H +#define SUBSCRIPTIONUICONTROLLER_H + +#include + +#include "core/controllers/serversController.h" +#include "core/controllers/settingsController.h" +#include "core/controllers/api/servicesCatalogController.h" +#include "core/controllers/api/subscriptionController.h" +#include "ui/models/api/apiSubscriptionPlansModel.h" +#include "ui/models/api/apiBenefitsModel.h" +#include "ui/models/api/apiServicesModel.h" +#include "ui/models/api/apiAccountInfoModel.h" +#include "ui/models/api/apiCountryModel.h" +#include "ui/models/api/apiDevicesModel.h" +class SubscriptionUiController : public QObject +{ + Q_OBJECT +public: + SubscriptionUiController(ServersController* serversController, + ApiServicesModel* apiServicesModel, + ServicesCatalogController* servicesCatalogController, + SubscriptionController* subscriptionController, + ApiSubscriptionPlansModel* apiSubscriptionPlansModel, + ApiBenefitsModel* apiBenefitsModel, + ApiAccountInfoModel* apiAccountInfoModel, + ApiCountryModel* apiCountryModel, + ApiDevicesModel* apiDevicesModel, + SettingsController* settingsController, + QObject *parent = nullptr); + + Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY vpnKeyExportReady) + Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY vpnKeyExportReady) + Q_PROPERTY(QString vpnKey READ getVpnKey NOTIFY vpnKeyExportReady) + +public slots: + bool exportNativeConfig(int serverIndex, const QString &serverCountryCode, const QString &fileName); + bool revokeNativeConfig(int serverIndex, const QString &serverCountryCode); + bool exportVpnKey(int serverIndex, const QString &fileName); + void prepareVpnKeyExport(int serverIndex); + void copyVpnKeyToClipboard(); + + bool fillAvailableServices(); + bool importPremiumFromAppStore(const QString &storeProductId); + bool importFreeFromGateway(); + bool restoreServiceFromAppStore(); + bool importTrialFromGateway(const QString &email); + bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, + bool reloadServiceConfig = false); + bool updateServiceFromTelegram(const int serverIndex); + bool deactivateDevice(int serverIndex, const bool isRemoveEvent); + bool deactivateExternalDevice(int serverIndex, const QString &uuid, const QString &serverCountryCode); + + void validateConfig(); + + void setCurrentProtocol(int serverIndex, const QString &protocolName); + bool isVlessProtocol(int serverIndex); + + void removeApiConfig(int serverIndex); + + bool getAccountInfo(int serverIndex, bool reload); + void getRenewalLink(int serverIndex); + void updateApiCountryModel(); + void updateApiDevicesModel(); + +signals: + void configValidated(bool isValid); + void errorOccurred(ErrorCode errorCode); + void trialEmailError(const QString &message); + void subscriptionExpiredOnServer(); + void renewalLinkReceived(const QString &url); + + void installServerFromApiFinished(const QString &message, int preferredDefaultServerIndex = -1); + void changeApiCountryFinished(const QString &message); + void reloadServerFromApiFinished(const QString &message); + void updateServerFromApiFinished(); + void subscriptionRefreshNeeded(); + + void apiConfigRemoved(const QString &message); + + void vpnKeyExportReady(); + +private: + QList getQrCodes(); + int getQrCodesCount(); + QString getVpnKey(); + + QList m_qrCodes; + QString m_vpnKey; + + ServersController* m_serversController; + ApiServicesModel* m_apiServicesModel; + ServicesCatalogController* m_servicesCatalogController; + SubscriptionController* m_subscriptionController; + ApiSubscriptionPlansModel* m_apiSubscriptionPlansModel; + ApiBenefitsModel* m_apiBenefitsModel; + ApiAccountInfoModel* m_apiAccountInfoModel; + ApiCountryModel* m_apiCountryModel; + ApiDevicesModel* m_apiDevicesModel; + SettingsController* m_settingsController; +}; + +#endif // SUBSCRIPTIONUICONTROLLER_H diff --git a/client/ui/controllers/appSplitTunnelingController.cpp b/client/ui/controllers/appSplitTunnelingController.cpp deleted file mode 100644 index 6452bba2c..000000000 --- a/client/ui/controllers/appSplitTunnelingController.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "appSplitTunnelingController.h" - -#include - -#include "core/defs.h" - -AppSplitTunnelingController::AppSplitTunnelingController(const std::shared_ptr &settings, - const QSharedPointer &appSplitTunnelingModel, QObject *parent) - : QObject(parent), m_settings(settings), m_appSplitTunnelingModel(appSplitTunnelingModel) -{ -} - -void AppSplitTunnelingController::addApp(const QString &appPath) -{ - - InstalledAppInfo appInfo { "", "", appPath }; - if (!appPath.isEmpty()) { - QFileInfo fileInfo(appPath); - appInfo.appName = fileInfo.fileName(); - } - - if (m_appSplitTunnelingModel->addApp(appInfo)) { - emit finished(tr("Application added: %1").arg(appInfo.appName)); - - } else { - emit errorOccurred(tr("The application has already been added")); - } -} - -void AppSplitTunnelingController::addApps(QVector> apps) -{ - for (const auto &app : apps) { - InstalledAppInfo appInfo { app.first, app.second, "" }; - - m_appSplitTunnelingModel->addApp(appInfo); - } - emit finished(tr("The selected applications have been added")); -} - -void AppSplitTunnelingController::removeApp(const int index) -{ - auto modelIndex = m_appSplitTunnelingModel->index(index); - auto appPath = m_appSplitTunnelingModel->data(modelIndex, AppSplitTunnelingModel::Roles::AppPathRole).toString(); - m_appSplitTunnelingModel->removeApp(modelIndex); - - QFileInfo fileInfo(appPath); - - emit finished(tr("Application removed: %1").arg(fileInfo.fileName())); -} diff --git a/client/ui/controllers/appSplitTunnelingController.h b/client/ui/controllers/appSplitTunnelingController.h deleted file mode 100644 index da1009ecf..000000000 --- a/client/ui/controllers/appSplitTunnelingController.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef APPSPLITTUNNELINGCONTROLLER_H -#define APPSPLITTUNNELINGCONTROLLER_H - -#include - -#include "settings.h" -#include "ui/models/appSplitTunnelingModel.h" - -class AppSplitTunnelingController : public QObject -{ - Q_OBJECT -public: - explicit AppSplitTunnelingController(const std::shared_ptr &settings, - const QSharedPointer &sitesModel, QObject *parent = nullptr); - -public slots: - void addApp(const QString &appPath); - void addApps(QVector> apps); - void removeApp(const int index); - -signals: - void errorOccurred(const QString &errorMessage); - void finished(const QString &message); - -private: - std::shared_ptr m_settings; - - QSharedPointer m_appSplitTunnelingModel; -}; - -#endif // APPSPLITTUNNELINGCONTROLLER_H diff --git a/client/ui/controllers/appSplitTunnelingUiController.cpp b/client/ui/controllers/appSplitTunnelingUiController.cpp new file mode 100644 index 000000000..ee06160d8 --- /dev/null +++ b/client/ui/controllers/appSplitTunnelingUiController.cpp @@ -0,0 +1,79 @@ +#include "appSplitTunnelingUiController.h" + +#include + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" + +AppSplitTunnelingUiController::AppSplitTunnelingUiController(AppSplitTunnelingController* appSplitTunnelingController, + AppSplitTunnelingModel* appSplitTunnelingModel, + QObject *parent) + : QObject(parent), + m_appSplitTunnelingController(appSplitTunnelingController), + m_appSplitTunnelingModel(appSplitTunnelingModel) +{ + m_appSplitTunnelingModel->updateModel(m_appSplitTunnelingController->getApps()); +} + +void AppSplitTunnelingUiController::addApp(const QString &appPath) +{ + amnezia::InstalledAppInfo appInfo { "", "", appPath }; + if (!appPath.isEmpty()) { + QFileInfo fileInfo(appPath); + appInfo.appName = fileInfo.fileName(); + } + + if (m_appSplitTunnelingController->addApp(appInfo)) { + emit finished(tr("Application added: %1").arg(appInfo.appName)); + } else { + emit errorOccurred(tr("The application has already been added")); + } +} + +void AppSplitTunnelingUiController::addApps(QVector> apps) +{ + for (const auto &app : apps) { + amnezia::InstalledAppInfo appInfo { app.first, app.second, "" }; + m_appSplitTunnelingController->addApp(appInfo); + } + emit finished(tr("The selected applications have been added")); +} + +void AppSplitTunnelingUiController::removeApp(const int index) +{ + auto modelIndex = m_appSplitTunnelingModel->index(index); + auto appPath = m_appSplitTunnelingModel->data(modelIndex, AppSplitTunnelingModel::Roles::AppPathRole).toString(); + m_appSplitTunnelingController->removeApp(index); + + QFileInfo fileInfo(appPath); + emit finished(tr("Application removed: %1").arg(fileInfo.fileName())); +} + +void AppSplitTunnelingUiController::toggleSplitTunneling(bool enabled) +{ + m_appSplitTunnelingController->toggleSplitTunneling(enabled); + emit isSplitTunnelingEnabledChanged(); +} + +void AppSplitTunnelingUiController::setRouteMode(int routeMode) +{ + m_appSplitTunnelingController->setRouteMode(static_cast(routeMode)); + emit routeModeChanged(); +} + +int AppSplitTunnelingUiController::getRouteMode() const +{ + return static_cast(m_appSplitTunnelingController->getRouteMode()); +} + +bool AppSplitTunnelingUiController::isSplitTunnelingEnabled() const +{ + return m_appSplitTunnelingController->isSplitTunnelingEnabled(); +} + +void AppSplitTunnelingUiController::updateModel() +{ + m_appSplitTunnelingModel->updateModel(m_appSplitTunnelingController->getApps()); +} + diff --git a/client/ui/controllers/appSplitTunnelingUiController.h b/client/ui/controllers/appSplitTunnelingUiController.h new file mode 100644 index 000000000..0db0e81bb --- /dev/null +++ b/client/ui/controllers/appSplitTunnelingUiController.h @@ -0,0 +1,48 @@ +#ifndef APPSPLITTUNNELINGUICONTROLLER_H +#define APPSPLITTUNNELINGUICONTROLLER_H + +#include +#include + +#include "core/controllers/appSplitTunnelingController.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "ui/models/appSplitTunnelingModel.h" + +class AppSplitTunnelingUiController : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int routeMode READ getRouteMode WRITE setRouteMode NOTIFY routeModeChanged) + Q_PROPERTY(bool isSplitTunnelingEnabled READ isSplitTunnelingEnabled NOTIFY isSplitTunnelingEnabledChanged) + +public: + explicit AppSplitTunnelingUiController(AppSplitTunnelingController* appSplitTunnelingController, + AppSplitTunnelingModel* appSplitTunnelingModel, + QObject *parent = nullptr); + +public slots: + void addApp(const QString &appPath); + void addApps(QVector> apps); + void removeApp(const int index); + void toggleSplitTunneling(bool enabled); + void setRouteMode(int routeMode); + + int getRouteMode() const; + bool isSplitTunnelingEnabled() const; + + void updateModel(); + +signals: + void routeModeChanged(); + void isSplitTunnelingEnabledChanged(); + void errorOccurred(const QString &errorMessage); + void finished(const QString &message); + +private: + AppSplitTunnelingController* m_appSplitTunnelingController; + AppSplitTunnelingModel* m_appSplitTunnelingModel; +}; + +#endif // APPSPLITTUNNELINGUICONTROLLER_H diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp deleted file mode 100644 index d84b9c898..000000000 --- a/client/ui/controllers/connectionController.cpp +++ /dev/null @@ -1,182 +0,0 @@ -#include "connectionController.h" - -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) - #include -#else - #include -#endif - -#include "amnezia_application.h" -#include "utilities.h" -#include "core/controllers/vpnConfigurationController.h" -#include "version.h" - -ConnectionController::ConnectionController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const QSharedPointer &clientManagementModel, - const QSharedPointer &vpnConnection, const std::shared_ptr &settings, - QObject *parent) - : QObject(parent), - m_serversModel(serversModel), - m_containersModel(containersModel), - m_clientManagementModel(clientManagementModel), - m_vpnConnection(vpnConnection), - m_settings(settings) -{ - connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, this, &ConnectionController::onConnectionStateChanged); - connect(this, &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); - connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); - - connect(this, &ConnectionController::connectButtonClicked, this, &ConnectionController::toggleConnection, Qt::QueuedConnection); - - m_state = Vpn::ConnectionState::Disconnected; -} - -void ConnectionController::openConnection() -{ -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) - if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) - { - emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning); - return; - } -#endif - - int serverIndex = m_serversModel->getDefaultServerIndex(); - QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); - - DockerContainer container = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); - - if (!m_containersModel->isSupportedByCurrentPlatform(container)) { - emit connectionErrorOccurred(ErrorCode::NotSupportedOnThisPlatform); - return; - } - - QSharedPointer serverController(new ServerController(m_settings)); - VpnConfigurationsController vpnConfigurationController(m_settings, serverController); - - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - - auto dns = m_serversModel->getDnsPair(serverIndex); - - auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container); - emit connectToVpn(serverIndex, credentials, container, vpnConfiguration); -} - -void ConnectionController::closeConnection() -{ - emit disconnectFromVpn(); -} - -ErrorCode ConnectionController::getLastConnectionError() -{ - return m_vpnConnection->lastError(); -} - -void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) -{ - m_state = state; - - m_isConnected = false; - m_connectionStateText = tr("Connecting..."); - switch (state) { - case Vpn::ConnectionState::Connected: { - amnApp->networkManager()->clearConnectionCache(); - - m_isConnectionInProgress = false; - m_isConnected = true; - m_connectionStateText = tr("Connected"); - break; - } - case Vpn::ConnectionState::Connecting: { - m_isConnectionInProgress = true; - break; - } - case Vpn::ConnectionState::Reconnecting: { - m_isConnectionInProgress = true; - m_connectionStateText = tr("Reconnecting..."); - break; - } - case Vpn::ConnectionState::Disconnected: { - m_isConnectionInProgress = false; - m_connectionStateText = tr("Connect"); - break; - } - case Vpn::ConnectionState::Disconnecting: { - m_isConnectionInProgress = true; - m_connectionStateText = tr("Disconnecting..."); - break; - } - case Vpn::ConnectionState::Preparing: { - m_isConnectionInProgress = true; - m_connectionStateText = tr("Preparing..."); - break; - } - case Vpn::ConnectionState::Error: { - m_isConnectionInProgress = false; - m_connectionStateText = tr("Connect"); - emit connectionErrorOccurred(getLastConnectionError()); - break; - } - case Vpn::ConnectionState::Unknown: { - m_isConnectionInProgress = false; - m_connectionStateText = tr("Connect"); - emit connectionErrorOccurred(getLastConnectionError()); - break; - } - } - emit connectionStateChanged(); -} - -void ConnectionController::onCurrentContainerUpdated() -{ - if (m_isConnected || m_isConnectionInProgress) { - emit reconnectWithUpdatedContainer(tr("Settings updated successfully, reconnnection...")); - openConnection(); - } else { - emit reconnectWithUpdatedContainer(tr("Settings updated successfully")); - } -} - -void ConnectionController::onTranslationsUpdated() -{ - // get translated text of current state - onConnectionStateChanged(getCurrentConnectionState()); -} - -Vpn::ConnectionState ConnectionController::getCurrentConnectionState() -{ - return m_state; -} - -QString ConnectionController::connectionStateText() const -{ - return m_connectionStateText; -} - -void ConnectionController::toggleConnection() -{ - if (m_state == Vpn::ConnectionState::Preparing) { - emit preparingConfig(); - return; - } - - if (isConnectionInProgress()) { - closeConnection(); - } else if (isConnected()) { - closeConnection(); - } else { - emit prepareConfig(); - } -} - -bool ConnectionController::isConnectionInProgress() const -{ - return m_isConnectionInProgress; -} - -bool ConnectionController::isConnected() const -{ - return m_isConnected; -} diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h deleted file mode 100644 index cabeb601a..000000000 --- a/client/ui/controllers/connectionController.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef CONNECTIONCONTROLLER_H -#define CONNECTIONCONTROLLER_H - -#include "protocols/vpnprotocol.h" -#include "ui/models/clientManagementModel.h" -#include "ui/models/containers_model.h" -#include "ui/models/servers_model.h" -#include "vpnconnection.h" - -class ConnectionController : public QObject -{ - Q_OBJECT - -public: - Q_PROPERTY(bool isConnected READ isConnected NOTIFY connectionStateChanged) - Q_PROPERTY(bool isConnectionInProgress READ isConnectionInProgress NOTIFY connectionStateChanged) - Q_PROPERTY(QString connectionStateText READ connectionStateText NOTIFY connectionStateChanged) - - explicit ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &clientManagementModel, - const QSharedPointer &vpnConnection, const std::shared_ptr &settings, - QObject *parent = nullptr); - - ~ConnectionController() = default; - - bool isConnected() const; - bool isConnectionInProgress() const; - QString connectionStateText() const; - -public slots: - void toggleConnection(); - - void openConnection(); - void closeConnection(); - - ErrorCode getLastConnectionError(); - void onConnectionStateChanged(Vpn::ConnectionState state); - - void onCurrentContainerUpdated(); - - void onTranslationsUpdated(); - -signals: - void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration); - void disconnectFromVpn(); - void connectionStateChanged(); - - void connectionErrorOccurred(ErrorCode errorCode); - void reconnectWithUpdatedContainer(const QString &message); - - void connectButtonClicked(); - void preparingConfig(); - void prepareConfig(); - -private: - Vpn::ConnectionState getCurrentConnectionState(); - - void continueConnection(); - - QSharedPointer m_serversModel; - QSharedPointer m_containersModel; - QSharedPointer m_clientManagementModel; - - QSharedPointer m_vpnConnection; - - std::shared_ptr m_settings; - - bool m_isConnected = false; - bool m_isConnectionInProgress = false; - QString m_connectionStateText = tr("Connect"); - - Vpn::ConnectionState m_state; -}; - -#endif // CONNECTIONCONTROLLER_H diff --git a/client/ui/controllers/connectionUiController.cpp b/client/ui/controllers/connectionUiController.cpp new file mode 100644 index 000000000..a69c0bf3b --- /dev/null +++ b/client/ui/controllers/connectionUiController.cpp @@ -0,0 +1,152 @@ +#include "connectionUiController.h" + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) + #include +#else + #include +#endif + +#include "amneziaApplication.h" +#include "core/controllers/serversController.h" + +ConnectionUiController::ConnectionUiController(ConnectionController* connectionController, + ServersController* serversController, + QObject *parent) + : QObject(parent), + m_connectionController(connectionController), + m_serversController(serversController) +{ + connect(m_connectionController, &ConnectionController::connectionStateChanged, this, &ConnectionUiController::onConnectionStateChanged); + + connect(this, &ConnectionUiController::connectButtonClicked, this, &ConnectionUiController::toggleConnection, Qt::QueuedConnection); + + m_state = Vpn::ConnectionState::Disconnected; +} + +void ConnectionUiController::openConnection() +{ + int serverIndex = m_serversController->getDefaultServerIndex(); + + ErrorCode errorCode = m_connectionController->openConnection(serverIndex); + + if (errorCode != ErrorCode::NoError) { + emit connectionErrorOccurred(errorCode); + return; + } +} + +void ConnectionUiController::closeConnection() +{ + m_connectionController->closeConnection(); +} + +ErrorCode ConnectionUiController::getLastConnectionError() +{ + return m_connectionController->lastConnectionError(); +} + +void ConnectionUiController::onConnectionStateChanged(Vpn::ConnectionState state) +{ + m_state = state; + + m_isConnected = false; + m_connectionStateText = tr("Connecting..."); + switch (state) { + case Vpn::ConnectionState::Connected: { + amnApp->networkManager()->clearConnectionCache(); + + m_isConnectionInProgress = false; + m_isConnected = true; + m_connectionStateText = tr("Connected"); + break; + } + case Vpn::ConnectionState::Connecting: { + m_isConnectionInProgress = true; + break; + } + case Vpn::ConnectionState::Reconnecting: { + m_isConnectionInProgress = true; + m_connectionStateText = tr("Reconnecting..."); + break; + } + case Vpn::ConnectionState::Disconnected: { + m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); + break; + } + case Vpn::ConnectionState::Disconnecting: { + m_isConnectionInProgress = true; + m_connectionStateText = tr("Disconnecting..."); + break; + } + case Vpn::ConnectionState::Preparing: { + m_isConnectionInProgress = true; + m_connectionStateText = tr("Preparing..."); + break; + } + case Vpn::ConnectionState::Error: { + m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); + emit connectionErrorOccurred(getLastConnectionError()); + break; + } + case Vpn::ConnectionState::Unknown: { + m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); + emit connectionErrorOccurred(getLastConnectionError()); + break; + } + } + emit connectionStateChanged(); +} + +void ConnectionUiController::onCurrentContainerUpdated() +{ + if (m_isConnected || m_isConnectionInProgress) { + emit reconnectWithUpdatedContainer(tr("Settings updated successfully, reconnection...")); + openConnection(); + } else { + emit reconnectWithUpdatedContainer(tr("Settings updated successfully")); + } +} + +void ConnectionUiController::onTranslationsUpdated() +{ + onConnectionStateChanged(getCurrentConnectionState()); +} + +Vpn::ConnectionState ConnectionUiController::getCurrentConnectionState() +{ + return m_state; +} + +QString ConnectionUiController::connectionStateText() const +{ + return m_connectionStateText; +} + +void ConnectionUiController::toggleConnection() +{ + if (m_state == Vpn::ConnectionState::Preparing) { + emit preparingConfig(); + return; + } + + if (isConnectionInProgress()) { + closeConnection(); + } else if (isConnected()) { + closeConnection(); + } else { + emit prepareConfig(); + } +} + +bool ConnectionUiController::isConnectionInProgress() const +{ + return m_isConnectionInProgress; +} + +bool ConnectionUiController::isConnected() const +{ + return m_isConnected; +} diff --git a/client/ui/controllers/connectionUiController.h b/client/ui/controllers/connectionUiController.h new file mode 100644 index 000000000..e09f2977e --- /dev/null +++ b/client/ui/controllers/connectionUiController.h @@ -0,0 +1,68 @@ +#ifndef CONNECTIONUICONTROLLER_H +#define CONNECTIONUICONTROLLER_H + +#include + +#include "core/controllers/connectionController.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/protocols/vpnProtocol.h" +#include "core/controllers/serversController.h" + +class ConnectionUiController : public QObject +{ + Q_OBJECT + +public: + Q_PROPERTY(bool isConnected READ isConnected NOTIFY connectionStateChanged) + Q_PROPERTY(bool isConnectionInProgress READ isConnectionInProgress NOTIFY connectionStateChanged) + Q_PROPERTY(QString connectionStateText READ connectionStateText NOTIFY connectionStateChanged) + + explicit ConnectionUiController(ConnectionController* connectionController, + ServersController* serversController, + QObject *parent = nullptr); + + ~ConnectionUiController() = default; + + bool isConnected() const; + bool isConnectionInProgress() const; + QString connectionStateText() const; + +public slots: + void toggleConnection(); + + void openConnection(); + void closeConnection(); + + ErrorCode getLastConnectionError(); + void onConnectionStateChanged(Vpn::ConnectionState state); + + void onCurrentContainerUpdated(); + + void onTranslationsUpdated(); + +signals: + void connectionStateChanged(); + + void connectionErrorOccurred(ErrorCode errorCode); + void reconnectWithUpdatedContainer(const QString &message); + + void connectButtonClicked(); + void preparingConfig(); + void prepareConfig(); + +private: + Vpn::ConnectionState getCurrentConnectionState(); + + ConnectionController* m_connectionController; + ServersController* m_serversController; + + bool m_isConnected = false; + bool m_isConnectionInProgress = false; + QString m_connectionStateText = tr("Connect"); + + Vpn::ConnectionState m_state; +}; + +#endif diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp deleted file mode 100644 index cdaead402..000000000 --- a/client/ui/controllers/exportController.cpp +++ /dev/null @@ -1,392 +0,0 @@ -#include "exportController.h" - -#include -#include -#include -#include -#include -#include -#include -#include "core/controllers/vpnConfigurationController.h" -#include "core/qrCodeUtils.h" -#include "core/serialization/serialization.h" -#include "core/serialization/transfer.h" -#include "systemController.h" -#include - -ExportController::ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &clientManagementModel, - const std::shared_ptr &settings, QObject *parent) - : QObject(parent), - m_serversModel(serversModel), - m_containersModel(containersModel), - m_clientManagementModel(clientManagementModel), - m_settings(settings) -{ -} - -void ExportController::generateFullAccessConfig() -{ - clearPreviousConfig(); - - int serverIndex = m_serversModel->getProcessedServerIndex(); - QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); - - QJsonArray containers = serverConfig.value(config_key::containers).toArray(); - for (auto i = 0; i < containers.size(); i++) { - auto containerConfig = containers.at(i).toObject(); - auto containerType = ContainerProps::containerFromString(containerConfig.value(config_key::container).toString()); - - for (auto protocol : ContainerProps::protocolsForContainer(containerType)) { - auto protocolConfig = containerConfig.value(ProtocolProps::protoToString(protocol)).toObject(); - - protocolConfig.remove(config_key::last_config); - containerConfig[ProtocolProps::protoToString(protocol)] = protocolConfig; - } - - containers.replace(i, containerConfig); - } - serverConfig[config_key::containers] = containers; - - QByteArray compressedConfig = QJsonDocument(serverConfig).toJson(); - compressedConfig = qCompress(compressedConfig, 8); - m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); - - m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(compressedConfig); - emit exportConfigChanged(); -} - -void ExportController::generateConnectionConfig(const QString &clientName) -{ - clearPreviousConfig(); - - int serverIndex = m_serversModel->getProcessedServerIndex(); - ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - - DockerContainer container = static_cast(m_containersModel->getProcessedContainerIndex()); - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); - - QSharedPointer serverController(new ServerController(m_settings)); - VpnConfigurationsController vpnConfigurationController(m_settings, serverController); - ErrorCode errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container, containerConfig); - - errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig, clientName, serverController); - if (errorCode != ErrorCode::NoError) { - emit exportErrorOccurred(errorCode); - return; - } - - QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); - if (!errorCode) { - serverConfig.remove(config_key::userName); - serverConfig.remove(config_key::password); - serverConfig.remove(config_key::port); - serverConfig.insert(config_key::containers, QJsonArray { containerConfig }); - serverConfig.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - - auto dns = m_serversModel->getDnsPair(serverIndex); - serverConfig.insert(config_key::dns1, dns.first); - serverConfig.insert(config_key::dns2, dns.second); - } - - QByteArray compressedConfig = QJsonDocument(serverConfig).toJson(); - compressedConfig = qCompress(compressedConfig, 8); - m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); - - m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(compressedConfig); - emit exportConfigChanged(); -} - -ErrorCode ExportController::generateNativeConfig(const DockerContainer container, const QString &clientName, const Proto &protocol, - QJsonObject &jsonNativeConfig) -{ - clearPreviousConfig(); - - int serverIndex = m_serversModel->getProcessedServerIndex(); - ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - auto dns = m_serversModel->getDnsPair(serverIndex); - bool isApiConfig = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::IsServerFromTelegramApiRole)); - - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); - - QSharedPointer serverController(new ServerController(m_settings)); - VpnConfigurationsController vpnConfigurationController(m_settings, serverController); - - QString protocolConfigString; - ErrorCode errorCode = vpnConfigurationController.createProtocolConfigString(isApiConfig, dns, credentials, container, containerConfig, - protocol, protocolConfigString); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - jsonNativeConfig = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); - - if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg || protocol == Proto::Xray) { - errorCode = m_clientManagementModel->appendClient(jsonNativeConfig, clientName, container, credentials, serverController); - } - return errorCode; -} - -void ExportController::generateOpenVpnConfig(const QString &clientName) -{ - QJsonObject nativeConfig; - DockerContainer container = static_cast(m_containersModel->getProcessedContainerIndex()); - ErrorCode errorCode = ErrorCode::NoError; - - if (container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks) { - errorCode = generateNativeConfig(container, clientName, Proto::OpenVpn, nativeConfig); - } else { - errorCode = generateNativeConfig(container, clientName, ContainerProps::defaultProtocol(container), nativeConfig); - } - - if (errorCode) { - emit exportErrorOccurred(errorCode); - return; - } - - QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n"); - for (const QString &line : std::as_const(lines)) { - m_config.append(line + "\n"); - } - - m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8()); - emit exportConfigChanged(); -} - -void ExportController::generateWireGuardConfig(const QString &clientName) -{ - QJsonObject nativeConfig; - ErrorCode errorCode = generateNativeConfig(DockerContainer::WireGuard, clientName, Proto::WireGuard, nativeConfig); - if (errorCode) { - emit exportErrorOccurred(errorCode); - return; - } - - QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n"); - for (const QString &line : std::as_const(lines)) { - m_config.append(line + "\n"); - } - - m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8()); - - emit exportConfigChanged(); -} - -void ExportController::generateAwgConfig(const QString &clientName) -{ - QJsonObject nativeConfig; - ErrorCode errorCode = generateNativeConfig(static_cast(m_containersModel->getProcessedContainerIndex()), clientName, - Proto::Awg, nativeConfig); - if (errorCode) { - emit exportErrorOccurred(errorCode); - return; - } - - QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n"); - for (const QString &line : std::as_const(lines)) { - m_config.append(line + "\n"); - } - - m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8()); - - emit exportConfigChanged(); -} - -void ExportController::generateShadowSocksConfig() -{ - QJsonObject nativeConfig; - DockerContainer container = static_cast(m_containersModel->getProcessedContainerIndex()); - ErrorCode errorCode = ErrorCode::NoError; - - if (container == DockerContainer::Cloak) { - errorCode = generateNativeConfig(container, "", Proto::ShadowSocks, nativeConfig); - } else { - errorCode = generateNativeConfig(container, "", ContainerProps::defaultProtocol(container), nativeConfig); - } - - if (errorCode) { - emit exportErrorOccurred(errorCode); - return; - } - - QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n"); - for (const QString &line : std::as_const(lines)) { - m_config.append(line + "\n"); - } - - m_nativeConfigString = QString("%1:%2@%3:%4") - .arg(nativeConfig.value("method").toString(), nativeConfig.value("password").toString(), - nativeConfig.value("server").toString(), nativeConfig.value("server_port").toString()); - - m_nativeConfigString = "ss://" + m_nativeConfigString.toUtf8().toBase64(); - - auto qr = qrCodeUtils::generateQrCode(m_nativeConfigString.toUtf8()); - m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1))); - - emit exportConfigChanged(); -} - -void ExportController::generateCloakConfig() -{ - QJsonObject nativeConfig; - ErrorCode errorCode = generateNativeConfig(DockerContainer::Cloak, "", Proto::Cloak, nativeConfig); - if (errorCode) { - emit exportErrorOccurred(errorCode); - return; - } - - nativeConfig.remove(config_key::transport_proto); - nativeConfig.insert("ProxyMethod", "shadowsocks"); - - QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n"); - for (const QString &line : std::as_const(lines)) { - m_config.append(line + "\n"); - } - - emit exportConfigChanged(); -} - -void ExportController::generateXrayConfig(const QString &clientName) -{ - // Xray data - QJsonObject nativeConfig; - ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, clientName, Proto::Xray, nativeConfig); - if (errorCode) { - emit exportErrorOccurred(errorCode); - return; - } - - QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n"); - for (const QString &line : std::as_const(lines)) { - m_config.append(line + "\n"); - } - // Xray data - - // Parse the Xray data to extract VLESS parameters and generate string - QString configString = QString(QJsonDocument(nativeConfig).toJson(QJsonDocument::Compact)); - - QJsonDocument doc = QJsonDocument::fromJson(configString.toUtf8()); - if (doc.isNull() || !doc.isObject()) { - qDebug() << "ERROR: Failed to parse config JSON"; - emit exportErrorOccurred(ErrorCode::InternalError); - return; - } - - QJsonObject xrayConfig = doc.object(); - QJsonArray outbounds = xrayConfig.value("outbounds").toArray(); - - if (outbounds.isEmpty()) { - qDebug() << "ERROR: Outbounds array is empty"; - emit exportErrorOccurred(ErrorCode::InternalError); - return; - } - - QJsonObject outbound = outbounds[0].toObject(); - QJsonObject settings = outbound.value("settings").toObject(); - QJsonObject streamSettings = outbound.value("streamSettings").toObject(); - - QJsonArray vnext = settings.value("vnext").toArray(); - if (vnext.isEmpty()) { - qDebug() << "ERROR: vnext array is empty"; - emit exportErrorOccurred(ErrorCode::InternalError); - return; - } - - QJsonObject server = vnext[0].toObject(); - QJsonArray users = server.value("users").toArray(); - if (users.isEmpty()) { - qDebug() << "ERROR: users array is empty"; - emit exportErrorOccurred(ErrorCode::InternalError); - return; - } - - QJsonObject user = users[0].toObject(); - - amnezia::serialization::VlessServerObject vlessServer; - vlessServer.address = server.value("address").toString(); - vlessServer.port = server.value("port").toInt(); - vlessServer.id = user.value("id").toString(); - vlessServer.flow = user.value("flow").toString("xtls-rprx-vision"); - vlessServer.encryption = user.value("encryption").toString("none"); - - vlessServer.network = streamSettings.value("network").toString("tcp"); - vlessServer.security = streamSettings.value("security").toString("reality"); - - if (vlessServer.security == "reality") { - QJsonObject realitySettings = streamSettings.value("realitySettings").toObject(); - vlessServer.serverName = realitySettings.value("serverName").toString(); - vlessServer.publicKey = realitySettings.value("publicKey").toString(); - vlessServer.shortId = realitySettings.value("shortId").toString(); - vlessServer.fingerprint = realitySettings.value("fingerprint").toString("chrome"); - vlessServer.spiderX = realitySettings.value("spiderX").toString(""); - } - - m_nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "AmneziaVPN"); - - emit exportConfigChanged(); -} - -QString ExportController::getConfig() -{ - return m_config; -} - -QString ExportController::getNativeConfigString() -{ - return m_nativeConfigString; -} - -QList ExportController::getQrCodes() -{ - return m_qrCodes; -} - -void ExportController::exportConfig(const QString &fileName) -{ - SystemController::saveFile(fileName, m_config); -} - -void ExportController::updateClientManagementModel(const DockerContainer container, ServerCredentials credentials) -{ - QSharedPointer serverController(new ServerController(m_settings)); - ErrorCode errorCode = m_clientManagementModel->updateModel(container, credentials, serverController); - if (errorCode != ErrorCode::NoError) { - emit exportErrorOccurred(errorCode); - } -} - -void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials) -{ - QSharedPointer serverController(new ServerController(m_settings)); - ErrorCode errorCode = - m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex(), serverController); - if (errorCode != ErrorCode::NoError) { - emit exportErrorOccurred(errorCode); - } - emit revokeConfigCompleted(); -} - -void ExportController::renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials) -{ - QSharedPointer serverController(new ServerController(m_settings)); - ErrorCode errorCode = m_clientManagementModel->renameClient(row, clientName, container, credentials, serverController); - if (errorCode != ErrorCode::NoError) { - emit exportErrorOccurred(errorCode); - } -} - -int ExportController::getQrCodesCount() -{ - return m_qrCodes.size(); -} - -void ExportController::clearPreviousConfig() -{ - m_config.clear(); - m_nativeConfigString.clear(); - m_qrCodes.clear(); - - emit exportConfigChanged(); -} diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h deleted file mode 100644 index 72f08b3e2..000000000 --- a/client/ui/controllers/exportController.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef EXPORTCONTROLLER_H -#define EXPORTCONTROLLER_H - -#include - -#include "ui/models/clientManagementModel.h" -#include "ui/models/containers_model.h" -#include "ui/models/servers_model.h" - -class ExportController : public QObject -{ - Q_OBJECT -public: - explicit ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &clientManagementModel, const std::shared_ptr &settings, - QObject *parent = nullptr); - - Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY exportConfigChanged) - Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged) - Q_PROPERTY(QString config READ getConfig NOTIFY exportConfigChanged) - Q_PROPERTY(QString nativeConfigString READ getNativeConfigString NOTIFY exportConfigChanged) - -public slots: - void generateFullAccessConfig(); - void generateConnectionConfig(const QString &clientName); - void generateOpenVpnConfig(const QString &clientName); - void generateWireGuardConfig(const QString &clientName); - void generateAwgConfig(const QString &clientName); - void generateShadowSocksConfig(); - void generateCloakConfig(); - void generateXrayConfig(const QString &clientName); - - QString getConfig(); - QString getNativeConfigString(); - QList getQrCodes(); - - void exportConfig(const QString &fileName); - - void updateClientManagementModel(const DockerContainer container, ServerCredentials credentials); - void revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials); - void renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials); - -signals: - void generateConfig(int type); - void revokeConfigCompleted(); - void exportErrorOccurred(const QString &errorMessage); - void exportErrorOccurred(ErrorCode errorCode); - - void exportConfigChanged(); - - void saveFile(const QString &fileName, const QString &data); - -private: - int getQrCodesCount(); - - void clearPreviousConfig(); - - ErrorCode generateNativeConfig(const DockerContainer container, const QString &clientName, const Proto &protocol, - QJsonObject &jsonNativeConfig); - - QSharedPointer m_serversModel; - QSharedPointer m_containersModel; - QSharedPointer m_clientManagementModel; - std::shared_ptr m_settings; - - QString m_config; - QString m_nativeConfigString; - QList m_qrCodes; -}; - -#endif // EXPORTCONTROLLER_H diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp deleted file mode 100644 index 3708d0f66..000000000 --- a/client/ui/controllers/importController.cpp +++ /dev/null @@ -1,773 +0,0 @@ -#include "importController.h" - -#include -#include -#include -#include -#include -#include - -#include "core/api/apiDefs.h" -#include "core/api/apiUtils.h" -#include "core/errorstrings.h" -#include "core/qrCodeUtils.h" -#include "core/serialization/serialization.h" -#include "protocols/protocols_defs.h" -#include "systemController.h" -#include "utilities.h" - -#ifdef Q_OS_ANDROID - #include "platforms/android/android_controller.h" -#endif -#if defined(Q_OS_IOS) || defined(MACOS_NE) - #include -#endif - -namespace -{ - ConfigTypes checkConfigFormat(const QString &config) - { - const QString openVpnConfigPatternCli = "client"; - const QString openVpnConfigPatternDriver1 = "dev tun"; - const QString openVpnConfigPatternDriver2 = "dev tap"; - - const QString wireguardConfigPatternSectionInterface = "[Interface]"; - const QString wireguardConfigPatternSectionPeer = "[Peer]"; - - const QString xrayConfigPatternInbound = "inbounds"; - const QString xrayConfigPatternOutbound = "outbounds"; - - const QString amneziaConfigPattern = "containers"; - const QString amneziaConfigPatternHostName = "hostName"; - const QString amneziaConfigPatternUserName = "userName"; - const QString amneziaConfigPatternPassword = "password"; - const QString amneziaFreeConfigPattern = "api_key"; - const QString amneziaPremiumConfigPattern = "auth_data"; - const QString backupPattern = "Servers/serversList"; - - if (config.contains(backupPattern)) { - return ConfigTypes::Backup; - } else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) - || config.contains(amneziaPremiumConfigPattern) - || (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName) - && config.contains(amneziaConfigPatternPassword))) { - return ConfigTypes::Amnezia; - } else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) { - return ConfigTypes::WireGuard; - } else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) { - return ConfigTypes::Xray; - } else if (config.contains(openVpnConfigPatternCli) - && (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { - return ConfigTypes::OpenVpn; - } - return ConfigTypes::Invalid; - } - -#if defined Q_OS_ANDROID - ImportController *mInstance = nullptr; -#endif -} // namespace - -ImportController::ImportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) -{ -#ifdef Q_OS_ANDROID - mInstance = this; -#endif -} - -bool ImportController::extractConfigFromFile(const QString &fileName) -{ - QString data; - if (!SystemController::readFile(fileName, data)) { - emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false); - return false; - } - m_configFileName = QFileInfo(QFile(fileName).fileName()).fileName(); -#ifdef Q_OS_ANDROID - if (m_configFileName.isEmpty()) { - m_configFileName = AndroidController::instance()->getFileName(fileName); - } -#endif - return extractConfigFromData(data); -} - -bool ImportController::extractConfigFromData(QString data) -{ - m_maliciousWarningText.clear(); - - QString config = data; - QString prefix; - QString errormsg; - - if (config.startsWith("vless://")) { - m_configType = ConfigTypes::Xray; - m_config = extractXrayConfig( - Utils::JsonToString(serialization::vless::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact), - prefix); - return m_config.empty() ? false : true; - } - - if (config.startsWith("vmess://") && config.contains("@")) { - m_configType = ConfigTypes::Xray; - m_config = extractXrayConfig( - Utils::JsonToString(serialization::vmess_new::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact), - prefix); - return m_config.empty() ? false : true; - } - - if (config.startsWith("vmess://")) { - m_configType = ConfigTypes::Xray; - m_config = extractXrayConfig( - Utils::JsonToString(serialization::vmess::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact), - prefix); - return m_config.empty() ? false : true; - } - - if (config.startsWith("trojan://")) { - m_configType = ConfigTypes::Xray; - m_config = extractXrayConfig( - Utils::JsonToString(serialization::trojan::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact), - prefix); - return m_config.empty() ? false : true; - } - - if (config.startsWith("ss://") && !config.contains("plugin=")) { - m_configType = ConfigTypes::ShadowSocks; - m_config = extractXrayConfig( - Utils::JsonToString(serialization::ss::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact), prefix); - return m_config.empty() ? false : true; - } - - if (config.startsWith("ssd://")) { - QStringList tmp; - QList> servers = serialization::ssd::Deserialize(config, &prefix, &tmp); - m_configType = ConfigTypes::ShadowSocks; - // Took only first config from list - if (!servers.isEmpty()) { - m_config = extractXrayConfig(servers.first().first); - } - return m_config.empty() ? false : true; - } - - m_configType = checkConfigFormat(config); - if (m_configType == ConfigTypes::Invalid) { - config.replace("vpn://", ""); - QByteArray ba = QByteArray::fromBase64(config.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QByteArray baUncompressed = qUncompress(ba); - if (!baUncompressed.isEmpty()) { - ba = baUncompressed; - } - - config = ba; - m_configType = checkConfigFormat(config); - } - - switch (m_configType) { - case ConfigTypes::OpenVpn: { - m_config = extractOpenVpnConfig(config); - if (!m_config.empty()) { - checkForMaliciousStrings(m_config); - return true; - } - return false; - } - case ConfigTypes::Awg: - case ConfigTypes::WireGuard: { - m_config = extractWireGuardConfig(config); - return m_config.empty() ? false : true; - } - case ConfigTypes::Xray: { - m_config = extractXrayConfig(config); - return m_config.empty() ? false : true; - } - case ConfigTypes::Amnezia: { - m_config = QJsonDocument::fromJson(config.toUtf8()).object(); - - if (apiUtils::isServerFromApi(m_config)) { - auto apiConfig = m_config.value(apiDefs::key::apiConfig).toObject(); - apiConfig[apiDefs::key::vpnKey] = data; - m_config[apiDefs::key::apiConfig] = apiConfig; - } - - processAmneziaConfig(m_config); - if (!m_config.empty()) { - checkForMaliciousStrings(m_config); - return true; - } - return false; - } - case ConfigTypes::Backup: { - if (!m_serversModel->getServersCount()) { - emit restoreAppConfig(config.toUtf8()); - } else { - emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false); - } - break; - } - case ConfigTypes::Invalid: { - emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false); - m_configFileName.clear(); - break; - } - } - return false; -} - -bool ImportController::extractConfigFromQr(const QByteArray &data) -{ - m_configType = checkConfigFormat(QString::fromUtf8(data)); - - QJsonObject dataObj = QJsonDocument::fromJson(data).object(); - if (!dataObj.isEmpty()) { - m_config = dataObj; - return true; - } - - QByteArray ba_uncompressed = qUncompress(data); - if (!ba_uncompressed.isEmpty()) { - m_config = QJsonDocument::fromJson(ba_uncompressed).object(); - if (m_config.isEmpty()) { - return false; - } - m_configType = checkConfigFormat(QString::fromUtf8(ba_uncompressed)); - return true; - } - - if (m_configType == ConfigTypes::Invalid) { - QByteArray ba = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QByteArray baUncompressed = qUncompress(ba); - - if (!baUncompressed.isEmpty()) { - ba = baUncompressed; - } - - if (!ba.isEmpty()) { - m_config = QJsonDocument::fromJson(ba).object(); - if (m_config.isEmpty()) { - return false; - } - m_configType = checkConfigFormat(QString::fromUtf8(ba)); - return true; - } - } - - return false; -} - -QString ImportController::getConfig() -{ - return QJsonDocument(m_config).toJson(QJsonDocument::Indented); -} - -QString ImportController::getConfigFileName() -{ - return m_configFileName; -} - -QString ImportController::getMaliciousWarningText() -{ - return m_maliciousWarningText; -} - -bool ImportController::isNativeWireGuardConfig() -{ - return m_configType == ConfigTypes::WireGuard; -} - -void ImportController::processNativeWireGuardConfig() -{ - auto containers = m_config.value(config_key::containers).toArray(); - if (!containers.isEmpty()) { - auto container = containers.at(0).toObject(); - auto serverProtocolConfig = container.value(ContainerProps::containerTypeToProtocolString(DockerContainer::WireGuard)).toObject(); - auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object(); - - QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7)); - QString junkPacketMinSize = QString::number(10); - QString junkPacketMaxSize = QString::number(50); - clientProtocolConfig[config_key::junkPacketCount] = junkPacketCount; - clientProtocolConfig[config_key::junkPacketMinSize] = junkPacketMinSize; - clientProtocolConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize; - clientProtocolConfig[config_key::initPacketJunkSize] = "0"; - clientProtocolConfig[config_key::responsePacketJunkSize] = "0"; - clientProtocolConfig[config_key::initPacketMagicHeader] = "1"; - clientProtocolConfig[config_key::responsePacketMagicHeader] = "2"; - clientProtocolConfig[config_key::underloadPacketMagicHeader] = "3"; - clientProtocolConfig[config_key::transportPacketMagicHeader] = "4"; - - clientProtocolConfig[config_key::cookieReplyPacketJunkSize] = "0"; - clientProtocolConfig[config_key::transportPacketJunkSize] = "0"; - - clientProtocolConfig[config_key::specialJunk1] = protocols::awg::defaultSpecialJunk1; - - clientProtocolConfig[config_key::isObfuscationEnabled] = true; - - serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(clientProtocolConfig).toJson()); - container["wireguard"] = serverProtocolConfig; - containers.replace(0, container); - m_config[config_key::containers] = containers; - } -} - -void ImportController::importConfig() -{ - ServerCredentials credentials; - credentials.hostName = m_config.value(config_key::hostName).toString(); - credentials.port = m_config.value(config_key::port).toInt(); - credentials.userName = m_config.value(config_key::userName).toString(); - credentials.secretData = m_config.value(config_key::password).toString(); - - if (credentials.isValid() || m_config.contains(config_key::containers)) { - m_serversModel->addServer(m_config); - emit importFinished(); - } else if (m_config.contains(config_key::configVersion)) { - quint16 crc = qChecksum(QJsonDocument(m_config).toJson()); - if (m_serversModel->isServerFromApiAlreadyExists(crc)) { - emit importErrorOccurred(ErrorCode::ApiConfigAlreadyAdded, true); - } else { - m_config.insert(config_key::crc, crc); - - m_serversModel->addServer(m_config); - emit importFinished(); - } - } else { - qDebug() << "Failed to import profile"; - qDebug().noquote() << QJsonDocument(m_config).toJson(); - emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false); - } - - m_config = {}; - m_configFileName.clear(); - m_maliciousWarningText.clear(); -} - -void ImportController::clearConfigFileName() -{ - m_configFileName.clear(); -} - -QJsonObject ImportController::extractOpenVpnConfig(const QString &data) -{ - QJsonObject openVpnConfig; - openVpnConfig[config_key::config] = data; - - QJsonObject lastConfig; - lastConfig[config_key::last_config] = QString(QJsonDocument(openVpnConfig).toJson()); - lastConfig[config_key::isThirdPartyConfig] = true; - - QJsonObject containers; - containers.insert(config_key::container, QJsonValue("amnezia-openvpn")); - containers.insert(config_key::openvpn, QJsonValue(lastConfig)); - - QJsonArray arr; - arr.push_back(containers); - - QString hostName; - const static QRegularExpression hostNameRegExp("remote\\s+([^\\s]+)"); - QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data); - if (hostNameMatch.hasMatch()) { - hostName = hostNameMatch.captured(1); - } - - QJsonObject config; - config[config_key::containers] = arr; - config[config_key::defaultContainer] = "amnezia-openvpn"; - config[config_key::description] = m_settings->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); - if (dnsMatch.hasNext()) { - config[config_key::dns1] = dnsMatch.next().captured(1); - } - if (dnsMatch.hasNext()) { - config[config_key::dns2] = dnsMatch.next().captured(1); - } - - config[config_key::hostName] = hostName; - - return config; -} - -QJsonObject ImportController::extractWireGuardConfig(const QString &data) -{ - QMap configMap; - auto configByLines = data.split("\n"); - for (const QString &line : configByLines) { - QString trimmedLine = line.trimmed(); - if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { - continue; - } else { - QStringList parts = trimmedLine.split(" = "); - if (parts.count() == 2) { - configMap[parts.at(0).trimmed()] = parts.at(1).trimmed(); - } - } - } - - QJsonObject lastConfig; - lastConfig[config_key::config] = data; - - auto url { QUrl::fromUserInput(configMap.value("Endpoint")) }; - QString hostName; - QString port; - if (!url.host().isEmpty()) { - hostName = url.host(); - } else { - qDebug() << "Key parameter 'Endpoint' is missing or has an invalid format"; - emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false); - return QJsonObject(); - } - - if (url.port() != -1) { - port = QString::number(url.port()); - } else { - port = protocols::wireguard::defaultPort; - } - - lastConfig[config_key::hostName] = hostName; - lastConfig[config_key::port] = port.toInt(); - - if (!configMap.value("PrivateKey").isEmpty() && !configMap.value("Address").isEmpty() && !configMap.value("PublicKey").isEmpty()) { - lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey"); - lastConfig[config_key::client_ip] = configMap.value("Address"); - - if (!configMap.value("PresharedKey").isEmpty()) { - lastConfig[config_key::psk_key] = configMap.value("PresharedKey"); - } else if (!configMap.value("PreSharedKey").isEmpty()) { - lastConfig[config_key::psk_key] = configMap.value("PreSharedKey"); - } - - lastConfig[config_key::server_pub_key] = configMap.value("PublicKey"); - } else { - qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)"; - emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false); - return QJsonObject(); - } - - if (!configMap.value("MTU").isEmpty()) { - lastConfig[config_key::mtu] = configMap.value("MTU"); - } - - if (!configMap.value("PersistentKeepalive").isEmpty()) { - lastConfig[config_key::persistent_keep_alive] = configMap.value("PersistentKeepalive"); - } - - QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configMap.value("AllowedIPs").split(", ")); - - lastConfig[config_key::allowed_ips] = allowedIpsJsonArray; - - QString protocolName = "wireguard"; - QString protocolVersion; - - const QStringList requiredJunkFields = { config_key::junkPacketCount, config_key::junkPacketMinSize, - config_key::junkPacketMaxSize, config_key::initPacketJunkSize, - config_key::responsePacketJunkSize, config_key::initPacketMagicHeader, - config_key::responsePacketMagicHeader, config_key::underloadPacketMagicHeader, - config_key::transportPacketMagicHeader }; - - const QStringList optionalJunkFields = { config_key::cookieReplyPacketJunkSize, - config_key::transportPacketJunkSize, - config_key::specialJunk1, config_key::specialJunk2, config_key::specialJunk3, - config_key::specialJunk4, config_key::specialJunk5 - }; - - bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(), - [&configMap](const QString &field) { return !configMap.value(field).isEmpty(); }); - if (hasAllRequiredFields) { - for (const QString &field : requiredJunkFields) { - lastConfig[field] = configMap.value(field); - } - - for (const QString &field : optionalJunkFields) { - if (!configMap.value(field).isEmpty()) { - lastConfig[field] = configMap.value(field); - } - } - - bool hasCookieReplyPacketJunkSize = !configMap.value(config_key::cookieReplyPacketJunkSize).isEmpty(); - bool hasTransportPacketJunkSize = !configMap.value(config_key::transportPacketJunkSize).isEmpty(); - bool hasSpecialJunk = !configMap.value(config_key::specialJunk1).isEmpty() || - !configMap.value(config_key::specialJunk2).isEmpty() || - !configMap.value(config_key::specialJunk3).isEmpty() || - !configMap.value(config_key::specialJunk4).isEmpty() || - !configMap.value(config_key::specialJunk5).isEmpty(); - - if (hasCookieReplyPacketJunkSize && hasTransportPacketJunkSize) { - protocolVersion = "2"; - } else if (hasSpecialJunk && !hasCookieReplyPacketJunkSize && !hasTransportPacketJunkSize) { - protocolVersion = "1.5"; - } - protocolName = "awg"; - m_configType = ConfigTypes::Awg; - } - - if (!configMap.value("MTU").isEmpty()) { - lastConfig[config_key::mtu] = configMap.value("MTU"); - } else { - lastConfig[config_key::mtu] = (protocolName == "awg") - ? protocols::awg::defaultMtu - : protocols::wireguard::defaultMtu; - } - - QJsonObject wireguardConfig; - wireguardConfig[config_key::last_config] = QString(QJsonDocument(lastConfig).toJson()); - wireguardConfig[config_key::isThirdPartyConfig] = true; - wireguardConfig[config_key::port] = port; - wireguardConfig[config_key::transport_proto] = "udp"; - if (protocolName == "awg" && !protocolVersion.isEmpty()) { - wireguardConfig[config_key::protocolVersion] = protocolVersion; - } - - QJsonObject containers; - containers.insert(config_key::container, QJsonValue("amnezia-" + protocolName)); - containers.insert(protocolName, QJsonValue(wireguardConfig)); - - QJsonArray arr; - arr.push_back(containers); - - QJsonObject config; - config[config_key::containers] = arr; - config[config_key::defaultContainer] = "amnezia-" + protocolName; - config[config_key::description] = m_settings->nextAvailableServerName(); - - const static QRegularExpression dnsRegExp( - "DNS = " - "(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); - QRegularExpressionMatch dnsMatch = dnsRegExp.match(data); - if (dnsMatch.hasMatch()) { - config[config_key::dns1] = dnsMatch.captured(1); - config[config_key::dns2] = dnsMatch.captured(2); - } - - config[config_key::hostName] = hostName; - - return config; -} - -QJsonObject ImportController::extractXrayConfig(const QString &data, const QString &description) -{ - QJsonParseError parserErr; - QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr); - - QJsonObject xrayVpnConfig; - xrayVpnConfig[config_key::config] = jsonConf.toJson().constData(); - QJsonObject lastConfig; - lastConfig[config_key::last_config] = jsonConf.toJson().constData(); - lastConfig[config_key::isThirdPartyConfig] = true; - - QJsonObject containers; - if (m_configType == ConfigTypes::ShadowSocks) { - containers.insert(config_key::ssxray, QJsonValue(lastConfig)); - containers.insert(config_key::container, QJsonValue("amnezia-ssxray")); - } else { - containers.insert(config_key::container, QJsonValue("amnezia-xray")); - containers.insert(config_key::xray, QJsonValue(lastConfig)); - } - - QJsonArray arr; - arr.push_back(containers); - - QString hostName; - - const static QRegularExpression hostNameRegExp("\"address\":\\s*\"([^\"]+)"); - QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data); - if (hostNameMatch.hasMatch()) { - hostName = hostNameMatch.captured(1); - } - - QJsonObject config; - config[config_key::containers] = arr; - - if (m_configType == ConfigTypes::ShadowSocks) { - config[config_key::defaultContainer] = "amnezia-ssxray"; - } else { - config[config_key::defaultContainer] = "amnezia-xray"; - } - if (description.isEmpty()) { - config[config_key::description] = m_settings->nextAvailableServerName(); - } else { - config[config_key::description] = description; - } - config[config_key::hostName] = hostName; - - return config; -} - -#ifdef Q_OS_ANDROID -static QMutex qrDecodeMutex; - -// static -bool ImportController::decodeQrCode(const QString &code) -{ - QMutexLocker lock(&qrDecodeMutex); - - if (!mInstance->m_isQrCodeProcessed) { - mInstance->m_qrCodeChunks.clear(); - mInstance->m_isQrCodeProcessed = true; - mInstance->m_totalQrCodeChunksCount = 0; - mInstance->m_receivedQrCodeChunksCount = 0; - } - return mInstance->parseQrCodeChunk(code); -} -#endif - -#if defined Q_OS_ANDROID || defined Q_OS_IOS -void ImportController::startDecodingQr() -{ - m_qrCodeChunks.clear(); - m_totalQrCodeChunksCount = 0; - m_receivedQrCodeChunksCount = 0; - - #if defined(Q_OS_IOS) || defined(MACOS_NE) - m_isQrCodeProcessed = true; - #endif - #if defined Q_OS_ANDROID - AndroidController::instance()->startQrReaderActivity(); - #endif -} - -void ImportController::stopDecodingQr() -{ - emit qrDecodingFinished(); -} - -bool ImportController::parseQrCodeChunk(const QString &code) -{ - // qDebug() << code; - if (!m_isQrCodeProcessed) - return false; - - // check if chunk received - QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QDataStream s(&ba, QIODevice::ReadOnly); - qint16 magic; - s >> magic; - - if (magic == qrCodeUtils::qrMagicCode) { - quint8 chunksCount; - s >> chunksCount; - if (m_totalQrCodeChunksCount != chunksCount) { - m_qrCodeChunks.clear(); - } - - m_totalQrCodeChunksCount = chunksCount; - - quint8 chunkId; - s >> chunkId; - s >> m_qrCodeChunks[chunkId]; - m_receivedQrCodeChunksCount = m_qrCodeChunks.size(); - - if (m_qrCodeChunks.size() == m_totalQrCodeChunksCount) { - QByteArray data; - - for (int i = 0; i < m_totalQrCodeChunksCount; ++i) { - data.append(m_qrCodeChunks.value(i)); - } - - bool ok = extractConfigFromQr(data); - if (ok) { - m_isQrCodeProcessed = false; - qDebug() << "stopDecodingQr"; - stopDecodingQr(); - return true; - } else { - qDebug() << "error while extracting data from qr"; - m_qrCodeChunks.clear(); - m_totalQrCodeChunksCount = 0; - m_receivedQrCodeChunksCount = 0; - } - } - } else { - bool ok = extractConfigFromQr(ba); - if (ok) { - m_isQrCodeProcessed = false; - qDebug() << "stopDecodingQr"; - stopDecodingQr(); - return true; - } - } - return false; -} - -double ImportController::getQrCodeScanProgressBarValue() -{ - return (1.0 / m_totalQrCodeChunksCount) * m_receivedQrCodeChunksCount; -} - -QString ImportController::getQrCodeScanProgressString() -{ - return tr("Scanned %1 of %2.").arg(m_receivedQrCodeChunksCount).arg(m_totalQrCodeChunksCount); -} -#endif - -void ImportController::checkForMaliciousStrings(const QJsonObject &serverConfig) -{ - const QJsonArray &containers = serverConfig[config_key::containers].toArray(); - for (const QJsonValue &container : containers) { - auto containerConfig = container.toObject(); - auto containerName = containerConfig[config_key::container].toString(); - if ((containerName == ContainerProps::containerToString(DockerContainer::OpenVpn)) - || (containerName == ContainerProps::containerToString(DockerContainer::Cloak)) - || (containerName == ContainerProps::containerToString(DockerContainer::ShadowSocks))) { - - QString protocolConfig = - containerConfig[ProtocolProps::protoToString(Proto::OpenVpn)].toObject()[config_key::last_config].toString(); - QString protocolConfigJson = QJsonDocument::fromJson(protocolConfig.toUtf8()).object()[config_key::config].toString(); - - // https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/script-options.rst - QStringList dangerousTags { - "up", "tls-verify", "ipchange", "client-connect", "route-up", "route-pre-down", "client-disconnect", "down", "learn-address", "auth-user-pass-verify" - }; - - QStringList maliciousStrings; - QStringList lines = protocolConfigJson.split('\n', Qt::SkipEmptyParts); - - for (const QString &rawLine : lines) { - QString line = rawLine.trimmed(); - - QString command = line.section(' ', 0, 0, QString::SectionSkipEmpty); - if (dangerousTags.contains(command, Qt::CaseInsensitive)) { - maliciousStrings << rawLine; - } - } - - m_maliciousWarningText = tr("This configuration contains an OpenVPN setup. OpenVPN configurations can include malicious " - "scripts, so only add it if you fully trust the provider of this config. "); - - if (!maliciousStrings.isEmpty()) { - m_maliciousWarningText.push_back(tr("
In the imported configuration, potentially dangerous lines were found:")); - for (const auto &string : maliciousStrings) { - m_maliciousWarningText.push_back(QString("
%1").arg(string)); - } - } - } - } -} - -void ImportController::processAmneziaConfig(QJsonObject &config) -{ - auto containers = config.value(config_key::containers).toArray(); - for (auto i = 0; i < containers.size(); i++) { - auto container = containers.at(i).toObject(); - auto dockerContainer = ContainerProps::containerFromString(container.value(config_key::container).toString()); - if (ContainerProps::isAwgContainer(dockerContainer) || dockerContainer == DockerContainer::WireGuard) { - auto containerConfig = container.value(ContainerProps::containerTypeToProtocolString(dockerContainer)).toObject(); - auto protocolConfig = containerConfig.value(config_key::last_config).toString(); - if (protocolConfig.isEmpty()) { - return; - } - - QJsonObject jsonConfig = QJsonDocument::fromJson(protocolConfig.toUtf8()).object(); - jsonConfig[config_key::mtu] = - ContainerProps::isAwgContainer(dockerContainer) ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu; - - containerConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson()); - - container[ContainerProps::containerTypeToProtocolString(dockerContainer)] = containerConfig; - containers.replace(i, container); - config.insert(config_key::containers, containers); - } - } -} diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h deleted file mode 100644 index c2da0b8bd..000000000 --- a/client/ui/controllers/importController.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef IMPORTCONTROLLER_H -#define IMPORTCONTROLLER_H - -#include - -#include "ui/models/containers_model.h" -#include "ui/models/servers_model.h" - -namespace -{ - enum class ConfigTypes { - Amnezia, - OpenVpn, - WireGuard, - Awg, - Xray, - ShadowSocks, - Backup, - Invalid - }; -} - -class ImportController : public QObject -{ - Q_OBJECT -public: - explicit ImportController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const std::shared_ptr &settings, QObject *parent = nullptr); - -public slots: - void importConfig(); - void clearConfigFileName(); - bool extractConfigFromFile(const QString &fileName); - bool extractConfigFromData(QString data); - bool extractConfigFromQr(const QByteArray &data); - QString getConfig(); - QString getConfigFileName(); - QString getMaliciousWarningText(); - -#if defined Q_OS_ANDROID || defined Q_OS_IOS - void startDecodingQr(); - bool parseQrCodeChunk(const QString &code); - - double getQrCodeScanProgressBarValue(); - QString getQrCodeScanProgressString(); -#endif - -#if defined Q_OS_ANDROID - static bool decodeQrCode(const QString &code); -#endif - - bool isNativeWireGuardConfig(); - void processNativeWireGuardConfig(); - -signals: - void importFinished(); - void importErrorOccurred(ErrorCode errorCode, bool goToPageHome); - - void qrDecodingFinished(); - - void restoreAppConfig(const QByteArray &data); - -private: - QJsonObject extractOpenVpnConfig(const QString &data); - QJsonObject extractWireGuardConfig(const QString &data); - QJsonObject extractXrayConfig(const QString &data, const QString &description = ""); - - void checkForMaliciousStrings(const QJsonObject &protocolConfig); - - void processAmneziaConfig(QJsonObject &config); - -#if defined Q_OS_ANDROID || defined Q_OS_IOS - void stopDecodingQr(); -#endif - - QSharedPointer m_serversModel; - QSharedPointer m_containersModel; - std::shared_ptr m_settings; - - QJsonObject m_config; - QString m_configFileName; - ConfigTypes m_configType; - QString m_maliciousWarningText; - -#if defined Q_OS_ANDROID || defined Q_OS_IOS - QMap m_qrCodeChunks; - bool m_isQrCodeProcessed; - int m_totalQrCodeChunksCount; - int m_receivedQrCodeChunksCount; -#endif -}; - -#endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/importUiController.cpp b/client/ui/controllers/importUiController.cpp new file mode 100644 index 000000000..ce9b952c1 --- /dev/null +++ b/client/ui/controllers/importUiController.cpp @@ -0,0 +1,203 @@ +#include "importUiController.h" + +#include +#include +#include +#include +#include + +#include "systemController.h" + +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" +#endif + +#if defined Q_OS_ANDROID +ImportUiController* ImportUiController::mInstance = nullptr; +static QMutex qrDecodeMutex; +#endif + +ImportUiController::ImportUiController(ImportController* importController, QObject *parent) + : QObject(parent), + m_importController(importController), + m_isNativeWireGuardConfig(false) +{ +#if defined Q_OS_ANDROID + mInstance = this; +#endif + + connect(m_importController, &ImportController::importFinished, this, &ImportUiController::importFinished); + connect(m_importController, &ImportController::importErrorOccurred, this, &ImportUiController::importErrorOccurred); + connect(m_importController, &ImportController::restoreAppConfig, this, &ImportUiController::restoreAppConfig); +} + +bool ImportUiController::extractConfigFromFile(const QString &fileName) +{ + QString data; + if (!SystemController::readFile(fileName, data)) { + emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false); + return false; + } + + QString configFileName = QFileInfo(QFile(fileName).fileName()).fileName(); +#ifdef Q_OS_ANDROID + if (configFileName.isEmpty()) { + configFileName = AndroidController::instance()->getFileName(fileName); + } +#endif + + auto result = m_importController->extractConfigFromData(data, configFileName); + + if (result.errorCode != ErrorCode::NoError) { + emit importErrorOccurred(result.errorCode, false); + return false; + } + + m_config = result.config; + m_configFileName = result.configFileName; + m_maliciousWarningText = result.maliciousWarningText; + m_isNativeWireGuardConfig = result.isNativeWireGuardConfig; + + emit importConfigChanged(); + return true; +} + +bool ImportUiController::extractConfigFromData(QString data) +{ + auto result = m_importController->extractConfigFromData(data); + + if (result.errorCode != ErrorCode::NoError) { + emit importErrorOccurred(result.errorCode, false); + return false; + } + + m_config = result.config; + m_configFileName = result.configFileName; + m_maliciousWarningText = result.maliciousWarningText; + m_isNativeWireGuardConfig = result.isNativeWireGuardConfig; + + emit importConfigChanged(); + return true; +} + +bool ImportUiController::extractConfigFromQr(const QByteArray &data) +{ + auto result = m_importController->extractConfigFromQr(data); + + if (result.errorCode != ErrorCode::NoError) { + emit importErrorOccurred(result.errorCode, false); + return false; + } + + m_config = result.config; + m_configFileName = result.configFileName; + m_maliciousWarningText = result.maliciousWarningText; + m_isNativeWireGuardConfig = result.isNativeWireGuardConfig; + + emit importConfigChanged(); + return true; +} + +QString ImportUiController::getConfig() +{ + return QJsonDocument(m_config).toJson(QJsonDocument::Indented); +} + +QString ImportUiController::getConfigFileName() +{ + return m_configFileName; +} + +QString ImportUiController::getMaliciousWarningText() +{ + return m_maliciousWarningText; +} + +bool ImportUiController::isNativeWireGuardConfig() +{ + return m_isNativeWireGuardConfig; +} + +void ImportUiController::processNativeWireGuardConfig() +{ + m_config = m_importController->processNativeWireGuardConfig(m_config); + emit importConfigChanged(); +} + +void ImportUiController::importConfig() +{ + m_importController->importConfig(m_config); + + m_config = {}; + m_configFileName.clear(); + m_maliciousWarningText.clear(); + m_isNativeWireGuardConfig = false; + + emit importConfigChanged(); +} + +void ImportUiController::clearConfigFileName() +{ + m_configFileName.clear(); + emit importConfigChanged(); +} + +#if defined Q_OS_ANDROID || defined Q_OS_IOS +void ImportUiController::startDecodingQr() +{ + m_importController->startDecodingQr(); +#if defined Q_OS_ANDROID + AndroidController::instance()->startQrReaderActivity(); +#endif +} + +void ImportUiController::stopDecodingQr() +{ + emit qrDecodingFinished(); +} + +bool ImportUiController::parseQrCodeChunk(const QString &code) +{ + auto parseResult = m_importController->parseQrCodeChunk(code); + if (parseResult.success) { + m_config = parseResult.importResult.config; + m_configFileName = parseResult.importResult.configFileName; + m_maliciousWarningText = parseResult.importResult.maliciousWarningText; + m_isNativeWireGuardConfig = parseResult.importResult.isNativeWireGuardConfig; + emit importConfigChanged(); + stopDecodingQr(); + return true; + } + return false; +} + +double ImportUiController::getQrCodeScanProgressBarValue() +{ + const int total = m_importController->qrChunksTotal(); + if (total == 0) { + return 0.0; + } + return (1.0 / total) * m_importController->qrChunksReceived(); +} + +QString ImportUiController::getQrCodeScanProgressString() +{ + return tr("Scanned %1 of %2.").arg(m_importController->qrChunksReceived()).arg(m_importController->qrChunksTotal()); +} +#endif + +#if defined Q_OS_ANDROID +bool ImportUiController::decodeQrCode(const QString &code) +{ + QMutexLocker lock(&qrDecodeMutex); + + if (!mInstance) { + return false; + } + + if (!mInstance->m_importController->isQrDecodingActive()) { + mInstance->m_importController->startDecodingQr(); + } + return mInstance->parseQrCodeChunk(code); +} +#endif diff --git a/client/ui/controllers/importUiController.h b/client/ui/controllers/importUiController.h new file mode 100644 index 000000000..853539d05 --- /dev/null +++ b/client/ui/controllers/importUiController.h @@ -0,0 +1,68 @@ +#ifndef IMPORTUICONTROLLER_H +#define IMPORTUICONTROLLER_H + +#include + +#include "core/controllers/selfhosted/importController.h" + +class ImportUiController : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString config READ getConfig NOTIFY importConfigChanged) + Q_PROPERTY(QString configFileName READ getConfigFileName NOTIFY importConfigChanged) + Q_PROPERTY(QString maliciousWarningText READ getMaliciousWarningText NOTIFY importConfigChanged) + Q_PROPERTY(bool isNativeWireGuardConfig READ isNativeWireGuardConfig NOTIFY importConfigChanged) + +public: + explicit ImportUiController(ImportController* importController, QObject *parent = nullptr); + +public slots: + void importConfig(); + void clearConfigFileName(); + bool extractConfigFromFile(const QString &fileName); + bool extractConfigFromData(QString data); + bool extractConfigFromQr(const QByteArray &data); + QString getConfig(); + QString getConfigFileName(); + QString getMaliciousWarningText(); + bool isNativeWireGuardConfig(); + void processNativeWireGuardConfig(); + +#if defined Q_OS_ANDROID || defined Q_OS_IOS + void startDecodingQr(); + bool parseQrCodeChunk(const QString &code); + + double getQrCodeScanProgressBarValue(); + QString getQrCodeScanProgressString(); +#endif + +#if defined Q_OS_ANDROID + static bool decodeQrCode(const QString &code); +#endif + +signals: + void importFinished(); + void importErrorOccurred(ErrorCode errorCode, bool goToPageHome); + void qrDecodingFinished(); + void restoreAppConfig(const QByteArray &data); + void importConfigChanged(); + +private: +#if defined Q_OS_ANDROID || defined Q_OS_IOS + void stopDecodingQr(); +#endif + + ImportController* m_importController; + + QJsonObject m_config; + QString m_configFileName; + QString m_maliciousWarningText; + bool m_isNativeWireGuardConfig; + +#if defined Q_OS_ANDROID + static ImportUiController* mInstance; +#endif +}; + +#endif // IMPORTUICONTROLLER_H diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp deleted file mode 100644 index 8e1e198f2..000000000 --- a/client/ui/controllers/installController.cpp +++ /dev/null @@ -1,1105 +0,0 @@ -#include "installController.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/api/apiUtils.h" -#include "core/controllers/serverController.h" -#include "core/controllers/vpnConfigurationController.h" -#include "core/networkUtilities.h" -#include "logger.h" -#include "ui/models/protocols/awgConfigModel.h" -#include "ui/models/protocols/wireguardConfigModel.h" -#include "utilities.h" - -namespace -{ - Logger logger("ServerController"); - - namespace configKey - { - constexpr char serviceInfo[] = "service_info"; - constexpr char serviceType[] = "service_type"; - constexpr char serviceProtocol[] = "service_protocol"; - constexpr char userCountryCode[] = "user_country_code"; - - constexpr char serverCountryCode[] = "server_country_code"; - constexpr char serverCountryName[] = "server_country_name"; - constexpr char availableCountries[] = "available_countries"; - - constexpr char apiConfig[] = "api_config"; - constexpr char authData[] = "auth_data"; - } -} - -InstallController::InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &protocolsModel, - const QSharedPointer &clientManagementModel, - const std::shared_ptr &settings, QObject *parent) - : QObject(parent), - m_serversModel(serversModel), - m_containersModel(containersModel), - m_protocolModel(protocolsModel), - m_clientManagementModel(clientManagementModel), - m_settings(settings) -{ -} - -InstallController::~InstallController() -{ -#ifdef Q_OS_WINDOWS - for (QSharedPointer process : m_sftpMountProcesses) { - Utils::signalCtrl(process->processId(), CTRL_C_EVENT); - process->kill(); - process->waitForFinished(); - } -#endif -} - -void InstallController::install(DockerContainer container, int port, TransportProto transportProto) -{ - QJsonObject config; - auto mainProto = ContainerProps::defaultProtocol(container); - for (auto protocol : ContainerProps::protocolsForContainer(container)) { - QJsonObject containerConfig; - - if (protocol == mainProto) { - containerConfig.insert(config_key::port, QString::number(port)); - containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, protocol)); - containerConfig.insert(config_key::subnet_address, protocols::wireguard::defaultSubnetAddress); - - if (container == DockerContainer::Awg2) { - containerConfig[config_key::protocolVersion] = "2"; - - QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7)); - QString junkPacketMinSize = QString::number(10); - QString junkPacketMaxSize = QString::number(50); - - int s1 = QRandomGenerator::global()->bounded(15, 150); - int s2 = QRandomGenerator::global()->bounded(15, 150); - int s3 = QRandomGenerator::global()->bounded(1, 64); - int s4 = QRandomGenerator::global()->bounded(1, 20); - - // Ensure all values are unique and don't create equal packet sizes - QSet usedValues; - usedValues.insert(s1); - - while (usedValues.contains(s2) || s1 + AwgConstant::messageInitiationSize == s2 + AwgConstant::messageResponseSize) { - s2 = QRandomGenerator::global()->bounded(15, 150); - } - usedValues.insert(s2); - - while (usedValues.contains(s3) || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize - || s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) { - s3 = QRandomGenerator::global()->bounded(1, 64); - } - usedValues.insert(s3); - - while (usedValues.contains(s4)) { - s4 = QRandomGenerator::global()->bounded(1, 20); - } - - QString initPacketJunkSize = QString::number(s1); - QString responsePacketJunkSize = QString::number(s2); - QString cookieReplyPacketJunkSize = QString::number(s3); - QString transportPacketJunkSize = QString::number(s4); - - QVector> headersValue; - int min = 5; - auto max = (std::numeric_limits::max)(); - while (headersValue.size() != 4) { - auto first = QRandomGenerator::global()->bounded(min, max); - auto second = QRandomGenerator::global()->bounded(first, max); - min = second; - - headersValue.push_back(QPair(QString::number(first), QString::number(second))); - } - - QString initPacketMagicHeader = headersValue.at(0).first + "-" + headersValue.at(0).second; - QString responsePacketMagicHeader = headersValue.at(1).first + "-" + headersValue.at(1).second; - QString underloadPacketMagicHeader = headersValue.at(2).first + "-" + headersValue.at(2).second; - QString transportPacketMagicHeader = headersValue.at(3).first + "-" + headersValue.at(3).second; - - containerConfig[config_key::junkPacketCount] = junkPacketCount; - containerConfig[config_key::junkPacketMinSize] = junkPacketMinSize; - containerConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize; - containerConfig[config_key::initPacketJunkSize] = initPacketJunkSize; - containerConfig[config_key::responsePacketJunkSize] = responsePacketJunkSize; - containerConfig[config_key::initPacketMagicHeader] = initPacketMagicHeader; - containerConfig[config_key::responsePacketMagicHeader] = responsePacketMagicHeader; - containerConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader; - containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; - - containerConfig[config_key::cookieReplyPacketJunkSize] = cookieReplyPacketJunkSize; - containerConfig[config_key::transportPacketJunkSize] = transportPacketJunkSize; - - containerConfig[config_key::specialJunk1] = protocols::awg::defaultSpecialJunk1; - containerConfig[config_key::specialJunk2] = protocols::awg::defaultSpecialJunk2; - containerConfig[config_key::specialJunk3] = protocols::awg::defaultSpecialJunk3; - containerConfig[config_key::specialJunk4] = protocols::awg::defaultSpecialJunk4; - containerConfig[config_key::specialJunk5] = protocols::awg::defaultSpecialJunk5; - - } else if (container == DockerContainer::Sftp) { - containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); - containerConfig.insert(config_key::password, Utils::getRandomString(16)); - } else if (container == DockerContainer::Socks5Proxy) { - containerConfig.insert(config_key::userName, protocols::socks5Proxy::defaultUserName); - containerConfig.insert(config_key::password, Utils::getRandomString(16)); - } - - config.insert(config_key::container, ContainerProps::containerToString(container)); - } - config.insert(ProtocolProps::protoToString(protocol), containerConfig); - } - - ServerCredentials serverCredentials; - if (m_shouldCreateServer) { - if (isServerAlreadyExists()) { - return; - } - serverCredentials = m_processedServerCredentials; - } else { - int serverIndex = m_serversModel->getProcessedServerIndex(); - serverCredentials = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); - } - - QSharedPointer serverController(new ServerController(m_settings)); - connect(serverController.get(), &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); - connect(this, &InstallController::cancelInstallation, serverController.get(), &ServerController::cancelInstallation); - - QMap installedContainers; - ErrorCode errorCode = getAlreadyInstalledContainers(serverCredentials, serverController, installedContainers); - if (errorCode) { - emit installationErrorOccurred(errorCode); - return; - } - - QString finishMessage = ""; - - if (!installedContainers.contains(container)) { - errorCode = serverController->setupContainer(serverCredentials, container, config); - if (errorCode) { - emit installationErrorOccurred(errorCode); - return; - } - - installedContainers.insert(container, config); - finishMessage = tr("%1 installed successfully. ").arg(ContainerProps::containerHumanNames().value(container)); - } else { - finishMessage = tr("%1 is already installed on the server. ").arg(ContainerProps::containerHumanNames().value(container)); - } - - if (errorCode) { - emit installationErrorOccurred(errorCode); - return; - } - - if (m_shouldCreateServer) { - installServer(container, installedContainers, serverCredentials, serverController, finishMessage); - } else { - installContainer(container, installedContainers, serverCredentials, serverController, finishMessage); - } -} - -void InstallController::installServer(const DockerContainer container, const QMap &installedContainers, - const ServerCredentials &serverCredentials, const QSharedPointer &serverController, - QString &finishMessage) -{ - if (installedContainers.size() > 1) { - finishMessage += tr("\nAdded containers that were already installed on the server"); - } - - QJsonObject server; - server.insert(config_key::hostName, m_processedServerCredentials.hostName); - server.insert(config_key::userName, m_processedServerCredentials.userName); - server.insert(config_key::password, m_processedServerCredentials.secretData); - server.insert(config_key::port, m_processedServerCredentials.port); - server.insert(config_key::description, m_settings->nextAvailableServerName()); - - QJsonArray containerConfigs; - VpnConfigurationsController vpnConfigurationController(m_settings, serverController); - for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { - auto containerConfig = iterator.value(); - - if (ContainerProps::isSupportedByCurrentPlatform(container)) { - auto errorCode = vpnConfigurationController.createProtocolConfigForContainer(m_processedServerCredentials, iterator.key(), - containerConfig); - if (errorCode) { - emit installationErrorOccurred(errorCode); - return; - } - containerConfigs.append(containerConfig); - - errorCode = m_clientManagementModel->appendClient(iterator.key(), serverCredentials, containerConfig, - QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController); - if (errorCode) { - emit installationErrorOccurred(errorCode); - return; - } - } else { - containerConfigs.append(containerConfig); - } - } - - server.insert(config_key::containers, containerConfigs); - server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - - m_serversModel->addServer(server); - - emit installServerFinished(finishMessage); -} - -void InstallController::installContainer(const DockerContainer container, const QMap &installedContainers, - const ServerCredentials &serverCredentials, - const QSharedPointer &serverController, QString &finishMessage) -{ - bool isInstalledContainerAddedToGui = false; - - VpnConfigurationsController vpnConfigurationController(m_settings, serverController); - for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { - QJsonObject containerConfig = m_containersModel->getContainerConfig(iterator.key()); - if (containerConfig.isEmpty()) { - containerConfig = iterator.value(); - - if (ContainerProps::isSupportedByCurrentPlatform(container)) { - auto errorCode = - vpnConfigurationController.createProtocolConfigForContainer(serverCredentials, iterator.key(), containerConfig); - if (errorCode) { - emit installationErrorOccurred(errorCode); - return; - } - m_serversModel->addContainerConfig(iterator.key(), containerConfig); - - errorCode = m_clientManagementModel->appendClient(iterator.key(), serverCredentials, containerConfig, - QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController); - if (errorCode) { - emit installationErrorOccurred(errorCode); - return; - } - } else { - m_serversModel->addContainerConfig(iterator.key(), containerConfig); - } - - if (container != iterator.key()) { // skip the newly installed container - isInstalledContainerAddedToGui = true; - } - } - } - if (isInstalledContainerAddedToGui) { - finishMessage += tr("\nAlready installed containers were found on the server. " - "All installed containers have been added to the application"); - } - - emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other); -} - -bool InstallController::isServerAlreadyExists() -{ - for (int i = 0; i < m_serversModel->getServersCount(); i++) { - auto modelIndex = m_serversModel->index(i); - const ServerCredentials credentials = - qvariant_cast(m_serversModel->data(modelIndex, ServersModel::Roles::CredentialsRole)); - if (m_processedServerCredentials.hostName == credentials.hostName && m_processedServerCredentials.port == credentials.port) { - emit serverAlreadyExists(i); - return true; - } - } - return false; -} - -void InstallController::scanServerForInstalledContainers() -{ - int serverIndex = m_serversModel->getProcessedServerIndex(); - ServerCredentials serverCredentials = - qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); - - QMap installedContainers; - QSharedPointer serverController(new ServerController(m_settings)); - ErrorCode errorCode = getAlreadyInstalledContainers(serverCredentials, serverController, installedContainers); - - if (errorCode == ErrorCode::NoError) { - bool isInstalledContainerAddedToGui = false; - VpnConfigurationsController vpnConfigurationController(m_settings, serverController); - - for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { - auto container = iterator.key(); - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - if (containerConfig.isEmpty()) { - containerConfig = iterator.value(); - - if (ContainerProps::isSupportedByCurrentPlatform(container)) { - auto errorCode = - vpnConfigurationController.createProtocolConfigForContainer(serverCredentials, container, containerConfig); - if (errorCode) { - emit installationErrorOccurred(errorCode); - return; - } - m_serversModel->addContainerConfig(container, containerConfig); - - errorCode = m_clientManagementModel->appendClient(container, serverCredentials, containerConfig, - QString("Admin [%1]").arg(QSysInfo::prettyProductName()), - serverController); - if (errorCode) { - emit installationErrorOccurred(errorCode); - return; - } - } else { - m_serversModel->addContainerConfig(container, containerConfig); - } - - isInstalledContainerAddedToGui = true; - } - } - - emit scanServerFinished(isInstalledContainerAddedToGui); - return; - } - - emit installationErrorOccurred(errorCode); -} - -ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentials &credentials, - const QSharedPointer &serverController, - QMap &installedContainers) -{ - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - auto cbReadStdErr = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'"); - - ErrorCode errorCode = serverController->runScript(credentials, script, cbReadStdOut, cbReadStdErr); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - auto containersInfo = stdOut.split("\n"); - for (auto &containerInfo : containersInfo) { - if (containerInfo.isEmpty()) { - continue; - } - const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z0-9]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*"); - QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo); - if (containerAndPortMatch.hasMatch()) { - QString name = containerAndPortMatch.captured(1); - QString port = containerAndPortMatch.captured(2); - QString transportProto = containerAndPortMatch.captured(3); - DockerContainer container = ContainerProps::containerFromString(name); - - QJsonObject config; - Proto mainProto = ContainerProps::defaultProtocol(container); - const auto &protocols = ContainerProps::protocolsForContainer(container); - - for (const auto &protocol : protocols) { - QJsonObject containerConfig; - - // for Multiprotocols (OpenVPN over SS, OpenVPN over Cloak) - bool shouldProcessProtocol = false; - if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - shouldProcessProtocol = true; - } else { - shouldProcessProtocol = (protocol == mainProto); - } - - if (shouldProcessProtocol) { - containerConfig.insert(config_key::port, port); - containerConfig.insert(config_key::transport_proto, transportProto); - - if (protocol == Proto::Awg) { - QString configPath = amnezia::protocols::awg::serverConfigPath; - if (container == DockerContainer::Awg) { - configPath = amnezia::protocols::awg::serverLegacyConfigPath; - } - QString serverConfig = serverController->getTextFileFromContainer(container, credentials, configPath, errorCode); - - QMap serverConfigMap; - auto serverConfigLines = serverConfig.split("\n"); - for (auto &line : serverConfigLines) { - auto trimmedLine = line.trimmed(); - if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { - continue; - } else { - QStringList parts = trimmedLine.split(" = "); - if (parts.count() == 2) { - serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed()); - } - } - } - - containerConfig[config_key::subnet_address] = serverConfigMap.value("Address").remove("/24"); - containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount); - containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize); - containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize); - containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize); - containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize); - containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader); - containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader); - containerConfig[config_key::underloadPacketMagicHeader] = - serverConfigMap.value(config_key::underloadPacketMagicHeader); - containerConfig[config_key::transportPacketMagicHeader] = - serverConfigMap.value(config_key::transportPacketMagicHeader); - - // hack to parse i1-i5 from commented lines in server config - containerConfig[config_key::specialJunk1] = serverConfigMap.value(QString("# ") + config_key::specialJunk1); - containerConfig[config_key::specialJunk2] = serverConfigMap.value(QString("# ") + config_key::specialJunk2); - containerConfig[config_key::specialJunk3] = serverConfigMap.value(QString("# ") + config_key::specialJunk3); - containerConfig[config_key::specialJunk4] = serverConfigMap.value(QString("# ") + config_key::specialJunk4); - containerConfig[config_key::specialJunk5] = serverConfigMap.value(QString("# ") + config_key::specialJunk5); - - if (container == DockerContainer::Awg2) { - containerConfig[config_key::protocolVersion] = "2"; - containerConfig[config_key::cookieReplyPacketJunkSize] = - serverConfigMap.value(config_key::cookieReplyPacketJunkSize); - containerConfig[config_key::transportPacketJunkSize] = serverConfigMap.value(config_key::transportPacketJunkSize); - } - - } else if (protocol == Proto::WireGuard) { - QString serverConfig = serverController->getTextFileFromContainer(container, credentials, - protocols::wireguard::serverConfigPath, errorCode); - - QMap serverConfigMap; - auto serverConfigLines = serverConfig.split("\n"); - for (auto &line : serverConfigLines) { - auto trimmedLine = line.trimmed(); - if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { - continue; - } else { - QStringList parts = trimmedLine.split(" = "); - if (parts.count() == 2) { - serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed()); - } - } - } - containerConfig[config_key::subnet_address] = serverConfigMap.value("Address").remove("/24"); - } else if (protocol == Proto::Sftp) { - stdOut.clear(); - script = QString("sudo docker inspect --format '{{.Config.Cmd}}' %1").arg(name); - - ErrorCode errorCode = serverController->runScript(credentials, script, cbReadStdOut, cbReadStdErr); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - auto sftpInfo = stdOut.split(":"); - if (sftpInfo.size() < 2) { - logger.error() << "Key parameters for the sftp container are missing"; - continue; - } - auto userName = sftpInfo.at(0); - userName = userName.remove(0, 1); - auto password = sftpInfo.at(1); - - containerConfig.insert(config_key::userName, userName); - containerConfig.insert(config_key::password, password); - } else if (protocol == Proto::Socks5Proxy) { - QString proxyConfig = serverController->getTextFileFromContainer(container, credentials, - protocols::socks5Proxy::proxyConfigPath, errorCode); - - const static QRegularExpression usernameAndPasswordRegExp("users (\\w+):CL:(\\w+)"); - QRegularExpressionMatch usernameAndPasswordMatch = usernameAndPasswordRegExp.match(proxyConfig); - - if (usernameAndPasswordMatch.hasMatch()) { - QString userName = usernameAndPasswordMatch.captured(1); - QString password = usernameAndPasswordMatch.captured(2); - - containerConfig.insert(config_key::userName, userName); - containerConfig.insert(config_key::password, password); - } - } else if (protocol == Proto::Xray) { - QString currentConfig = serverController->getTextFileFromContainer( - container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode); - - QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8()); - qDebug() << doc; - if (doc.isNull() || !doc.isObject()) { - logger.error() << "Failed to parse server config JSON"; - errorCode = ErrorCode::InternalError; - return errorCode; - } - QJsonObject serverConfig = doc.object(); - - if (!serverConfig.contains("inbounds")) { - logger.error() << "Server config missing 'inbounds' field"; - errorCode = ErrorCode::InternalError; - return errorCode; - } - - QJsonArray inbounds = serverConfig["inbounds"].toArray(); - if (inbounds.isEmpty()) { - logger.error() << "Server config has empty 'inbounds' array"; - errorCode = ErrorCode::InternalError; - return errorCode; - } - - QJsonObject inbound = inbounds[0].toObject(); - if (!inbound.contains("streamSettings")) { - logger.error() << "Inbound missing 'streamSettings' field"; - errorCode = ErrorCode::InternalError; - return errorCode; - } - - QJsonObject streamSettings = inbound["streamSettings"].toObject(); - QJsonObject realitySettings = streamSettings["realitySettings"].toObject(); - if (!realitySettings.contains("serverNames")) { - logger.error() << "Settings missing 'clients' field"; - errorCode = ErrorCode::InternalError; - return errorCode; - } - - QString siteName = realitySettings["serverNames"][0].toString(); - qDebug() << siteName; - - containerConfig.insert(config_key::site, siteName); - } else if (protocol == Proto::OpenVpn) { - QString serverConfig = serverController->getTextFileFromContainer(container, credentials, - protocols::openvpn::serverConfigPath, errorCode); - - QMap serverConfigMap; - auto serverConfigLines = serverConfig.split("\n"); - for (auto &line : serverConfigLines) { - auto trimmedLine = line.trimmed(); - if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) { - continue; - } else { - QStringList parts = trimmedLine.split(" "); - if (parts.count() >= 2) { - QString key = parts[0]; - QString value = parts.mid(1).join(" "); - serverConfigMap.insert(key, value); - } - } - } - - QString serverValue = serverConfigMap.value("server"); - - if (!serverValue.isEmpty()) { - QStringList serverParts = serverValue.split(" "); - if (serverParts.count() >= 1) { - containerConfig[config_key::subnet_address] = serverParts[0]; - } - } - - bool ncpDisable = serverConfig.contains("ncp-disable"); - containerConfig[config_key::ncp_disable] = ncpDisable; - - bool tlsAuth = serverConfig.contains("tls-auth"); - containerConfig[config_key::tls_auth] = tlsAuth; - - bool blockOutsideDns = serverConfig.contains("block-outside-dns"); - - containerConfig[config_key::block_outside_dns] = blockOutsideDns; - - QString cipher = serverConfigMap.value("cipher"); - if (!cipher.isEmpty()) { - containerConfig[config_key::cipher] = cipher; - } - - QString hash = serverConfigMap.value("auth"); - if (!hash.isEmpty()) { - containerConfig[config_key::hash] = hash; - } - } else if (protocol == Proto::Cloak) { - QString cloakConfig = serverController->getTextFileFromContainer(container, credentials, - "/opt/amnezia/cloak/ck-config.json", errorCode); - - QJsonDocument doc = QJsonDocument::fromJson(cloakConfig.toUtf8()); - - if (!doc.isNull() && doc.isObject()) { - QJsonObject cloakConfigObj = doc.object(); - - QString site = cloakConfigObj.value("RedirAddr").toString(); - if (!site.isEmpty()) { - containerConfig[config_key::site] = site; - } - } else { - qDebug() << "Failed to parse main loop Cloak JSON config"; - } - - } else if (protocol == Proto::ShadowSocks) { - QString shadowsocksConfig = serverController->getTextFileFromContainer( - container, credentials, "/opt/amnezia/shadowsocks/ss-config.json", errorCode); - - QJsonDocument doc = QJsonDocument::fromJson(shadowsocksConfig.toUtf8()); - - if (!doc.isNull() && doc.isObject()) { - QJsonObject ssConfigObj = doc.object(); - QString cipher = ssConfigObj.value("method").toString(); - if (!cipher.isEmpty()) { - containerConfig[config_key::cipher] = cipher; - } - } else { - qDebug() << "Failed to parse main loop Shadowsocks JSON config"; - } - } - - config.insert(config_key::container, ContainerProps::containerToString(container)); - } - if (shouldProcessProtocol) { - config.insert(ProtocolProps::protoToString(protocol), containerConfig); - } - } - installedContainers.insert(container, config); - } - - const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*"); - QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo); - if (torOrDnsRegMatch.hasMatch()) { - QString name = torOrDnsRegMatch.captured(1); - QString port = torOrDnsRegMatch.captured(2); - QString transportProto = torOrDnsRegMatch.captured(3); - DockerContainer container = ContainerProps::containerFromString(name); - - QJsonObject config; - Proto mainProto = ContainerProps::defaultProtocol(container); - for (auto protocol : ContainerProps::protocolsForContainer(container)) { - QJsonObject containerConfig; - if (protocol == mainProto) { - containerConfig.insert(config_key::port, port); - containerConfig.insert(config_key::transport_proto, transportProto); - - if (protocol == Proto::TorWebSite) { - stdOut.clear(); - script = QString("sudo docker exec -i %1 sh -c 'cat /var/lib/tor/hidden_service/hostname'").arg(name); - - ErrorCode errorCode = serverController->runScript(credentials, script, cbReadStdOut, cbReadStdErr); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - if (stdOut.isEmpty()) { - logger.error() << "Key parameters for the tor container are missing"; - continue; - } - - QString onion = stdOut; - onion.replace("\n", ""); - containerConfig.insert(config_key::site, onion); - } - - config.insert(config_key::container, ContainerProps::containerToString(container)); - } - config.insert(ProtocolProps::protoToString(protocol), containerConfig); - } - installedContainers.insert(container, config); - } - } - - return ErrorCode::NoError; -} - -void InstallController::updateContainer(QJsonObject config) -{ - int serverIndex = m_serversModel->getProcessedServerIndex(); - ServerCredentials serverCredentials = - qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); - - const DockerContainer container = ContainerProps::containerFromString(config.value(config_key::container).toString()); - QJsonObject oldContainerConfig = m_containersModel->getContainerConfig(container); - ErrorCode errorCode = ErrorCode::NoError; - - if (isUpdateDockerContainerRequired(container, oldContainerConfig, config)) { - QSharedPointer serverController(new ServerController(m_settings)); - connect(serverController.get(), &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); - connect(this, &InstallController::cancelInstallation, serverController.get(), &ServerController::cancelInstallation); - - errorCode = serverController->updateContainer(serverCredentials, container, oldContainerConfig, config); - clearCachedProfile(serverController); - } - - if (errorCode == ErrorCode::NoError) { - m_serversModel->updateContainerConfig(container, config); - m_protocolModel->updateModel(config); - - auto defaultContainer = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); - if ((serverIndex == m_serversModel->getDefaultServerIndex()) && (container == defaultContainer)) { - emit currentContainerUpdated(); - } else { - emit updateContainerFinished(tr("Settings updated successfully")); - } - - return; - } - - emit installationErrorOccurred(errorCode); -} - -void InstallController::rebootProcessedServer() -{ - int serverIndex = m_serversModel->getProcessedServerIndex(); - QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); - - QSharedPointer serverController(new ServerController(m_settings)); - const auto errorCode = m_serversModel->rebootServer(serverController); - if (errorCode == ErrorCode::NoError) { - emit rebootProcessedServerFinished(tr("Server '%1' was rebooted").arg(serverName)); - } else { - emit installationErrorOccurred(errorCode); - } -} - -void InstallController::removeProcessedServer() -{ - int serverIndex = m_serversModel->getProcessedServerIndex(); - QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); - - m_serversModel->removeServer(); - emit removeProcessedServerFinished(tr("Server '%1' was removed").arg(serverName)); -} - -void InstallController::removeAllContainers() -{ - int serverIndex = m_serversModel->getProcessedServerIndex(); - QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); - - QSharedPointer serverController(new ServerController(m_settings)); - ErrorCode errorCode = m_serversModel->removeAllContainers(serverController); - if (errorCode == ErrorCode::NoError) { - emit removeAllContainersFinished(tr("All containers from server '%1' have been removed").arg(serverName)); - return; - } - emit installationErrorOccurred(errorCode); -} - -void InstallController::removeProcessedContainer() -{ - int serverIndex = m_serversModel->getProcessedServerIndex(); - QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); - - int container = m_containersModel->getProcessedContainerIndex(); - QString containerName = m_containersModel->getProcessedContainerName(); - - QSharedPointer serverController(new ServerController(m_settings)); - ErrorCode errorCode = m_serversModel->removeContainer(serverController, container); - if (errorCode == ErrorCode::NoError) { - - emit removeProcessedContainerFinished(tr("%1 has been removed from the server '%2'").arg(containerName, serverName)); - return; - } - emit installationErrorOccurred(errorCode); -} - -void InstallController::removeApiConfig(const int serverIndex) -{ - m_serversModel->removeApiConfig(serverIndex); - emit apiConfigRemoved(tr("Api config removed")); -} - -void InstallController::clearCachedProfile(QSharedPointer serverController) -{ - if (serverController.isNull()) { - serverController.reset(new ServerController(m_settings)); - } - - int serverIndex = m_serversModel->getProcessedServerIndex(); - DockerContainer container = static_cast(m_containersModel->getProcessedContainerIndex()); - if (ContainerProps::containerService(container) == ServiceType::Other) { - return; - } - - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - ServerCredentials serverCredentials = - qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); - - m_serversModel->clearCachedProfile(container); - m_clientManagementModel->revokeClient(containerConfig, container, serverCredentials, serverIndex, serverController); - - emit cachedProfileCleared(tr("%1 cached profile cleared").arg(ContainerProps::containerHumanNames().value(container))); - QJsonObject updatedConfig = m_settings->containerConfig(serverIndex, container); - emit profileCleared(updatedConfig); -} - -QRegularExpression InstallController::ipAddressPortRegExp() -{ - return NetworkUtilities::ipAddressPortRegExp(); -} - -QRegularExpression InstallController::ipAddressRegExp() -{ - return NetworkUtilities::ipAddressRegExp(); -} - -void InstallController::setProcessedServerCredentials(const QString &hostName, const QString &userName, const QString &secretData) -{ - m_processedServerCredentials.hostName = hostName; - if (m_processedServerCredentials.hostName.contains(":")) { - m_processedServerCredentials.port = m_processedServerCredentials.hostName.split(":").at(1).toInt(); - m_processedServerCredentials.hostName = m_processedServerCredentials.hostName.split(":").at(0); - } - m_processedServerCredentials.userName = userName; - m_processedServerCredentials.secretData = secretData; -} - -void InstallController::setShouldCreateServer(bool shouldCreateServer) -{ - m_shouldCreateServer = shouldCreateServer; -} - -void InstallController::mountSftpDrive(const QString &port, const QString &password, const QString &username) -{ - QString mountPath; - QString cmd; - - int serverIndex = m_serversModel->getProcessedServerIndex(); - ServerCredentials serverCredentials = - qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); - QString hostname = serverCredentials.hostName; - -#ifdef Q_OS_WINDOWS - mountPath = Utils::getNextDriverLetter() + ":"; - // QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3") - // .arg(labelTftpUserNameText()) - // .arg(labelTftpPortText()) - // .arg(labelTftpPasswordText()); - - cmd = "C:\\Program Files\\SSHFS-Win\\bin\\sshfs.exe"; -#elif defined AMNEZIA_DESKTOP - mountPath = QString("%1/sftp:%2:%3").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), hostname, port); - QDir dir(mountPath); - if (!dir.exists()) { - dir.mkpath(mountPath); - } - - cmd = "/usr/local/bin/sshfs"; -#endif - -#ifdef AMNEZIA_DESKTOP - QSharedPointer process; - process.reset(new QProcess()); - m_sftpMountProcesses.append(process); - process->setProcessChannelMode(QProcess::MergedChannels); - - connect(process.get(), &QProcess::readyRead, this, [this, process, mountPath]() { - QString s = process->readAll(); - if (s.contains("The service sshfs has been started")) { - QDesktopServices::openUrl(QUrl("file:///" + mountPath)); - } - qDebug() << s; - }); - - process->setProgram(cmd); - - QString args = QString("%1@%2:/ %3 " - "-o port=%4 " - "-f " - "-o reconnect " - "-o rellinks " - "-o fstypename=SSHFS " - "-o ssh_command=/usr/bin/ssh.exe " - "-o UserKnownHostsFile=/dev/null " - "-o StrictHostKeyChecking=no " - "-o password_stdin") - .arg(username, hostname, mountPath, port); - - // args.replace("\n", " "); - // args.replace("\r", " "); - // #ifndef Q_OS_WIN - // args.replace("reconnect-orellinks", ""); - // #endif - process->setArguments(args.split(" ", Qt::SkipEmptyParts)); - process->start(); - process->waitForStarted(50); - if (process->state() != QProcess::Running) { - qDebug() << "onPushButtonSftpMountDriveClicked process not started"; - qDebug() << args; - } else { - process->write((password + "\n").toUtf8()); - } - -#endif -} - -bool InstallController::checkSshConnection(QSharedPointer serverController) -{ - if (serverController.isNull()) { - serverController.reset(new ServerController(m_settings)); - } - - ErrorCode errorCode = ErrorCode::NoError; - m_privateKeyPassphrase = ""; - - if (m_processedServerCredentials.secretData.contains("BEGIN") && m_processedServerCredentials.secretData.contains("PRIVATE KEY")) { - auto passphraseCallback = [this]() { - emit passphraseRequestStarted(); - QEventLoop loop; - QObject::connect(this, &InstallController::passphraseRequestFinished, &loop, &QEventLoop::quit); - loop.exec(); - - return m_privateKeyPassphrase; - }; - - QString decryptedPrivateKey; - errorCode = serverController->getDecryptedPrivateKey(m_processedServerCredentials, decryptedPrivateKey, passphraseCallback); - if (errorCode == ErrorCode::NoError) { - m_processedServerCredentials.secretData = decryptedPrivateKey; - } else { - emit installationErrorOccurred(errorCode); - return false; - } - } - - QString output; - output = serverController->checkSshConnection(m_processedServerCredentials, errorCode); - - if (errorCode != ErrorCode::NoError) { - emit installationErrorOccurred(errorCode); - return false; - } else { - if (output.contains(tr("Please login as the user"))) { - output.replace("\n", ""); - emit wrongInstallationUser(output); - return false; - } - } - return true; -} - -void InstallController::setEncryptedPassphrase(QString passphrase) -{ - m_privateKeyPassphrase = passphrase; - emit passphraseRequestFinished(); -} - -void InstallController::addEmptyServer() -{ - QJsonObject server; - server.insert(config_key::hostName, m_processedServerCredentials.hostName); - server.insert(config_key::userName, m_processedServerCredentials.userName); - server.insert(config_key::password, m_processedServerCredentials.secretData); - server.insert(config_key::port, m_processedServerCredentials.port); - server.insert(config_key::description, m_settings->nextAvailableServerName()); - - server.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); - - m_serversModel->addServer(server); - - emit installServerFinished(tr("Server added successfully")); -} - -void InstallController::validateConfig() -{ - int serverIndex = m_serversModel->getDefaultServerIndex(); - QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex); - - if (apiUtils::isServerFromApi(serverConfigObject)) { - emit configValidated(true); - return; - } - - if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { - emit noInstalledContainers(); - emit configValidated(false); - return; - } - - DockerContainer container = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); - - if (container == DockerContainer::None) { - emit installationErrorOccurred(ErrorCode::NoInstalledContainersError); - emit configValidated(false); - return; - } - - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - QSharedPointer serverController(new ServerController(m_settings)); - - auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) { - for (Proto protocol : ContainerProps::protocolsForContainer(container)) { - QString protocolConfig = - containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString(); - - if (protocolConfig.isEmpty()) { - return false; - } - } - return true; - }; - - if (isProtocolConfigExists(containerConfig, container)) { - emit configValidated(true); - return; - } - - struct ValidationResult { - ErrorCode errorCode = ErrorCode::NoError; - QJsonObject containerConfig; - }; - - QFuture future = - QtConcurrent::run([settings = m_settings, serverController, credentials, containerConfig, container]() mutable { - ValidationResult result; - result.containerConfig = containerConfig; - - VpnConfigurationsController vpnConfigurationController(settings, serverController); - result.errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container, - result.containerConfig); - return result; - }); - - auto *watcher = new QFutureWatcher(this); - connect(watcher, &QFutureWatcher::finished, this, - [this, watcher, container, credentials, serverController]() { - auto result = watcher->result(); - watcher->deleteLater(); - - if (result.errorCode != ErrorCode::NoError) { - emit installationErrorOccurred(result.errorCode); - emit configValidated(false); - return; - } - - m_serversModel->updateContainerConfig(container, result.containerConfig); - - ErrorCode appendError = m_clientManagementModel->appendClient( - container, credentials, result.containerConfig, - QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController); - - if (appendError != ErrorCode::NoError) { - emit installationErrorOccurred(appendError); - emit configValidated(false); - return; - } - - emit configValidated(true); - }); - watcher->setFuture(future); -} - -bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig, - const QJsonObject &newConfig) -{ - Proto mainProto = ContainerProps::defaultProtocol(container); - - const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); - const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); - - if (ContainerProps::isAwgContainer(container)) { - const AwgConfig oldConfig(oldProtoConfig); - const AwgConfig newConfig(newProtoConfig); - - if (oldConfig.hasEqualServerSettings(newConfig)) { - return false; - } - } else if (container == DockerContainer::WireGuard) { - const WgConfig oldConfig(oldProtoConfig); - const WgConfig newConfig(newProtoConfig); - - if (oldConfig.hasEqualServerSettings(newConfig)) { - return false; - } - } - - return true; -} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h deleted file mode 100644 index 034aa8494..000000000 --- a/client/ui/controllers/installController.h +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef INSTALLCONTROLLER_H -#define INSTALLCONTROLLER_H - -#include -#include - -#include "containers/containers_defs.h" -#include "core/defs.h" -#include "ui/models/clientManagementModel.h" -#include "ui/models/containers_model.h" -#include "ui/models/protocols_model.h" -#include "ui/models/servers_model.h" - -class InstallController : public QObject -{ - Q_OBJECT -public: - explicit InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &protocolsModel, - const QSharedPointer &clientManagementModel, - const std::shared_ptr &settings, QObject *parent = nullptr); - ~InstallController(); - -public slots: - void install(DockerContainer container, int port, TransportProto transportProto); - void setProcessedServerCredentials(const QString &hostName, const QString &userName, const QString &secretData); - void setShouldCreateServer(bool shouldCreateServer); - - void scanServerForInstalledContainers(); - - void updateContainer(QJsonObject config); - - void removeProcessedServer(); - void rebootProcessedServer(); - void removeAllContainers(); - void removeProcessedContainer(); - - void removeApiConfig(const int serverIndex); - - void clearCachedProfile(QSharedPointer serverController = nullptr); - - QRegularExpression ipAddressPortRegExp(); - QRegularExpression ipAddressRegExp(); - - void mountSftpDrive(const QString &port, const QString &password, const QString &username); - - bool checkSshConnection(QSharedPointer serverController = nullptr); - - void setEncryptedPassphrase(QString passphrase); - - void addEmptyServer(); - - void validateConfig(); - -signals: - void configValidated(bool isValid); - void installContainerFinished(const QString &finishMessage, bool isServiceInstall); - void installServerFinished(const QString &finishMessage); - - void updateContainerFinished(const QString &message); - - void scanServerFinished(bool isInstalledContainerFound); - - void rebootProcessedServerFinished(const QString &finishedMessage); - void removeProcessedServerFinished(const QString &finishedMessage); - void removeAllContainersFinished(const QString &finishedMessage); - void removeProcessedContainerFinished(const QString &finishedMessage); - - void installationErrorOccurred(ErrorCode errorCode); - void wrongInstallationUser(const QString &message); - - void serverAlreadyExists(int serverIndex); - - void passphraseRequestStarted(); - void passphraseRequestFinished(); - - void serverIsBusy(const bool isBusy); - void cancelInstallation(); - - void currentContainerUpdated(); - - void cachedProfileCleared(const QString &message); - void apiConfigRemoved(const QString &message); - - void noInstalledContainers(); - - void profileCleared(const QJsonObject &config); - -private: - void installServer(const DockerContainer container, const QMap &installedContainers, - const ServerCredentials &serverCredentials, const QSharedPointer &serverController, - QString &finishMessage); - void installContainer(const DockerContainer container, const QMap &installedContainers, - const ServerCredentials &serverCredentials, const QSharedPointer &serverController, - QString &finishMessage); - bool isServerAlreadyExists(); - - ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, const QSharedPointer &serverController, - QMap &installedContainers); - bool isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig); - - QSharedPointer m_serversModel; - QSharedPointer m_containersModel; - QSharedPointer m_protocolModel; - QSharedPointer m_clientManagementModel; - - std::shared_ptr m_settings; - - ServerCredentials m_processedServerCredentials; - - bool m_shouldCreateServer; - - QString m_privateKeyPassphrase; - -#ifndef Q_OS_IOS - QList> m_sftpMountProcesses; -#endif -}; - -#endif // INSTALLCONTROLLER_H diff --git a/client/ui/controllers/ipSplitTunnelingUiController.cpp b/client/ui/controllers/ipSplitTunnelingUiController.cpp new file mode 100644 index 000000000..c9081bd83 --- /dev/null +++ b/client/ui/controllers/ipSplitTunnelingUiController.cpp @@ -0,0 +1,87 @@ +#include "ipSplitTunnelingUiController.h" + +#include "systemController.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" + +IpSplitTunnelingUiController::IpSplitTunnelingUiController(IpSplitTunnelingController* ipSplitTunnelingController, + IpSplitTunnelingModel* ipSplitTunnelingModel, QObject *parent) + : QObject(parent), + m_ipSplitTunnelingController(ipSplitTunnelingController), + m_ipSplitTunnelingModel(ipSplitTunnelingModel) +{ + m_ipSplitTunnelingModel->updateModel(m_ipSplitTunnelingController->getCurrentSites()); +} + +void IpSplitTunnelingUiController::addSite(QString hostname) +{ + if (m_ipSplitTunnelingController->addSite(hostname)) { + emit finished(tr("New site added: %1").arg(hostname)); + } +} + +void IpSplitTunnelingUiController::removeSite(int index) +{ + auto modelIndex = m_ipSplitTunnelingModel->index(index); + auto hostname = m_ipSplitTunnelingModel->data(modelIndex, IpSplitTunnelingModel::Roles::UrlRole).toString(); + if (m_ipSplitTunnelingController->removeSite(hostname)) { + emit finished(tr("Site removed: %1").arg(hostname)); + } +} + +void IpSplitTunnelingUiController::removeSites() +{ + m_ipSplitTunnelingController->removeSites(); + emit finished(tr("Site list cleared!")); +} + +void IpSplitTunnelingUiController::importSites(const QString &fileName, bool replaceExisting) +{ + QByteArray jsonData; + if (!SystemController::readFile(fileName, jsonData)) { + emit errorOccurred(tr("Can't open file: %1").arg(fileName)); + return; + } + + QString errorMessage; + if (m_ipSplitTunnelingController->importSitesFromJson(jsonData, replaceExisting, errorMessage)) { + emit finished(tr("Import completed")); + } else { + emit errorOccurred(errorMessage); + } +} + +void IpSplitTunnelingUiController::exportSites(const QString &fileName) +{ + QByteArray jsonData = m_ipSplitTunnelingController->exportSitesToJson(); + SystemController::saveFile(fileName, QString::fromUtf8(jsonData)); + emit finished(tr("Export completed")); +} + +void IpSplitTunnelingUiController::toggleSplitTunneling(bool enabled) +{ + m_ipSplitTunnelingController->toggleSplitTunneling(enabled); + emit isSplitTunnelingEnabledChanged(); +} + +void IpSplitTunnelingUiController::setRouteMode(int routeMode) +{ + m_ipSplitTunnelingController->setRouteMode(static_cast(routeMode)); + emit routeModeChanged(); +} + +int IpSplitTunnelingUiController::getRouteMode() const +{ + return static_cast(m_ipSplitTunnelingController->getRouteMode()); +} + +bool IpSplitTunnelingUiController::isSplitTunnelingEnabled() const +{ + return m_ipSplitTunnelingController->isSplitTunnelingEnabled(); +} + +void IpSplitTunnelingUiController::updateModel() +{ + m_ipSplitTunnelingModel->updateModel(m_ipSplitTunnelingController->getCurrentSites()); +} diff --git a/client/ui/controllers/ipSplitTunnelingUiController.h b/client/ui/controllers/ipSplitTunnelingUiController.h new file mode 100644 index 000000000..0ac7f4a91 --- /dev/null +++ b/client/ui/controllers/ipSplitTunnelingUiController.h @@ -0,0 +1,46 @@ +#ifndef IPSPLITTUNNELINGUICONTROLLER_H +#define IPSPLITTUNNELINGUICONTROLLER_H + +#include + +#include "core/controllers/ipSplitTunnelingController.h" +#include "ui/models/ipSplitTunnelingModel.h" + +class IpSplitTunnelingUiController : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int routeMode READ getRouteMode WRITE setRouteMode NOTIFY routeModeChanged) + Q_PROPERTY(bool isSplitTunnelingEnabled READ isSplitTunnelingEnabled NOTIFY isSplitTunnelingEnabledChanged) + +public: + explicit IpSplitTunnelingUiController(IpSplitTunnelingController* ipSplitTunnelingController, + IpSplitTunnelingModel* ipSplitTunnelingModel, QObject *parent = nullptr); + +public slots: + void addSite(QString hostname); + void removeSite(int index); + void removeSites(); + void importSites(const QString &fileName, bool replaceExisting); + void exportSites(const QString &fileName); + void toggleSplitTunneling(bool enabled); + void setRouteMode(int routeMode); + + int getRouteMode() const; + bool isSplitTunnelingEnabled() const; + + void updateModel(); + +signals: + void routeModeChanged(); + void isSplitTunnelingEnabledChanged(); + void errorOccurred(const QString &errorMessage); + void finished(const QString &message); + void saveFile(const QString &fileName, const QString &data); + +private: + IpSplitTunnelingController* m_ipSplitTunnelingController; + IpSplitTunnelingModel* m_ipSplitTunnelingModel; +}; + +#endif // IPSPLITTUNNELINGUICONTROLLER_H diff --git a/client/ui/controllers/languageUiController.cpp b/client/ui/controllers/languageUiController.cpp new file mode 100644 index 000000000..ac856101e --- /dev/null +++ b/client/ui/controllers/languageUiController.cpp @@ -0,0 +1,125 @@ +#include "languageUiController.h" + +LanguageUiController::LanguageUiController(SettingsController* settingsController, + LanguageModel* languageModel, + QObject *parent) + : QObject(parent), + m_settingsController(settingsController), + m_languageModel(languageModel) +{ +} + +void LanguageUiController::onAppLanguageChanged(const QLocale &locale) +{ + emit updateTranslations(locale); + emit translationsUpdated(); +} + +void LanguageUiController::changeLanguage(const LanguageSettings::AvailableLanguageEnum language) +{ + QLocale locale = languageEnumToLocale(language); + m_settingsController->setAppLanguage(locale); +} + +int LanguageUiController::getCurrentLanguageIndex() const +{ + auto locale = m_settingsController->getAppLanguage(); + switch (locale.language()) { + case QLocale::English: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; + case QLocale::Russian: return static_cast(LanguageSettings::AvailableLanguageEnum::Russian); break; + case QLocale::Chinese: return static_cast(LanguageSettings::AvailableLanguageEnum::China_cn); break; + case QLocale::Ukrainian: return static_cast(LanguageSettings::AvailableLanguageEnum::Ukrainian); break; + case QLocale::Persian: return static_cast(LanguageSettings::AvailableLanguageEnum::Persian); break; + case QLocale::Arabic: return static_cast(LanguageSettings::AvailableLanguageEnum::Arabic); break; + case QLocale::Burmese: return static_cast(LanguageSettings::AvailableLanguageEnum::Burmese); break; + case QLocale::Urdu: return static_cast(LanguageSettings::AvailableLanguageEnum::Urdu); break; + case QLocale::Hindi: return static_cast(LanguageSettings::AvailableLanguageEnum::Hindi); break; + default: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; + } +} + +int LanguageUiController::getLineHeightAppend() const +{ + auto locale = m_settingsController->getAppLanguage(); + switch (locale.language()) { + case QLocale::Burmese: return 10; break; + default: return 0; break; + } +} + +QString LanguageUiController::getCurrentLanguageName() const +{ + int index = getCurrentLanguageIndex(); + return getLocalLanguageName(static_cast(index)); +} + +LanguageSettings::AvailableLanguageEnum LanguageUiController::getSystemLanguageEnum() const +{ + QLocale locale = QLocale::system(); + switch (locale.language()) { + case QLocale::Russian: return LanguageSettings::AvailableLanguageEnum::Russian; + case QLocale::Chinese: return LanguageSettings::AvailableLanguageEnum::China_cn; + case QLocale::Ukrainian: return LanguageSettings::AvailableLanguageEnum::Ukrainian; + case QLocale::Persian: return LanguageSettings::AvailableLanguageEnum::Persian; + case QLocale::Arabic: return LanguageSettings::AvailableLanguageEnum::Arabic; + case QLocale::Burmese: return LanguageSettings::AvailableLanguageEnum::Burmese; + case QLocale::Urdu: return LanguageSettings::AvailableLanguageEnum::Urdu; + case QLocale::Hindi: return LanguageSettings::AvailableLanguageEnum::Hindi; + case QLocale::English: return LanguageSettings::AvailableLanguageEnum::English; + default: return LanguageSettings::AvailableLanguageEnum::English; + } +} + +QString LanguageUiController::getCurrentSiteUrl(const QString &path) const +{ + auto locale = m_settingsController->getAppLanguage(); + if (locale.language() == QLocale::Russian) { + return "https://storage.googleapis.com/amnezia/amnezia.org" + (path.isEmpty() ? "" : (QString("?m-path=/%1").arg(path))); + } + return QString("https://amnezia.org") + (path.isEmpty() ? "" : (QString("/%1").arg(path))); +} + +QString LanguageUiController::getCurrentDocsUrl(const QString &path) const +{ + auto locale = m_settingsController->getAppLanguage(); + if (locale.language() == QLocale::Russian) { + return "https://storage.googleapis.com/amnezia/docs" + (path.isEmpty() ? "" : (QString("?m-path=/%1").arg(path))); + } + return QString("https://docs.amnezia.org") + (path.isEmpty() ? "" : (QString("/%1").arg(path))); +} + +QString LanguageUiController::getLocalLanguageName(const LanguageSettings::AvailableLanguageEnum language) const +{ + QString strLanguage(""); + switch (language) { + case LanguageSettings::AvailableLanguageEnum::English: strLanguage = "English"; break; + case LanguageSettings::AvailableLanguageEnum::Russian: strLanguage = "Русский"; break; + case LanguageSettings::AvailableLanguageEnum::Ukrainian: strLanguage = "Українська"; break; + case LanguageSettings::AvailableLanguageEnum::China_cn: strLanguage = "\347\256\200\344\275\223\344\270\255\346\226\207"; break; + case LanguageSettings::AvailableLanguageEnum::Persian: strLanguage = "فارسی"; break; + case LanguageSettings::AvailableLanguageEnum::Arabic: strLanguage = "العربية"; break; + case LanguageSettings::AvailableLanguageEnum::Burmese: strLanguage = "မြန်မာဘာသာ"; break; + case LanguageSettings::AvailableLanguageEnum::Urdu: strLanguage = "اُرْدُوْ"; break; + case LanguageSettings::AvailableLanguageEnum::Hindi: strLanguage = "हिन्दी"; break; + default: break; + } + + return strLanguage; +} + +QLocale LanguageUiController::languageEnumToLocale(const LanguageSettings::AvailableLanguageEnum language) const +{ + switch (language) { + case LanguageSettings::AvailableLanguageEnum::English: return QLocale::English; + case LanguageSettings::AvailableLanguageEnum::Russian: return QLocale::Russian; + case LanguageSettings::AvailableLanguageEnum::China_cn: return QLocale::Chinese; + case LanguageSettings::AvailableLanguageEnum::Ukrainian: return QLocale::Ukrainian; + case LanguageSettings::AvailableLanguageEnum::Persian: return QLocale::Persian; + case LanguageSettings::AvailableLanguageEnum::Arabic: return QLocale::Arabic; + case LanguageSettings::AvailableLanguageEnum::Burmese: return QLocale::Burmese; + case LanguageSettings::AvailableLanguageEnum::Urdu: return QLocale::Urdu; + case LanguageSettings::AvailableLanguageEnum::Hindi: return QLocale::Hindi; + default: return QLocale::English; + } +} + diff --git a/client/ui/controllers/languageUiController.h b/client/ui/controllers/languageUiController.h new file mode 100644 index 000000000..351cfc91e --- /dev/null +++ b/client/ui/controllers/languageUiController.h @@ -0,0 +1,46 @@ +#ifndef LANGUAGEUICONTROLLER_H +#define LANGUAGEUICONTROLLER_H + +#include +#include + +#include "core/controllers/settingsController.h" +#include "ui/models/languageModel.h" + +class LanguageUiController : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString currentLanguageName READ getCurrentLanguageName NOTIFY translationsUpdated) + Q_PROPERTY(int currentLanguageIndex READ getCurrentLanguageIndex NOTIFY translationsUpdated) + Q_PROPERTY(int lineHeightAppend READ getLineHeightAppend NOTIFY translationsUpdated) + +public: + explicit LanguageUiController(SettingsController* settingsController, + LanguageModel* languageModel, + QObject *parent = nullptr); + +public slots: + void changeLanguage(const LanguageSettings::AvailableLanguageEnum language); + void onAppLanguageChanged(const QLocale &locale); + int getCurrentLanguageIndex() const; + int getLineHeightAppend() const; + QString getCurrentLanguageName() const; + QString getCurrentSiteUrl(const QString &path = "") const; + QString getCurrentDocsUrl(const QString &path = "") const; + LanguageSettings::AvailableLanguageEnum getSystemLanguageEnum() const; + +signals: + void updateTranslations(const QLocale &locale); + void translationsUpdated(); + +private: + QString getLocalLanguageName(const LanguageSettings::AvailableLanguageEnum language) const; + QLocale languageEnumToLocale(const LanguageSettings::AvailableLanguageEnum language) const; + + SettingsController* m_settingsController; + LanguageModel* m_languageModel; +}; + +#endif // LANGUAGEUICONTROLLER_H + diff --git a/client/ui/controllers/focusController.cpp b/client/ui/controllers/qml/focusController.cpp similarity index 99% rename from client/ui/controllers/focusController.cpp rename to client/ui/controllers/qml/focusController.cpp index 65ae3b8b3..02cd7f280 100644 --- a/client/ui/controllers/focusController.cpp +++ b/client/ui/controllers/qml/focusController.cpp @@ -1,5 +1,5 @@ #include "focusController.h" -#include "utils/qmlUtils.h" +#include "ui/utils/qmlUtils.h" #include #include diff --git a/client/ui/controllers/focusController.h b/client/ui/controllers/qml/focusController.h similarity index 96% rename from client/ui/controllers/focusController.h rename to client/ui/controllers/qml/focusController.h index 11074dae6..ae068a2a6 100644 --- a/client/ui/controllers/focusController.h +++ b/client/ui/controllers/qml/focusController.h @@ -1,7 +1,7 @@ #ifndef FOCUSCONTROLLER_H #define FOCUSCONTROLLER_H -#include "ui/controllers/listViewFocusController.h" +#include "ui/controllers/qml/listViewFocusController.h" #include diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/qml/listViewFocusController.cpp similarity index 99% rename from client/ui/controllers/listViewFocusController.cpp rename to client/ui/controllers/qml/listViewFocusController.cpp index d94ec66e5..5cc9e768a 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/qml/listViewFocusController.cpp @@ -1,5 +1,5 @@ #include "listViewFocusController.h" -#include "utils/qmlUtils.h" +#include "ui/utils/qmlUtils.h" #include diff --git a/client/ui/controllers/listViewFocusController.h b/client/ui/controllers/qml/listViewFocusController.h similarity index 100% rename from client/ui/controllers/listViewFocusController.h rename to client/ui/controllers/qml/listViewFocusController.h diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/qml/pageController.cpp similarity index 58% rename from client/ui/controllers/pageController.cpp rename to client/ui/controllers/qml/pageController.cpp index 3f3319c6d..67b9b64c1 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/qml/pageController.cpp @@ -1,6 +1,7 @@ #include "pageController.h" -#include "utils/converter.h" -#include "core/errorstrings.h" + +#include "ui/utils/converter.h" +#include "core/utils/errorStrings.h" #if defined(MACOS_NE) #include "platforms/ios/ios_controller.h" #endif @@ -15,16 +16,29 @@ #include "platforms/android/android_controller.h" #endif #if defined Q_OS_MAC - #include "ui/macos_util.h" + #include "ui/utils/macosUtil.h" #endif -PageController::PageController(const QSharedPointer &serversModel, - const std::shared_ptr &settings, QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_settings(settings) +PageController::PageController(ServersController* serversController, + SettingsController* settingsController, + QObject *parent) + : QObject(parent), m_serversController(serversController), m_settingsController(settingsController) { #ifdef Q_OS_ANDROID auto initialPageNavigationBarColor = getInitialPageNavigationBarColor(); AndroidController::instance()->setNavigationBarColor(initialPageNavigationBarColor); + + connect(AndroidController::instance(), &AndroidController::imeInsetsChanged, this, [this](int heightDp) { + m_imeHeight = heightDp; + emit imeHeightChanged(heightDp); + emit safeAreaBottomMarginChanged(); + }); + connect(AndroidController::instance(), &AndroidController::systemBarsInsetsChanged, this, [this](int navBarHeightDp, int statusBarHeightDp) { + m_cachedNavigationBarHeight = navBarHeightDp; + m_cachedStatusBarHeight = statusBarHeightDp; + emit safeAreaBottomMarginChanged(); + emit safeAreaTopMarginChanged(); + }); #endif #if defined Q_OS_MACX @@ -43,10 +57,9 @@ PageController::PageController(const QSharedPointer &serversModel, bool PageController::isStartPageVisible() { - if (m_serversModel->getServersCount()) { - if (m_serversModel->getDefaultServerIndex() < 0) { - auto defaultServerIndex = m_serversModel->index(0); - m_serversModel->setData(defaultServerIndex, true, ServersModel::Roles::IsDefaultRole); + if (m_serversController->getServersCount()) { + if (m_serversController->getDefaultServerIndex() < 0) { + m_serversController->setDefaultServerIndex(0); } return false; } else { @@ -63,7 +76,6 @@ QString PageController::getPagePath(PageLoader::PageEnum page) void PageController::closeWindow() { -// On mobile platforms, quit app on close; on desktop, just hide window #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) qApp->quit(); #else @@ -97,7 +109,7 @@ void PageController::keyPressEvent(Qt::Key key) unsigned int PageController::getInitialPageNavigationBarColor() { - if (m_serversModel->getServersCount()) { + if (m_serversController->getServersCount()) { return 0xFF1C1D21; } else { return 0xFF0E0E11; @@ -113,7 +125,7 @@ void PageController::updateNavigationBarColor(const int color) void PageController::showOnStartup() { - if (!m_settings->isStartMinimized()) { + if (!m_settingsController->isStartMinimizedEnabled()) { emit raiseMainWindow(); } else { #if defined(Q_OS_WIN) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) @@ -165,6 +177,76 @@ int PageController::decrementDrawerDepth() } } +bool PageController::isEdgeToEdgeEnabled() +{ +#ifdef Q_OS_ANDROID + if (!m_edgeToEdgeCached) { + m_cachedEdgeToEdgeEnabled = AndroidController::instance()->isEdgeToEdgeEnabled(); + m_edgeToEdgeCached = true; + } + return m_cachedEdgeToEdgeEnabled; +#else + return false; +#endif +} + +int PageController::getStatusBarHeight() +{ +#ifdef Q_OS_ANDROID + if (m_cachedStatusBarHeight < 0) { + m_cachedStatusBarHeight = AndroidController::instance()->getStatusBarHeight(); + } + return m_cachedStatusBarHeight; +#else + return 0; +#endif +} + +int PageController::getNavigationBarHeight() +{ +#ifdef Q_OS_ANDROID + if (m_cachedNavigationBarHeight < 0) { + m_cachedNavigationBarHeight = AndroidController::instance()->getNavigationBarHeight(); + } + return m_cachedNavigationBarHeight; +#else + return 0; +#endif +} + +int PageController::getSafeAreaTopMargin() +{ +#ifdef Q_OS_ANDROID + if (isEdgeToEdgeEnabled()) { + int height = getStatusBarHeight(); + int result = height > 0 ? height : 40; + return result; + } +#endif + return 0; +} + +int PageController::getSafeAreaBottomMargin() +{ +#ifdef Q_OS_ANDROID + if (isEdgeToEdgeEnabled()) { + if (m_imeHeight > 0) { + return 0; + } + + int height = getNavigationBarHeight(); + int result = height > 0 ? height : 56; + return result; + } +#endif + return 0; +} + +int PageController::getImeHeight() +{ + return m_imeHeight; +} + void PageController::onShowErrorMessage(ErrorCode errorCode) { const auto fullErrorMessage = errorString(errorCode); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/qml/pageController.h similarity index 77% rename from client/ui/controllers/pageController.h rename to client/ui/controllers/qml/pageController.h index 001d86d48..df95fd7af 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/qml/pageController.h @@ -4,8 +4,11 @@ #include #include -#include "core/defs.h" -#include "ui/models/servers_model.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/controllers/settingsController.h" +#include "core/controllers/serversController.h" namespace PageLoader { @@ -62,8 +65,6 @@ namespace PageLoader PageSetupWizardApiFreeInfo, PageProtocolOpenVpnSettings, - PageProtocolShadowSocksSettings, - PageProtocolCloakSettings, PageProtocolXraySettings, PageProtocolWireGuardSettings, PageProtocolAwgSettings, @@ -93,9 +94,14 @@ class PageController : public QObject { Q_OBJECT public: - explicit PageController(const QSharedPointer &serversModel, const std::shared_ptr &settings, + explicit PageController(ServersController* serversController, + SettingsController* settingsController, QObject *parent = nullptr); + Q_PROPERTY(int safeAreaTopMargin READ getSafeAreaTopMargin NOTIFY safeAreaTopMarginChanged) + Q_PROPERTY(int safeAreaBottomMargin READ getSafeAreaBottomMargin NOTIFY safeAreaBottomMarginChanged) + Q_PROPERTY(int imeHeight READ getImeHeight NOTIFY imeHeightChanged) + public slots: bool isStartPageVisible(); QString getPagePath(PageLoader::PageEnum page); @@ -119,6 +125,13 @@ public slots: int incrementDrawerDepth(); int decrementDrawerDepth(); + bool isEdgeToEdgeEnabled(); + int getStatusBarHeight(); + int getNavigationBarHeight(); + int getSafeAreaTopMargin(); + int getSafeAreaBottomMargin(); + int getImeHeight(); + private slots: void onShowErrorMessage(amnezia::ErrorCode errorCode); @@ -154,14 +167,23 @@ signals: void escapePressed(); void closeTopDrawer(); -private: - QSharedPointer m_serversModel; + void imeHeightChanged(int height); + void safeAreaTopMarginChanged(); + void safeAreaBottomMarginChanged(); - std::shared_ptr m_settings; +private: + ServersController* m_serversController; + SettingsController* m_settingsController; bool m_isTriggeredByConnectButton; int m_drawerDepth = 0; + + mutable int m_cachedStatusBarHeight = -1; + mutable int m_cachedNavigationBarHeight = -1; + mutable bool m_cachedEdgeToEdgeEnabled = false; + mutable bool m_edgeToEdgeCached = false; + int m_imeHeight = 0; }; -#endif // PAGECONTROLLER_H +#endif diff --git a/client/ui/controllers/selfhosted/exportUiController.cpp b/client/ui/controllers/selfhosted/exportUiController.cpp new file mode 100644 index 000000000..4a9f8bfc4 --- /dev/null +++ b/client/ui/controllers/selfhosted/exportUiController.cpp @@ -0,0 +1,116 @@ +#include "exportUiController.h" + +#include "../systemController.h" + +ExportUiController::ExportUiController(ExportController* exportController, QObject *parent) + : QObject(parent), + m_exportController(exportController) +{ +} + +void ExportUiController::generateFullAccessConfig(int serverIndex) +{ + clearPreviousConfig(); + auto result = m_exportController->generateFullAccessConfig(serverIndex); + applyExportResult(result); +} + +void ExportUiController::generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName) +{ + clearPreviousConfig(); + auto result = m_exportController->generateConnectionConfig(serverIndex, containerIndex, clientName); + applyExportResult(result); +} + +void ExportUiController::generateOpenVpnConfig(int serverIndex, const QString &clientName) +{ + clearPreviousConfig(); + auto result = m_exportController->generateOpenVpnConfig(serverIndex, clientName); + applyExportResult(result); +} + +void ExportUiController::generateWireGuardConfig(int serverIndex, const QString &clientName) +{ + clearPreviousConfig(); + auto result = m_exportController->generateWireGuardConfig(serverIndex, clientName); + applyExportResult(result); +} + +void ExportUiController::generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName) +{ + clearPreviousConfig(); + auto result = m_exportController->generateAwgConfig(serverIndex, containerIndex, clientName); + applyExportResult(result); +} + + +void ExportUiController::generateXrayConfig(int serverIndex, const QString &clientName) +{ + clearPreviousConfig(); + auto result = m_exportController->generateXrayConfig(serverIndex, clientName); + applyExportResult(result); +} + +QString ExportUiController::getConfig() +{ + return m_config; +} + +QString ExportUiController::getNativeConfigString() +{ + return m_nativeConfigString; +} + +QList ExportUiController::getQrCodes() +{ + return m_qrCodes; +} + +void ExportUiController::exportConfig(const QString &fileName) +{ + SystemController::saveFile(fileName, m_config); +} + +void ExportUiController::updateClientManagementModel(int serverIndex, int containerIndex) +{ + m_exportController->updateClientManagementModel(serverIndex, containerIndex); +} + +void ExportUiController::revokeConfig(int row, int serverIndex, int containerIndex) +{ + m_exportController->revokeConfig(row, serverIndex, containerIndex); + emit revokeConfigFinished(); +} + +void ExportUiController::renameClient(int row, const QString &clientName, int serverIndex, int containerIndex) +{ + m_exportController->renameClient(row, clientName, serverIndex, containerIndex); +} + +int ExportUiController::getQrCodesCount() +{ + return m_qrCodes.size(); +} + +void ExportUiController::clearPreviousConfig() +{ + m_config.clear(); + m_nativeConfigString.clear(); + m_qrCodes.clear(); + + emit exportConfigChanged(); +} + +void ExportUiController::applyExportResult(const ExportController::ExportResult &result) +{ + if (result.errorCode != ErrorCode::NoError) { + emit exportErrorOccurred(result.errorCode); + return; + } + + m_config = result.config; + m_nativeConfigString = result.nativeConfigString; + m_qrCodes = result.qrCodes; + + emit exportConfigChanged(); +} diff --git a/client/ui/controllers/selfhosted/exportUiController.h b/client/ui/controllers/selfhosted/exportUiController.h new file mode 100644 index 000000000..bfc000b0e --- /dev/null +++ b/client/ui/controllers/selfhosted/exportUiController.h @@ -0,0 +1,59 @@ +#ifndef EXPORTUICONTROLLER_H +#define EXPORTUICONTROLLER_H + +#include + +#include "core/controllers/selfhosted/exportController.h" + +class ExportUiController : public QObject +{ + Q_OBJECT +public: + explicit ExportUiController(ExportController* exportController, QObject *parent = nullptr); + + Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY exportConfigChanged) + Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged) + Q_PROPERTY(QString config READ getConfig NOTIFY exportConfigChanged) + Q_PROPERTY(QString nativeConfigString READ getNativeConfigString NOTIFY exportConfigChanged) + +public slots: + void generateFullAccessConfig(int serverIndex); + void generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName); + void generateOpenVpnConfig(int serverIndex, const QString &clientName); + void generateWireGuardConfig(int serverIndex, const QString &clientName); + void generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName); + void generateXrayConfig(int serverIndex, const QString &clientName); + + QString getConfig(); + QString getNativeConfigString(); + QList getQrCodes(); + + void exportConfig(const QString &fileName); + + void updateClientManagementModel(int serverIndex, int containerIndex); + void revokeConfig(int row, int serverIndex, int containerIndex); + void renameClient(int row, const QString &clientName, int serverIndex, int containerIndex); + +signals: + void generateConfig(int type); + void revokeConfigFinished(); + void exportErrorOccurred(const QString &errorMessage); + void exportErrorOccurred(ErrorCode errorCode); + + void exportConfigChanged(); + + void saveFile(const QString &fileName, const QString &data); + +private: + int getQrCodesCount(); + void clearPreviousConfig(); + void applyExportResult(const ExportController::ExportResult &result); + + ExportController* m_exportController; + + QString m_config; + QString m_nativeConfigString; + QList m_qrCodes; +}; + +#endif // EXPORTUICONTROLLER_H diff --git a/client/ui/controllers/selfhosted/installUiController.cpp b/client/ui/controllers/selfhosted/installUiController.cpp new file mode 100755 index 000000000..b867a0f5d --- /dev/null +++ b/client/ui/controllers/selfhosted/installUiController.cpp @@ -0,0 +1,493 @@ +#include "installUiController.h" + +#include +#include +#include +#include +#include +#include + +#include "core/utils/api/apiUtils.h" +#include "core/controllers/selfhosted/installController.h" +#include "core/utils/selfhosted/sshSession.h" +#include "core/utils/networkUtilities.h" +#include "logger.h" +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" +#include "ui/models/protocols/awgConfigModel.h" +#include "ui/models/protocols/wireguardConfigModel.h" +#include "ui/models/protocols/openvpnConfigModel.h" +#include "ui/models/protocols/xrayConfigModel.h" +#ifdef Q_OS_WINDOWS +#include "ui/models/protocols/ikev2ConfigModel.h" +#endif +#include "ui/models/services/sftpConfigModel.h" +#include "ui/models/services/socks5ProxyConfigModel.h" +#include "ui/models/services/torConfigModel.h" +#include "core/utils/utilities.h" +#include "core/models/serverConfig.h" +#include "core/models/containerConfig.h" +#include "core/models/protocols/awgProtocolConfig.h" +#include "core/models/protocols/wireGuardProtocolConfig.h" +#include "core/models/protocols/openVpnProtocolConfig.h" +#include "core/models/protocols/xrayProtocolConfig.h" + +namespace +{ + Logger logger("InstallUiController"); + + namespace configKey + { + constexpr char serviceInfo[] = "service_info"; + constexpr char serviceType[] = "service_type"; + constexpr char serviceProtocol[] = "service_protocol"; + constexpr char userCountryCode[] = "user_country_code"; + + constexpr char serverCountryCode[] = "server_country_code"; + constexpr char serverCountryName[] = "server_country_name"; + constexpr char availableCountries[] = "available_countries"; + + constexpr char apiConfig[] = "api_config"; + constexpr char authData[] = "auth_data"; + } +} + +InstallUiController::InstallUiController(InstallController *installController, + ServersController *serversController, + SettingsController *settingsController, + ProtocolsModel *protocolsModel, + UsersController *usersController, + AwgConfigModel *awgConfigModel, + WireGuardConfigModel *wireGuardConfigModel, + OpenVpnConfigModel *openVpnConfigModel, + XrayConfigModel *xrayConfigModel, + TorConfigModel *torConfigModel, +#ifdef Q_OS_WINDOWS + Ikev2ConfigModel *ikev2ConfigModel, +#endif + SftpConfigModel *sftpConfigModel, + Socks5ProxyConfigModel *socks5ConfigModel, + QObject *parent) + : QObject(parent), + m_installController(installController), + m_serversController(serversController), + m_settingsController(settingsController), + m_protocolModel(protocolsModel), + m_usersController(usersController), + m_awgConfigModel(awgConfigModel), + m_wireGuardConfigModel(wireGuardConfigModel), + m_openVpnConfigModel(openVpnConfigModel), + m_xrayConfigModel(xrayConfigModel), + m_torConfigModel(torConfigModel), +#ifdef Q_OS_WINDOWS + m_ikev2ConfigModel(ikev2ConfigModel), +#endif + m_sftpConfigModel(sftpConfigModel), + m_socks5ConfigModel(socks5ConfigModel) +{ + 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); + } + }); +} + +InstallUiController::~InstallUiController() +{ +} + +void InstallUiController::install(DockerContainer container, int port, TransportProto transportProto, int serverIndex) +{ + const bool isNewServer = serverIndex < 0; + + ServerCredentials serverCredentials; + if (isNewServer) { + serverCredentials = m_processedServerCredentials; + } else { + serverCredentials = m_serversController->getServerCredentials(serverIndex); + m_processedServerCredentials = ServerCredentials(); + } + + QMap preparedContainers; + QString finishMessage; + ErrorCode errorCode; + + if (isNewServer) { + int existingServerIndex = -1; + if (m_installController->isServerAlreadyExists(serverCredentials, existingServerIndex)) { + emit serverAlreadyExists(existingServerIndex); + return; + } + + bool wasContainerInstalled = false; + errorCode = m_installController->installServer(serverCredentials, container, port, transportProto, wasContainerInstalled); + if (errorCode) { + emit installationErrorOccurred(errorCode); + return; + } + + int serverIndex = m_serversController->getServersCount() - 1; + ServerConfig serverConfig = m_serversController->getServerConfig(serverIndex); + QMap containers = serverConfig.containers(); + int containersCount = containers.size(); + + if (wasContainerInstalled) { + finishMessage = tr("%1 installed successfully. ").arg(ContainerUtils::containerHumanNames().value(container)); + } else { + finishMessage = tr("%1 is already installed on the server. ").arg(ContainerUtils::containerHumanNames().value(container)); + } + + if (containersCount > 1) { + finishMessage += tr("\nAdded containers that were already installed on the server"); + } + + emit installServerFinished(finishMessage); + } else { + ServerConfig serverConfig = m_serversController->getServerConfig(serverIndex); + QMap containers = serverConfig.containers(); + int containersCount = containers.size(); + + bool wasContainerInstalled = false; + errorCode = m_installController->installContainer(serverIndex, container, port, transportProto, + wasContainerInstalled); + if (errorCode) { + emit installationErrorOccurred(errorCode); + return; + } + + ServerConfig newServerConfig = m_serversController->getServerConfig(serverIndex); + QMap newContainers = newServerConfig.containers(); + int newContainersCount = newContainers.size(); + + bool hasNewContainers = (newContainersCount - containersCount) > (wasContainerInstalled ? 1 : 0); + + if (wasContainerInstalled) { + finishMessage = tr("%1 installed successfully. ").arg(ContainerUtils::containerHumanNames().value(container)); + } else { + finishMessage = tr("%1 is already installed on the server. ").arg(ContainerUtils::containerHumanNames().value(container)); + } + + if (hasNewContainers) { + finishMessage += tr("\nAlready installed containers were found on the server. " + "All installed containers have been added to the application"); + } + + emit installContainerFinished(finishMessage, ContainerUtils::containerService(container) == ServiceType::Other); + } +} + +void InstallUiController::scanServerForInstalledContainers(int serverIndex) +{ + ServerConfig serverBefore = m_serversController->getServerConfig(serverIndex); + QMap containersBefore = serverBefore.containers(); + int containersCountBefore = containersBefore.size(); + + ErrorCode errorCode = m_installController->scanServerForInstalledContainers(serverIndex); + + if (errorCode == ErrorCode::NoError) { + ServerConfig serverAfter = m_serversController->getServerConfig(serverIndex); + QMap containersAfter = serverAfter.containers(); + int containersCountAfter = containersAfter.size(); + + bool isInstalledContainerAdded = containersCountAfter > containersCountBefore; + emit scanServerFinished(isInstalledContainerAdded); + return; + } + + emit installationErrorOccurred(errorCode); +} + +void InstallUiController::updateContainer(int serverIndex, int containerIndex, int protocolIndex) +{ + DockerContainer container = static_cast(containerIndex); + + Proto protocolType = static_cast(protocolIndex); + + ContainerConfig containerConfig; + containerConfig.container = container; + + switch (protocolType) { + case Proto::Awg: { + containerConfig.protocolConfig = m_awgConfigModel->getProtocolConfig(); + break; + } + case Proto::WireGuard: { + containerConfig.protocolConfig = m_wireGuardConfigModel->getProtocolConfig(); + break; + } + case Proto::OpenVpn: { + containerConfig.protocolConfig = m_openVpnConfigModel->getProtocolConfig(); + break; + } + case Proto::Xray: + case Proto::SSXray: { + containerConfig.protocolConfig = m_xrayConfigModel->getProtocolConfig(); + break; + } + case Proto::TorWebSite: { + containerConfig.protocolConfig = m_torConfigModel->getProtocolConfig(); + break; + } + case Proto::Sftp: { + containerConfig.protocolConfig = m_sftpConfigModel->getProtocolConfig(); + break; + } + case Proto::Socks5Proxy: { + containerConfig.protocolConfig = m_socks5ConfigModel->getProtocolConfig(); + break; + } +#ifdef Q_OS_WINDOWS + case Proto::Ikev2: { + containerConfig.protocolConfig = m_ikev2ConfigModel->getProtocolConfig(); + break; + } +#endif + default: + return; + } + ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverIndex, container); + + ErrorCode errorCode = m_installController->updateContainer(serverIndex, container, oldContainerConfig, containerConfig); + + if (errorCode == ErrorCode::NoError) { + ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverIndex, container); + m_protocolModel->updateModel(updatedConfig); + + auto defaultContainer = m_serversController->getServerConfig(serverIndex).defaultContainer(); + if ((serverIndex == m_serversController->getDefaultServerIndex()) && (container == defaultContainer)) { + emit currentContainerUpdated(); + } else { + emit updateContainerFinished(tr("Settings updated successfully")); + } + + return; + } + + emit installationErrorOccurred(errorCode); +} + +void InstallUiController::rebootServer(int serverIndex) +{ + QString serverName = m_serversController->getServerConfig(serverIndex).displayName(); + + const auto errorCode = m_installController->rebootServer(serverIndex); + if (errorCode == ErrorCode::NoError) { + emit rebootServerFinished(tr("Server '%1' was rebooted").arg(serverName)); + } else { + emit installationErrorOccurred(errorCode); + } +} + +void InstallUiController::removeServer(int serverIndex) +{ + QString serverName = m_serversController->getServerConfig(serverIndex).displayName(); + + m_serversController->removeServer(serverIndex); + emit removeServerFinished(tr("Server '%1' was removed").arg(serverName)); +} + +void InstallUiController::removeAllContainers(int serverIndex) +{ + QString serverName = m_serversController->getServerConfig(serverIndex).displayName(); + + ErrorCode errorCode = m_installController->removeAllContainers(serverIndex); + if (errorCode == ErrorCode::NoError) { + emit removeAllContainersFinished(tr("All containers from server '%1' have been removed").arg(serverName)); + return; + } + emit installationErrorOccurred(errorCode); +} + +void InstallUiController::removeContainer(int serverIndex, int containerIndex) +{ + QString serverName = m_serversController->getServerConfig(serverIndex).displayName(); + + DockerContainer container = static_cast(containerIndex); + QString containerName = ContainerUtils::containerHumanNames().value(container); + + ErrorCode errorCode = m_installController->removeContainer(serverIndex, container); + if (errorCode == ErrorCode::NoError) { + + emit removeContainerFinished(tr("%1 has been removed from the server '%2'").arg(containerName, serverName)); + return; + } + emit installationErrorOccurred(errorCode); +} + +void InstallUiController::clearCachedProfile(int serverIndex, int containerIndex) +{ + DockerContainer container = static_cast(containerIndex); + if (ContainerUtils::containerService(container) == ServiceType::Other) { + return; + } + + m_installController->clearCachedProfile(serverIndex, container); + + emit cachedProfileCleared(tr("%1 cached profile cleared").arg(ContainerUtils::containerHumanNames().value(container))); + ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverIndex, container); + m_protocolModel->updateModel(updatedConfig); +} + +QRegularExpression InstallUiController::ipAddressRegExp() +{ + return NetworkUtilities::ipAddressRegExp(); +} + +void InstallUiController::clearProcessedServerCredentials() +{ + m_processedServerCredentials = ServerCredentials(); +} + +void InstallUiController::setProcessedServerCredentials(const QString &hostName, const QString &userName, const QString &secretData) +{ + m_processedServerCredentials.hostName = hostName; + if (m_processedServerCredentials.hostName.contains(":")) { + m_processedServerCredentials.port = m_processedServerCredentials.hostName.split(":").at(1).toInt(); + m_processedServerCredentials.hostName = m_processedServerCredentials.hostName.split(":").at(0); + } + m_processedServerCredentials.userName = userName; + m_processedServerCredentials.secretData = secretData; +} + +void InstallUiController::mountSftpDrive(int serverIndex, const QString &port, const QString &password, const QString &username) +{ + ServerCredentials serverCredentials = m_serversController->getServerCredentials(serverIndex); + ErrorCode errorCode = m_installController->mountSftpDrive(serverCredentials, port, password, username); + if (errorCode != ErrorCode::NoError) { + emit installationErrorOccurred(errorCode); + } +} + +bool InstallUiController::checkSshConnection() +{ + m_privateKeyPassphrase = ""; + + auto passphraseCallback = [this]() { + emit passphraseRequestStarted(); + QEventLoop loop; + QObject::connect(this, &InstallUiController::passphraseRequestFinished, &loop, &QEventLoop::quit); + loop.exec(); + return m_privateKeyPassphrase; + }; + + QString output; + ErrorCode errorCode = m_installController->checkSshConnection(m_processedServerCredentials, output, passphraseCallback); + + if (errorCode != ErrorCode::NoError) { + emit installationErrorOccurred(errorCode); + return false; + } else { + if (output.contains(tr("Please login as the user"))) { + output.replace("\n", ""); + emit wrongInstallationUser(output); + return false; + } + } + return true; +} + +void InstallUiController::setEncryptedPassphrase(QString passphrase) +{ + m_privateKeyPassphrase = passphrase; + emit passphraseRequestFinished(); +} + +void InstallUiController::addEmptyServer() +{ + SelfHostedServerConfig serverConfig; + serverConfig.hostName = m_processedServerCredentials.hostName; + serverConfig.userName = m_processedServerCredentials.userName; + serverConfig.password = m_processedServerCredentials.secretData; + serverConfig.port = m_processedServerCredentials.port; + serverConfig.description = m_settingsController->nextAvailableServerName(); + serverConfig.defaultContainer = DockerContainer::None; + + m_serversController->addServer(ServerConfig(serverConfig)); + emit installServerFinished(tr("Server added successfully")); +} + +void InstallUiController::validateConfig() +{ + int serverIndex = m_serversController->getDefaultServerIndex(); + m_installController->validateConfig(serverIndex); +} + +void InstallUiController::updateProtocols(int serverIndex, int containerIndex) +{ + DockerContainer container = static_cast(containerIndex); + ContainerConfig containerConfig = m_serversController->getContainerConfig(serverIndex, container); + containerConfig.container = container; + m_protocolModel->updateModel(containerConfig); +} + +void InstallUiController::openServerSettings(int serverIndex, int containerIndex, int protocolIndex) +{ + updateProtocolConfigModel(serverIndex, containerIndex, protocolIndex); +} + +void InstallUiController::openClientSettings(int serverIndex, int containerIndex, int protocolIndex) +{ + updateProtocolConfigModel(serverIndex, containerIndex, protocolIndex); +} + +int InstallUiController::defaultPort(int protocolIndex) +{ + Proto proto = static_cast(protocolIndex); + return ProtocolUtils::defaultPort(proto); +} + +int InstallUiController::getPortForInstall(int protocolIndex) +{ + Proto proto = static_cast(protocolIndex); + return ProtocolUtils::getPortForInstall(proto); +} + +int InstallUiController::defaultTransportProto(int protocolIndex) +{ + Proto proto = static_cast(protocolIndex); + return static_cast(ProtocolUtils::defaultTransportProto(proto)); +} + +bool InstallUiController::defaultPortChangeable(int protocolIndex) +{ + Proto proto = static_cast(protocolIndex); + return ProtocolUtils::defaultPortChangeable(proto); +} + +bool InstallUiController::defaultTransportProtoChangeable(int protocolIndex) +{ + Proto proto = static_cast(protocolIndex); + return ProtocolUtils::defaultTransportProtoChangeable(proto); +} + +void InstallUiController::updateProtocolConfigModel(int serverIndex, int containerIndex, int protocolIndex) +{ + DockerContainer container = static_cast(containerIndex); + ContainerConfig containerConfig = m_serversController->getContainerConfig(serverIndex, container); + containerConfig.container = container; + Proto protocolType = static_cast(protocolIndex); + + auto updateIfPresent = [&](auto* model, auto* config) { + if (model && config) model->updateModel(container, *config); + }; + + switch (protocolType) { + case Proto::Awg: updateIfPresent(m_awgConfigModel, containerConfig.getAwgProtocolConfig()); break; + case Proto::WireGuard: updateIfPresent(m_wireGuardConfigModel, containerConfig.getWireGuardProtocolConfig()); break; + case Proto::OpenVpn: updateIfPresent(m_openVpnConfigModel, containerConfig.getOpenVpnProtocolConfig()); break; + case Proto::Xray: updateIfPresent(m_xrayConfigModel, containerConfig.getXrayProtocolConfig()); break; + case Proto::TorWebSite: updateIfPresent(m_torConfigModel, containerConfig.getTorProtocolConfig()); break; + case Proto::Sftp: updateIfPresent(m_sftpConfigModel, containerConfig.getSftpProtocolConfig()); break; + case Proto::Socks5Proxy: updateIfPresent(m_socks5ConfigModel, containerConfig.getSocks5ProxyProtocolConfig()); break; +#ifdef Q_OS_WINDOWS + case Proto::Ikev2: updateIfPresent(m_ikev2ConfigModel, containerConfig.getIkev2ProtocolConfig()); break; +#endif + default: break; + } +} + diff --git a/client/ui/controllers/selfhosted/installUiController.h b/client/ui/controllers/selfhosted/installUiController.h new file mode 100644 index 000000000..89e0291cf --- /dev/null +++ b/client/ui/controllers/selfhosted/installUiController.h @@ -0,0 +1,151 @@ +#ifndef INSTALLUICONTROLLER_H +#define INSTALLUICONTROLLER_H + +#include +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/controllers/serversController.h" +#include "core/controllers/settingsController.h" +#include "core/controllers/selfhosted/usersController.h" +#include "core/controllers/selfhosted/installController.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/models/containerConfig.h" +#include "ui/models/protocolsModel.h" +#include "ui/models/protocols/awgConfigModel.h" +#include "ui/models/protocols/wireguardConfigModel.h" +#include "ui/models/protocols/openvpnConfigModel.h" +#include "ui/models/protocols/xrayConfigModel.h" +#ifdef Q_OS_WINDOWS +#include "ui/models/protocols/ikev2ConfigModel.h" +#endif +#include "ui/models/services/sftpConfigModel.h" +#include "ui/models/services/socks5ProxyConfigModel.h" +#include "ui/models/services/torConfigModel.h" +#include "core/models/protocols/sftpProtocolConfig.h" +#include "core/models/protocols/socks5ProxyProtocolConfig.h" + +class InstallUiController : public QObject +{ + Q_OBJECT +public: + explicit InstallUiController(InstallController* installController, + ServersController* serversController, + SettingsController* settingsController, + ProtocolsModel* protocolsModel, + UsersController* usersController, + AwgConfigModel* awgConfigModel, + WireGuardConfigModel* wireGuardConfigModel, + OpenVpnConfigModel* openVpnConfigModel, + XrayConfigModel* xrayConfigModel, + TorConfigModel* torConfigModel, +#ifdef Q_OS_WINDOWS + Ikev2ConfigModel* ikev2ConfigModel, +#endif + SftpConfigModel* sftpConfigModel, + Socks5ProxyConfigModel* socks5ConfigModel, + QObject *parent = nullptr); + ~InstallUiController(); + +public slots: + void install(DockerContainer container, int port, TransportProto transportProto, int serverIndex); + void setProcessedServerCredentials(const QString &hostName, const QString &userName, const QString &secretData); + void clearProcessedServerCredentials(); + + void scanServerForInstalledContainers(int serverIndex); + + void updateContainer(int serverIndex, int containerIndex, int protocolIndex); + + void removeServer(int serverIndex); + void rebootServer(int serverIndex); + void removeAllContainers(int serverIndex); + void removeContainer(int serverIndex, int containerIndex); + + void clearCachedProfile(int serverIndex, int containerIndex); + + QRegularExpression ipAddressRegExp(); + + void mountSftpDrive(int serverIndex, const QString &port, const QString &password, const QString &username); + + bool checkSshConnection(); + + void setEncryptedPassphrase(QString passphrase); + + void addEmptyServer(); + + void validateConfig(); + + Q_INVOKABLE void updateProtocols(int serverIndex, int containerIndex); + + void openServerSettings(int serverIndex, int containerIndex, int protocolIndex); + void openClientSettings(int serverIndex, int containerIndex, int protocolIndex); + + int defaultPort(int protocolIndex); + int getPortForInstall(int protocolIndex); + int defaultTransportProto(int protocolIndex); + bool defaultPortChangeable(int protocolIndex); + bool defaultTransportProtoChangeable(int protocolIndex); + +signals: + void installContainerFinished(const QString &finishMessage, bool isServiceInstall); + void installServerFinished(const QString &finishMessage); + + void updateContainerFinished(const QString &message); + + void scanServerFinished(bool isInstalledContainerFound); + + void rebootServerFinished(const QString &finishedMessage); + void removeServerFinished(const QString &finishedMessage); + void removeAllContainersFinished(const QString &finishedMessage); + void removeContainerFinished(const QString &finishedMessage); + + void installationErrorOccurred(ErrorCode errorCode); + void wrongInstallationUser(const QString &message); + + void serverAlreadyExists(int serverIndex); + + void passphraseRequestStarted(); + void passphraseRequestFinished(); + + 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: + + InstallController* m_installController; + ServersController* m_serversController; + SettingsController* m_settingsController; + ProtocolsModel* m_protocolModel; + UsersController* m_usersController; + + AwgConfigModel* m_awgConfigModel; + WireGuardConfigModel* m_wireGuardConfigModel; + OpenVpnConfigModel* m_openVpnConfigModel; + XrayConfigModel* m_xrayConfigModel; + TorConfigModel* m_torConfigModel; +#ifdef Q_OS_WINDOWS + Ikev2ConfigModel* m_ikev2ConfigModel; +#endif + SftpConfigModel* m_sftpConfigModel; + Socks5ProxyConfigModel* m_socks5ConfigModel; + + ServerCredentials m_processedServerCredentials; + + QString m_privateKeyPassphrase; + + void updateProtocolConfigModel(int serverIndex, int containerIndex, int protocolIndex); +}; + +#endif // INSTALLUICONTROLLER_H diff --git a/client/ui/controllers/serversUiController.cpp b/client/ui/controllers/serversUiController.cpp new file mode 100644 index 000000000..ccafe0f4b --- /dev/null +++ b/client/ui/controllers/serversUiController.cpp @@ -0,0 +1,491 @@ +#include "serversUiController.h" + +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" +#include "core/utils/api/apiUtils.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 +#include +#include "core/models/serverConfig.h" +#include "core/models/protocolConfig.h" +#include "core/models/containerConfig.h" +#include "core/models/protocols/awgProtocolConfig.h" + +using namespace amnezia; + +namespace +{ + namespace configKey + { + constexpr char apiConfig[] = "api_config"; + constexpr char serverCountryCode[] = "server_country_code"; + constexpr char serverCountryName[] = "server_country_name"; + constexpr char userCountryCode[] = "user_country_code"; + constexpr char serviceType[] = "service_type"; + } +} + +ServersUiController::ServersUiController(ServersController* serversController, + SettingsController* settingsController, + ServersModel* serversModel, + ContainersModel* containersModel, + ContainersModel* defaultServerContainersModel, + QObject *parent) + : QObject(parent), + m_serversController(serversController), + m_settingsController(settingsController), + m_serversModel(serversModel), + m_containersModel(containersModel), + m_defaultServerContainersModel(defaultServerContainersModel) +{ +} + +void ServersUiController::removeServer(int index) +{ + m_serversController->removeServer(index); + updateModel(); +} + +void ServersUiController::editServerName(int index, const QString &name) +{ + ServerConfig serverConfig = m_serversController->getServerConfig(index); + + if (serverConfig.isApiV1()) { + ApiV1ServerConfig* apiV1 = serverConfig.as(); + if (apiV1) { + apiV1->name = name; + } + } else if (serverConfig.isApiV2()) { + ApiV2ServerConfig* apiV2 = serverConfig.as(); + if (apiV2) { + apiV2->name = name; + apiV2->nameOverriddenByUser = true; + } + } else { + serverConfig.visit([&name](auto& arg) { + arg.description = name; + }); + } + + m_serversController->editServer(index, serverConfig); + updateModel(); +} + +void ServersUiController::setDefaultServerIndex(int index) +{ + m_serversController->setDefaultServerIndex(index); + updateModel(); + emit defaultServerIndexChanged(index); +} + +void ServersUiController::setDefaultContainer(int serverIndex, int containerIndex) +{ + auto container = static_cast(containerIndex); + m_serversController->setDefaultContainer(serverIndex, container); + updateModel(); +} + +void ServersUiController::toggleAmneziaDns(bool enabled) +{ + m_settingsController->toggleAmneziaDns(enabled); + updateModel(); +} + +void ServersUiController::onDefaultServerChanged(int index) +{ + setProcessedServerIndex(index); + updateModel(); + updateDefaultServerContainersModel(); + emit defaultServerIndexChanged(index); +} + +void ServersUiController::updateModel() +{ + int defaultIndex = m_serversController->getDefaultServerIndex(); + bool wasEmpty = !hasServersFromGatewayApi(); + int serversCount = m_serversController->getServersCount(); + + if (m_processedServerIndex >= serversCount) { + setProcessedServerIndex(defaultIndex); + } else if (m_processedServerIndex >= 0) { + setProcessedServerIndex(m_processedServerIndex); + } + + m_serversModel->updateModel(m_serversController->getServers(), defaultIndex, m_settingsController->isAmneziaDnsEnabled()); + + + updateContainersModel(); + updateDefaultServerContainersModel(); + + bool isEmpty = !hasServersFromGatewayApi(); + if (wasEmpty != isEmpty) { + emit hasServersFromGatewayApiChanged(); + } + + emit defaultServerIndexChanged(defaultIndex); +} + +int ServersUiController::getDefaultServerIndex() const +{ + return m_serversController->getDefaultServerIndex(); +} + +QString ServersUiController::getDefaultServerName() const +{ + int defaultIndex = getDefaultServerIndex(); + return m_serversController->getServerConfig(defaultIndex).displayName(); +} + +QString ServersUiController::getDefaultServerDefaultContainerName() const +{ + int defaultIndex = getDefaultServerIndex(); + const ServerConfig server = m_serversController->getServerConfig(defaultIndex); + return ContainerUtils::containerHumanNames().value(server.defaultContainer()); +} + +QString ServersUiController::getDefaultServerDescriptionCollapsed() const +{ + int defaultIndex = getDefaultServerIndex(); + const ServerConfig server = m_serversController->getServerConfig(defaultIndex); + QString description = getDefaultServerDescription(server, defaultIndex); + + if (server.isApiConfig()) { + return description; + } + + DockerContainer container = server.defaultContainer(); + QString containerName = ContainerUtils::containerHumanNames().value(container); + QString protocolVersion; + QString hostName = server.hostName(); + + if (ContainerUtils::isAwgContainer(container)) { + ContainerConfig containerConfig = server.containerConfig(container); + if (auto* awgProtocolConfig = containerConfig.getAwgProtocolConfig()) { + QString version = awgProtocolConfig->serverConfig.protocolVersion; + if (version == protocols::awg::awgV2) { + protocolVersion = QObject::tr(" (version 2)"); + } else if (version == protocols::awg::awgV1_5) { + protocolVersion = QObject::tr(" (version 1.5)"); + } + + if (container == DockerContainer::Awg && !awgProtocolConfig->serverConfig.isThirdPartyConfig) { + containerName = "AmneziaWG Legacy"; + } + } + } + + return description + containerName + protocolVersion + " | " + hostName; +} + +QString ServersUiController::getDefaultServerImagePathCollapsed() const +{ + int defaultIndex = getDefaultServerIndex(); + const ServerConfig server = m_serversController->getServerConfig(defaultIndex); + + if (server.isApiV2()) { + const ApiV2ServerConfig* apiV2 = server.as(); + if (!apiV2) return QString(); + const QString countryCode = apiV2->apiConfig.serverCountryCode; + if (countryCode.isEmpty()) { + return ""; + } + return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode.toUpper()); + } + return ""; +} + +QString ServersUiController::getDefaultServerDescriptionExpanded() const +{ + int defaultIndex = getDefaultServerIndex(); + const ServerConfig server = m_serversController->getServerConfig(defaultIndex); + QString description = getDefaultServerDescription(server, defaultIndex); + + if (server.isApiConfig()) { + return description; + } + + return description + server.hostName(); +} + +bool ServersUiController::isDefaultServerDefaultContainerHasSplitTunneling() const +{ + int defaultIndex = getDefaultServerIndex(); + const ServerConfig server = m_serversController->getServerConfig(defaultIndex); + DockerContainer defaultContainer = server.defaultContainer(); + + ContainerConfig containerConfig = server.containerConfig(defaultContainer); + + if (defaultContainer == DockerContainer::Awg || defaultContainer == DockerContainer::WireGuard) { + auto hasSplitTunnelingFromAllowedIps = [](const QStringList& allowedIps, const QString& nativeConfig) -> bool { + bool hasSplitTunneling = !allowedIps.isEmpty() && !allowedIps.contains("0.0.0.0/0"); + if (!hasSplitTunneling && !nativeConfig.isEmpty()) { + hasSplitTunneling = nativeConfig.contains("AllowedIPs") + && !nativeConfig.contains("AllowedIPs = 0.0.0.0/0, ::/0"); + } + return hasSplitTunneling; + }; + + if (defaultContainer == DockerContainer::Awg) { + if (const auto* awgConfig = containerConfig.getAwgProtocolConfig()) { + if (awgConfig->hasClientConfig()) { + return hasSplitTunnelingFromAllowedIps( + awgConfig->clientConfig->allowedIps, + awgConfig->clientConfig->nativeConfig + ); + } + } + } else if (defaultContainer == DockerContainer::WireGuard) { + if (const auto* wgConfig = containerConfig.getWireGuardProtocolConfig()) { + if (wgConfig->hasClientConfig()) { + return hasSplitTunnelingFromAllowedIps( + wgConfig->clientConfig->allowedIps, + wgConfig->clientConfig->nativeConfig + ); + } + } + } + return false; + } else if (defaultContainer == DockerContainer::OpenVpn) { + if (const auto* ovpnConfig = containerConfig.getOpenVpnProtocolConfig()) { + if (ovpnConfig->hasClientConfig()) { + return !ovpnConfig->clientConfig->nativeConfig.isEmpty() + && !ovpnConfig->clientConfig->nativeConfig.contains("redirect-gateway"); + } + } + } + return false; +} + +bool ServersUiController::isDefaultServerFromApi() const +{ + int defaultIndex = getDefaultServerIndex(); + const ServerConfig server = m_serversController->getServerConfig(defaultIndex); + const int configVersion = server.configVersion(); + return configVersion == apiDefs::ConfigSource::Telegram + || configVersion == apiDefs::ConfigSource::AmneziaGateway; +} + +int ServersUiController::getProcessedServerIndex() const +{ + return m_processedServerIndex; +} + +int ServersUiController::getProcessedContainerIndex() const +{ + return m_processedContainerIndex; +} + +void ServersUiController::setProcessedContainerIndex(int index) +{ + if (m_processedContainerIndex != index) { + m_processedContainerIndex = index; + m_containersModel->setProcessedContainerIndex(index); + emit processedContainerIndexChanged(m_processedContainerIndex); + } +} + +void ServersUiController::setProcessedServerIndex(int index) +{ + if (index >= m_serversController->getServersCount()) { + return; + } + + if (m_processedServerIndex != index) { + m_processedServerIndex = index; + m_serversModel->setProcessedServerIndex(index); + + if (index >= 0) { + updateContainersModel(); + + ServerConfig server = m_serversController->getServerConfig(index); + setProcessedContainerIndex(static_cast(server.defaultContainer())); + + if (server.isApiV2()) { + const ApiV2ServerConfig* apiV2 = server.as(); + if (apiV2 && !apiV2->apiConfig.availableCountries.isEmpty()) { + emit updateApiCountryModel(); + } + emit updateApiServicesModel(); + } + } + + emit processedServerIndexChanged(m_processedServerIndex); + } +} + +bool ServersUiController::processedServerIsPremium() const +{ + ServerConfig server = m_serversController->getServerConfig(m_processedServerIndex); + if (server.isApiV1()) { + const ApiV1ServerConfig* apiV1 = server.as(); + return apiV1 ? apiV1->isPremium() : false; + } else if (server.isApiV2()) { + const ApiV2ServerConfig* apiV2 = server.as(); + return apiV2 ? (apiV2->isPremium() || apiV2->isExternalPremium()) : false; + } + return false; +} + +const ServerCredentials ServersUiController::getProcessedServerCredentials() const +{ + return m_serversController->getServerCredentials(m_processedServerIndex); +} + +bool ServersUiController::isDefaultServerCurrentlyProcessed() const +{ + return m_serversController->getDefaultServerIndex() == m_processedServerIndex; +} + +bool ServersUiController::isProcessedServerHasWriteAccess() const +{ + ServerCredentials credentials = m_serversController->getServerCredentials(m_processedServerIndex); + return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); +} + + +QString ServersUiController::getDefaultServerDescription(const ServerConfig& server, int index) const +{ + QString description; + + if (server.isApiV2()) { + const ApiV2ServerConfig* apiV2 = server.as(); + if (!apiV2) return QString(); + if (!apiV2->apiConfig.serverCountryCode.isEmpty()) { + return apiV2->apiConfig.serverCountryName; + } + return apiV2->description; + } else if (server.isApiV1()) { + const ApiV1ServerConfig* apiV1 = server.as(); + return apiV1 ? apiV1->description : QString(); + } else { + ServerCredentials credentials = m_serversController->getServerCredentials(index); + if (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()) { + bool isAmneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled(); + if (isAmneziaDnsEnabled && isAmneziaDnsContainerInstalled(index)) { + description += "Amnezia DNS | "; + } + } else { + if (server.dns1() == protocols::dns::amneziaDnsIp) { + description += "Amnezia DNS | "; + } + } + return description; + } +} + +bool ServersUiController::isAmneziaDnsContainerInstalled(int serverIndex) const +{ + const ServerConfig server = m_serversController->getServerConfig(serverIndex); + QMap containers = server.containers(); + + return containers.contains(DockerContainer::Dns); +} + +bool ServersUiController::hasServersFromGatewayApi() const +{ + QVector servers = m_serversController->getServers(); + for (const ServerConfig &server : servers) { + if (server.isApiV2()) { + return true; + } + } + return false; +} + +bool ServersUiController::isAdVisible() const +{ + int defaultIndex = getDefaultServerIndex(); + if (defaultIndex < 0) { + return false; + } + ServerConfig server = m_serversController->getServerConfig(defaultIndex); + if (server.isApiV2()) { + const ApiV2ServerConfig* apiV2 = server.as(); + if (!apiV2) return false; + return apiV2->apiConfig.serviceInfo.isAdVisible; + } + return false; +} + +QString ServersUiController::adHeader() const +{ + int defaultIndex = getDefaultServerIndex(); + if (defaultIndex < 0) { + return QString(); + } + ServerConfig server = m_serversController->getServerConfig(defaultIndex); + if (server.isApiV2()) { + const ApiV2ServerConfig* apiV2 = server.as(); + if (!apiV2) return QString(); + return apiV2->apiConfig.serviceInfo.adHeader; + } + return QString(); +} + +QString ServersUiController::adDescription() const +{ + int defaultIndex = getDefaultServerIndex(); + if (defaultIndex < 0) { + return QString(); + } + ServerConfig server = m_serversController->getServerConfig(defaultIndex); + if (server.isApiV2()) { + const ApiV2ServerConfig* apiV2 = server.as(); + if (!apiV2) return QString(); + return apiV2->apiConfig.serviceInfo.adDescription; + } + return QString(); +} + +void ServersUiController::updateContainersModel() +{ + if (m_processedServerIndex < 0 || m_processedServerIndex >= m_serversController->getServersCount()) { + return; + } + ServerConfig server = m_serversController->getServerConfig(m_processedServerIndex); + QMap containers = server.containers(); + m_containersModel->updateModel(containers); +} + +void ServersUiController::updateDefaultServerContainersModel() +{ + int defaultIndex = m_serversController->getDefaultServerIndex(); + if (defaultIndex < 0 || defaultIndex >= m_serversController->getServersCount()) { + return; + } + ServerConfig server = m_serversController->getServerConfig(defaultIndex); + QMap containers = server.containers(); + m_defaultServerContainersModel->updateModel(containers); +} + +QStringList ServersUiController::getAllInstalledServicesName(int serverIndex) const +{ + QStringList servicesName; + ServerConfig server = m_serversController->getServerConfig(serverIndex); + QMap containers = server.containers(); + + for (auto it = containers.begin(); it != containers.end(); ++it) { + DockerContainer container = it.key(); + if (ContainerUtils::containerService(container) == ServiceType::Other) { + if (container == DockerContainer::Dns) { + servicesName.append("DNS"); + } else if (container == DockerContainer::Sftp) { + servicesName.append("SFTP"); + } else if (container == DockerContainer::TorWebSite) { + servicesName.append("TOR"); + } else if (container == DockerContainer::Socks5Proxy) { + servicesName.append("SOCKS5"); + } + } + } + servicesName.sort(); + return servicesName; +} + diff --git a/client/ui/controllers/serversUiController.h b/client/ui/controllers/serversUiController.h new file mode 100644 index 000000000..7d16362af --- /dev/null +++ b/client/ui/controllers/serversUiController.h @@ -0,0 +1,115 @@ +#ifndef SERVERSUICONTROLLER_H +#define SERVERSUICONTROLLER_H + +#include + +#include +#include +#include + +#include "core/controllers/serversController.h" +#include "core/controllers/settingsController.h" +#include "ui/models/serversModel.h" +#include "ui/models/containersModel.h" +#include "core/models/serverConfig.h" + +class ServersUiController : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int defaultIndex READ getDefaultServerIndex NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerDefaultContainerName READ getDefaultServerDefaultContainerName NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerDescriptionCollapsed READ getDefaultServerDescriptionCollapsed NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerImagePathCollapsed READ getDefaultServerImagePathCollapsed NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerDescriptionExpanded READ getDefaultServerDescriptionExpanded NOTIFY defaultServerIndexChanged) + Q_PROPERTY(bool isDefaultServerDefaultContainerHasSplitTunneling READ isDefaultServerDefaultContainerHasSplitTunneling NOTIFY defaultServerIndexChanged) + Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged) + + Q_PROPERTY(int processedIndex 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 hasServersFromGatewayApi READ hasServersFromGatewayApi NOTIFY hasServersFromGatewayApiChanged) + + Q_PROPERTY(bool isAdVisible READ isAdVisible NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString adHeader READ adHeader NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString adDescription READ adDescription NOTIFY defaultServerIndexChanged) + +public: + explicit ServersUiController(ServersController* serversController, + SettingsController* settingsController, + ServersModel* serversModel, + ContainersModel* containersModel, + ContainersModel* defaultServerContainersModel, + QObject *parent = nullptr); + +public slots: + void removeServer(int index); + void editServerName(int index, const QString &name); + void setDefaultServerIndex(int index); + void setDefaultContainer(int serverIndex, int containerIndex); + void toggleAmneziaDns(bool enabled); + void onDefaultServerChanged(int index); + + // Getters for properties + int getDefaultServerIndex() const; + QString getDefaultServerName() const; + QString getDefaultServerDefaultContainerName() const; + QString getDefaultServerDescriptionCollapsed() const; + QString getDefaultServerImagePathCollapsed() const; + QString getDefaultServerDescriptionExpanded() const; + bool isDefaultServerDefaultContainerHasSplitTunneling() const; + bool isDefaultServerFromApi() const; + + int getProcessedServerIndex() const; + void setProcessedServerIndex(int index); + int getProcessedContainerIndex() const; + void setProcessedContainerIndex(int index); + bool processedServerIsPremium() const; + + const ServerCredentials getProcessedServerCredentials() const; + bool isDefaultServerCurrentlyProcessed() const; + bool isProcessedServerHasWriteAccess() const; + + bool hasServersFromGatewayApi() const; + + bool isAdVisible() const; + QString adHeader() const; + QString adDescription() const; + + QStringList getAllInstalledServicesName(int serverIndex) const; + +signals: + void errorOccurred(const QString &errorMessage); + void finished(const QString &message); + void defaultServerIndexChanged(int index); + void processedServerIndexChanged(int index); + void processedContainerIndexChanged(int index); + void hasServersFromGatewayApiChanged(); + void updateApiCountryModel(); + void updateApiServicesModel(); + +public: + void updateModel(); + +private: + QString getDefaultServerDescription(const ServerConfig& server, int index) const; + bool isAmneziaDnsContainerInstalled(int serverIndex) const; + + void updateContainersModel(); + void updateDefaultServerContainersModel(); + void updateApiModelsForProcessedServer(); + + ServersController* m_serversController; + SettingsController* m_settingsController; + ServersModel* m_serversModel; + ContainersModel* m_containersModel; + ContainersModel* m_defaultServerContainersModel; + + int m_processedServerIndex = -1; + int m_processedContainerIndex = -1; +}; + +#endif // SERVERSUICONTROLLER_H + diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp deleted file mode 100644 index 5363ab220..000000000 --- a/client/ui/controllers/settingsController.cpp +++ /dev/null @@ -1,535 +0,0 @@ -#include "settingsController.h" - -#include -#include - -#include "logger.h" -#include "systemController.h" -#include "ui/qautostart.h" -#include "amnezia_application.h" -#include "version.h" -#ifdef Q_OS_ANDROID - #include "platforms/android/android_controller.h" -#endif - -#if defined(Q_OS_IOS) || defined(MACOS_NE) - #include -#endif - -SettingsController::SettingsController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const QSharedPointer &languageModel, - const QSharedPointer &sitesModel, - const QSharedPointer &appSplitTunnelingModel, - const std::shared_ptr &settings, QObject *parent) - : QObject(parent), - m_serversModel(serversModel), - m_containersModel(containersModel), - m_languageModel(languageModel), - m_sitesModel(sitesModel), - m_appSplitTunnelingModel(appSplitTunnelingModel), - m_settings(settings) -{ - m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH); - checkIfNeedDisableLogs(); -#ifdef Q_OS_ANDROID - connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsController::onNotificationStateChanged); - connect(AndroidController::instance(), &AndroidController::imeInsetsChanged, this, [this](int heightDp) { - m_imeHeight = heightDp; - emit imeHeightChanged(heightDp); - emit safeAreaBottomMarginChanged(); - }); - connect(AndroidController::instance(), &AndroidController::systemBarsInsetsChanged, this, [this](int navBarHeightDp, int statusBarHeightDp) { - m_cachedNavigationBarHeight = navBarHeightDp; - m_cachedStatusBarHeight = statusBarHeightDp; - emit safeAreaBottomMarginChanged(); - emit safeAreaTopMarginChanged(); - }); - connect(AndroidController::instance(), &AndroidController::activityPaused, this, &SettingsController::activityPaused); - connect(AndroidController::instance(), &AndroidController::activityResumed, this, &SettingsController::activityResumed); -#endif - - m_isDevModeEnabled = m_settings->isDevGatewayEnv(); - toggleDevGatewayEnv(m_isDevModeEnabled); -} - -QString getPlatformName() -{ -#if defined(Q_OS_WINDOWS) - return "Windows"; -#elif defined(Q_OS_ANDROID) - return "Android"; -#elif defined(Q_OS_LINUX) - return "Linux"; -#elif defined(Q_OS_MACX) - return "MacOS"; -#elif defined(Q_OS_IOS) - return "iOS"; -#else - return "Unknown"; -#endif -} - -void SettingsController::toggleAmneziaDns(bool enable) -{ - m_settings->setUseAmneziaDns(enable); - emit amneziaDnsToggled(enable); -} - -bool SettingsController::isAmneziaDnsEnabled() -{ - return m_settings->useAmneziaDns(); -} - -QString SettingsController::getPrimaryDns() -{ - return m_settings->primaryDns(); -} - -void SettingsController::setPrimaryDns(const QString &dns) -{ - m_settings->setPrimaryDns(dns); - emit primaryDnsChanged(); -} - -QString SettingsController::getSecondaryDns() -{ - return m_settings->secondaryDns(); -} - -void SettingsController::setSecondaryDns(const QString &dns) -{ - return m_settings->setSecondaryDns(dns); - emit secondaryDnsChanged(); -} - -bool SettingsController::isLoggingEnabled() -{ - return m_settings->isSaveLogs(); -} - -void SettingsController::toggleLogging(bool enable) -{ - m_settings->setSaveLogs(enable); -#if defined(Q_OS_IOS) - AmneziaVPN::toggleLogging(enable); -#endif - if (enable == true) { - qInfo().noquote() << QString("Logging has enabled on %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH); - qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); - } - emit loggingStateChanged(); -} - -void SettingsController::openLogsFolder() -{ - Logger::openLogsFolder(false); -} - -void SettingsController::openServiceLogsFolder() -{ - Logger::openLogsFolder(true); -} - -void SettingsController::exportLogsFile(const QString &fileName) -{ -#ifdef Q_OS_ANDROID - AndroidController::instance()->exportLogsFile(fileName); -#else - SystemController::saveFile(fileName, Logger::getLogFile()); -#endif -} - -void SettingsController::exportServiceLogsFile(const QString &fileName) -{ -#ifdef Q_OS_ANDROID - AndroidController::instance()->exportLogsFile(fileName); -#else - SystemController::saveFile(fileName, Logger::getServiceLogFile()); -#endif -} - -void SettingsController::clearLogs() -{ -#ifdef Q_OS_ANDROID - AndroidController::instance()->clearLogs(); -#else - Logger::clearLogs(false); - Logger::clearServiceLogs(); -#endif - - qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH); - qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); - qInfo().noquote() << QString("SSL backend: %1").arg(QSslSocket::sslLibraryVersionString()); -} - -void SettingsController::backupAppConfig(const QString &fileName) -{ - QByteArray data = m_settings->backupAppConfig(); - QJsonDocument doc = QJsonDocument::fromJson(data); - QJsonObject config = doc.object(); - - config["AppPlatform"] = getPlatformName(); - config["Conf/autoStart"] = Autostart::isAutostart(); - config["Conf/killSwitchEnabled"] = isKillSwitchEnabled(); - config["Conf/strictKillSwitchEnabled"] = isStrictKillSwitchEnabled(); - config["Conf/useAmneziaDns"] = isAmneziaDnsEnabled(); - - SystemController::saveFile(fileName, QJsonDocument(config).toJson()); -} - -void SettingsController::restoreAppConfig(const QString &fileName) -{ - QByteArray data; - if (!SystemController::readFile(fileName, data)) { - emit changeSettingsErrorOccurred(tr("Can't open file: %1").arg(fileName)); - return; - } - restoreAppConfigFromData(data); -} - -void SettingsController::restoreAppConfigFromData(const QByteArray &data) -{ - bool ok = m_settings->restoreAppConfig(data); - if (ok) { - QJsonObject newConfigData = QJsonDocument::fromJson(data).object(); - -#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) || defined(Q_OS_MACX) - bool autoStart = false; - if (newConfigData.contains("Conf/autoStart")) { - autoStart = newConfigData["Conf/autoStart"].toBool(); - } - toggleAutoStart(autoStart); -#endif - - m_serversModel->resetModel(); - m_languageModel->changeLanguage( - static_cast(m_languageModel->getCurrentLanguageIndex())); - -#if defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID) - int appSplitTunnelingRouteMode = newConfigData.value("Conf/appsRouteMode").toInt(); - bool appSplittunnelingEnabled = - newConfigData.value("Conf/appsSplitTunnelingEnabled").toVariant().toString().toLower() == "true"; - m_appSplitTunnelingModel->setRouteMode(appSplitTunnelingRouteMode); - - #if defined(Q_OS_WINDOWS) - m_appSplitTunnelingModel->setRouteMode(static_cast(Settings::AppsRouteMode::VpnAllExceptApps)); - #endif - - if (newConfigData.contains("AppPlatform")) { //if backup is from a new version - if (newConfigData.value("AppPlatform").toString() != getPlatformName()) { - m_appSplitTunnelingModel->clearAppsList(); - } - } - - m_appSplitTunnelingModel->toggleSplitTunneling(appSplittunnelingEnabled); -#endif - - int siteSplitTunnelingRouteMode = newConfigData.value("Conf/routeMode").toInt(); - bool siteSplittunnelingEnabled = - newConfigData.value("Conf/sitesSplitTunnelingEnabled").toVariant().toString().toLower() == "true"; - m_sitesModel->setRouteMode(siteSplitTunnelingRouteMode); - m_sitesModel->toggleSplitTunneling(siteSplittunnelingEnabled); - -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - m_settings->setAutoConnect(false); - m_settings->setStartMinimized(false); - m_settings->setKillSwitchEnabled(false); - m_settings->setStrictKillSwitchEnabled(false); -#endif - - bool amneziaDnsEnabled = newConfigData.contains("Conf/useAmneziaDns") - ? newConfigData.value("Conf/useAmneziaDns").toBool() - : m_settings->useAmneziaDns(); - emit amneziaDnsToggled(amneziaDnsEnabled); - - emit restoreBackupFinished(); - } else { - emit changeSettingsErrorOccurred(tr("Backup file is corrupted")); - } -} - -QString SettingsController::getAppVersion() -{ - return m_appVersion; -} - -void SettingsController::clearSettings() -{ - m_settings->clearSettings(); - m_serversModel->resetModel(); - m_languageModel->changeLanguage(m_languageModel->getSystemLanguageEnum()); - - m_sitesModel->setRouteMode(Settings::RouteMode::VpnOnlyForwardSites); - m_sitesModel->toggleSplitTunneling(false); - - m_appSplitTunnelingModel->setRouteMode(Settings::AppsRouteMode::VpnAllExceptApps); - m_appSplitTunnelingModel->toggleSplitTunneling(false); - - toggleAutoStart(false); - - emit changeSettingsFinished(tr("All settings have been reset to default values")); - -#if defined(Q_OS_IOS) || defined(MACOS_NE) - AmneziaVPN::clearSettings(); -#endif -} - -bool SettingsController::isAutoConnectEnabled() -{ - return m_settings->isAutoConnect(); -} - -void SettingsController::toggleAutoConnect(bool enable) -{ - m_settings->setAutoConnect(enable); -} - -bool SettingsController::isAutoStartEnabled() -{ - return Autostart::isAutostart(); -} - -void SettingsController::toggleAutoStart(bool enable) -{ - Autostart::setAutostart(enable); - if (!enable) { - toggleStartMinimized(false); - } -} - -bool SettingsController::isStartMinimizedEnabled() -{ - return m_settings->isStartMinimized(); -} - -void SettingsController::toggleStartMinimized(bool enable) -{ - m_settings->setStartMinimized(enable); - emit startMinimizedChanged(); -} - -bool SettingsController::isNewsNotificationsEnabled() -{ - return m_settings->isNewsNotifications(); -} -void SettingsController::toggleNewsNotificationsEnabled(bool enable) -{ - m_settings->setNewsNotifications(enable); -} - -bool SettingsController::isScreenshotsEnabled() -{ - return m_settings->isScreenshotsEnabled(); -} - -void SettingsController::toggleScreenshotsEnabled(bool enable) -{ - m_settings->setScreenshotsEnabled(enable); -} - -bool SettingsController::isCameraPresent() -{ -#if defined Q_OS_IOS - return true; -#elif defined Q_OS_ANDROID - return AndroidController::instance()->isCameraPresent(); -#else - return false; -#endif -} - -void SettingsController::checkIfNeedDisableLogs() -{ - if (m_settings->isSaveLogs()) { - m_loggingDisableDate = m_settings->getLogEnableDate().addDays(14); - if (m_loggingDisableDate <= QDateTime::currentDateTime()) { - toggleLogging(false); - clearLogs(); - emit loggingDisableByWatcher(); - } - } -} - -bool SettingsController::isKillSwitchEnabled() -{ - return m_settings->isKillSwitchEnabled(); -} - -void SettingsController::toggleKillSwitch(bool enable) -{ - m_settings->setKillSwitchEnabled(enable); - emit killSwitchEnabledChanged(); - if (enable == false) { - emit strictKillSwitchEnabledChanged(false); - } else { - emit strictKillSwitchEnabledChanged(isStrictKillSwitchEnabled()); - } -} - -bool SettingsController::isStrictKillSwitchEnabled() -{ - return m_settings->isStrictKillSwitchEnabled(); -} - -void SettingsController::toggleStrictKillSwitch(bool enable) -{ - m_settings->setStrictKillSwitchEnabled(enable); - emit strictKillSwitchEnabledChanged(enable); -} - -bool SettingsController::isNotificationPermissionGranted() -{ -#ifdef Q_OS_ANDROID - return AndroidController::instance()->isNotificationPermissionGranted(); -#else - return true; -#endif -} - -void SettingsController::requestNotificationPermission() -{ -#ifdef Q_OS_ANDROID - AndroidController::instance()->requestNotificationPermission(); -#endif -} - -QString SettingsController::getInstallationUuid() -{ - return m_settings->getInstallationUuid(false); -} - -void SettingsController::enableDevMode() -{ - m_isDevModeEnabled = true; - emit devModeEnabled(); -} - -bool SettingsController::isDevModeEnabled() -{ - return m_isDevModeEnabled; -} - -void SettingsController::resetGatewayEndpoint() -{ - m_settings->resetGatewayEndpoint(); - emit gatewayEndpointChanged(m_settings->getGatewayEndpoint()); -} - -void SettingsController::setGatewayEndpoint(const QString &endpoint) -{ - m_settings->setGatewayEndpoint(endpoint); - emit gatewayEndpointChanged(endpoint); -} - -QString SettingsController::getGatewayEndpoint() -{ - return m_settings->isDevGatewayEnv() ? "Dev endpoint" : m_settings->getGatewayEndpoint(); -} - -bool SettingsController::isDevGatewayEnv() -{ - return m_settings->isDevGatewayEnv(); -} - -void SettingsController::toggleDevGatewayEnv(bool enabled) -{ - m_settings->toggleDevGatewayEnv(enabled); - if (enabled) { - m_settings->setDevGatewayEndpoint(); - } else { - m_settings->resetGatewayEndpoint(); - } - emit gatewayEndpointChanged(m_settings->getGatewayEndpoint()); - emit devGatewayEnvChanged(enabled); -} - -bool SettingsController::isOnTv() -{ -#ifdef Q_OS_ANDROID - return AndroidController::instance()->isOnTv(); -#else - return false; -#endif -} - -bool SettingsController::isEdgeToEdgeEnabled() -{ -#ifdef Q_OS_ANDROID - if (!m_edgeToEdgeCached) { - m_cachedEdgeToEdgeEnabled = AndroidController::instance()->isEdgeToEdgeEnabled(); - m_edgeToEdgeCached = true; - } - return m_cachedEdgeToEdgeEnabled; -#else - return false; -#endif -} - -int SettingsController::getStatusBarHeight() -{ -#ifdef Q_OS_ANDROID - if (m_cachedStatusBarHeight < 0) { - m_cachedStatusBarHeight = AndroidController::instance()->getStatusBarHeight(); - } - return m_cachedStatusBarHeight; -#else - return 0; -#endif -} - -int SettingsController::getNavigationBarHeight() -{ -#ifdef Q_OS_ANDROID - if (m_cachedNavigationBarHeight < 0) { - m_cachedNavigationBarHeight = AndroidController::instance()->getNavigationBarHeight(); - } - return m_cachedNavigationBarHeight; -#else - return 0; -#endif -} - -int SettingsController::getSafeAreaTopMargin() -{ -#ifdef Q_OS_ANDROID - if (isEdgeToEdgeEnabled()) { - int height = getStatusBarHeight(); - int result = height > 0 ? height : 40; // fallback to 40 if system returns 0 - return result; - } -#endif - return 0; -} - -int SettingsController::getSafeAreaBottomMargin() -{ -#ifdef Q_OS_ANDROID - if (isEdgeToEdgeEnabled()) { - if (m_imeHeight > 0) { - return 0; - } - - int height = getNavigationBarHeight(); - int result = height > 0 ? height : 56; // fallback to 56 if system returns 0 - return result; - } -#endif - return 0; -} - -int SettingsController::getImeHeight() -{ - return m_imeHeight; -} - -bool SettingsController::isHomeAdLabelVisible() -{ - return m_settings->isHomeAdLabelVisible(); -} - -void SettingsController::disableHomeAdLabel() -{ - m_settings->disableHomeAdLabel(); - emit isHomeAdLabelVisibleChanged(false); -} diff --git a/client/ui/controllers/settingsUiController.cpp b/client/ui/controllers/settingsUiController.cpp new file mode 100644 index 000000000..a577d3cbc --- /dev/null +++ b/client/ui/controllers/settingsUiController.cpp @@ -0,0 +1,351 @@ +#include "settingsUiController.h" + +#include +#include +#include +#include + +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "logger.h" +#include "systemController.h" +#include "amneziaApplication.h" +#include "version.h" +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" +#endif + +#if defined(Q_OS_IOS) || defined(MACOS_NE) + #include +#endif + +SettingsUiController::SettingsUiController(SettingsController* settingsController, + ServersController* serversController, + LanguageUiController* languageUiController, + QObject *parent) + : QObject(parent), + m_settingsController(settingsController), + m_serversController(serversController), + m_languageUiController(languageUiController) +{ +#ifdef Q_OS_ANDROID + connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsUiController::onNotificationStateChanged); + connect(AndroidController::instance(), &AndroidController::activityPaused, this, &SettingsUiController::activityPaused); + connect(AndroidController::instance(), &AndroidController::activityResumed, this, &SettingsUiController::activityResumed); +#endif + + m_settingsController->checkIfNeedDisableLogs(); + if (m_settingsController->isDevGatewayEnv()) { + m_settingsController->enableDevMode(); + } +} + +void SettingsUiController::toggleAmneziaDns(bool enable) +{ + m_settingsController->toggleAmneziaDns(enable); + emit amneziaDnsToggled(enable); +} + +bool SettingsUiController::isAmneziaDnsEnabled() +{ + return m_settingsController->isAmneziaDnsEnabled(); +} + +QString SettingsUiController::getPrimaryDns() +{ + return m_settingsController->getPrimaryDns(); +} + +void SettingsUiController::setPrimaryDns(const QString &dns) +{ + m_settingsController->setPrimaryDns(dns); + emit primaryDnsChanged(); +} + +QString SettingsUiController::getSecondaryDns() +{ + return m_settingsController->getSecondaryDns(); +} + +void SettingsUiController::setSecondaryDns(const QString &dns) +{ + m_settingsController->setSecondaryDns(dns); + emit secondaryDnsChanged(); +} + +bool SettingsUiController::isLoggingEnabled() +{ + return m_settingsController->isLoggingEnabled(); +} + +void SettingsUiController::toggleLogging(bool enable) +{ + m_settingsController->toggleLogging(enable); +#if defined(Q_OS_IOS) + AmneziaVPN::toggleLogging(enable); +#endif + if (enable == true) { + qInfo().noquote() << QString("Logging has enabled on %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH); + qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); + } + emit loggingStateChanged(); +} + +void SettingsUiController::openLogsFolder() +{ + Logger::openLogsFolder(false); +} + +void SettingsUiController::openServiceLogsFolder() +{ + Logger::openLogsFolder(true); +} + +void SettingsUiController::exportLogsFile(const QString &fileName) +{ +#ifdef Q_OS_ANDROID + AndroidController::instance()->exportLogsFile(fileName); +#else + SystemController::saveFile(fileName, Logger::getLogFile()); +#endif +} + +void SettingsUiController::exportServiceLogsFile(const QString &fileName) +{ +#ifdef Q_OS_ANDROID + AndroidController::instance()->exportLogsFile(fileName); +#else + SystemController::saveFile(fileName, Logger::getServiceLogFile()); +#endif +} + +void SettingsUiController::clearLogs() +{ + m_settingsController->clearLogs(); + + qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH); + qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); + qInfo().noquote() << QString("SSL backend: %1").arg(QSslSocket::sslLibraryVersionString()); +} + +void SettingsUiController::backupAppConfig(const QString &fileName) +{ + QByteArray data = m_settingsController->backupAppConfig(); + SystemController::saveFile(fileName, data); +} + +void SettingsUiController::restoreAppConfig(const QString &fileName) +{ + QFile file(fileName); + + if (!file.open(QIODevice::ReadOnly)) { + emit errorOccurred(ErrorCode::OpenError); + return; + } + + restoreAppConfigFromData(file.readAll()); +} + +void SettingsUiController::restoreAppConfigFromData(const QByteArray &data) +{ + ErrorCode errorCode = m_settingsController->restoreAppConfigFromData(data); + if (errorCode == ErrorCode::NoError) { + emit appLanguageChanged( + static_cast(m_languageUiController->getCurrentLanguageIndex())); + + bool amneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled(); + emit amneziaDnsToggled(amneziaDnsEnabled); + + emit restoreBackupFinished(); + } else { + emit errorOccurred(errorCode); + } +} + +QString SettingsUiController::getAppVersion() +{ + return m_settingsController->getAppVersion(); +} + +void SettingsUiController::clearSettings() +{ + m_settingsController->clearSettings(); + emit resetLanguageToSystem(); + + emit changeSettingsFinished(tr("All settings have been reset to default values")); + +#if defined(Q_OS_IOS) || defined(MACOS_NE) + AmneziaVPN::clearSettings(); +#endif +} + +bool SettingsUiController::isAutoConnectEnabled() +{ + return m_settingsController->isAutoConnectEnabled(); +} + +void SettingsUiController::toggleAutoConnect(bool enable) +{ + m_settingsController->toggleAutoConnect(enable); +} + +bool SettingsUiController::isAutoStartEnabled() +{ + return m_settingsController->isAutoStartEnabled(); +} + +void SettingsUiController::toggleAutoStart(bool enable) +{ + m_settingsController->toggleAutoStart(enable); +} + +bool SettingsUiController::isStartMinimizedEnabled() +{ + return m_settingsController->isStartMinimizedEnabled(); +} + +void SettingsUiController::toggleStartMinimized(bool enable) +{ + m_settingsController->toggleStartMinimized(enable); + emit startMinimizedChanged(); +} + +bool SettingsUiController::isScreenshotsEnabled() +{ + return m_settingsController->isScreenshotsEnabled(); +} + +void SettingsUiController::toggleScreenshotsEnabled(bool enable) +{ + m_settingsController->toggleScreenshotsEnabled(enable); +} + +bool SettingsUiController::isNewsNotificationsEnabled() +{ + return m_settingsController->isNewsNotificationsEnabled(); +} + +void SettingsUiController::toggleNewsNotificationsEnabled(bool enable) +{ + m_settingsController->toggleNewsNotificationsEnabled(enable); +} + +bool SettingsUiController::isCameraPresent() +{ +#if defined Q_OS_IOS + return true; +#elif defined Q_OS_ANDROID + return AndroidController::instance()->isCameraPresent(); +#else + return false; +#endif +} + +bool SettingsUiController::isKillSwitchEnabled() +{ + return m_settingsController->isKillSwitchEnabled(); +} + +void SettingsUiController::toggleKillSwitch(bool enable) +{ + m_settingsController->toggleKillSwitch(enable); + emit killSwitchEnabledChanged(); + if (enable == false) { + emit strictKillSwitchEnabledChanged(false); + } else { + emit strictKillSwitchEnabledChanged(isStrictKillSwitchEnabled()); + } +} + +bool SettingsUiController::isStrictKillSwitchEnabled() +{ + return m_settingsController->isStrictKillSwitchEnabled(); +} + +void SettingsUiController::toggleStrictKillSwitch(bool enable) +{ + m_settingsController->toggleStrictKillSwitch(enable); + emit strictKillSwitchEnabledChanged(enable); +} + +bool SettingsUiController::isNotificationPermissionGranted() +{ +#ifdef Q_OS_ANDROID + return AndroidController::instance()->isNotificationPermissionGranted(); +#else + return true; +#endif +} + +void SettingsUiController::requestNotificationPermission() +{ +#ifdef Q_OS_ANDROID + AndroidController::instance()->requestNotificationPermission(); +#endif +} + +QString SettingsUiController::getInstallationUuid() +{ + return m_settingsController->getInstallationUuid(); +} + +void SettingsUiController::enableDevMode() +{ + m_settingsController->enableDevMode(); + emit devModeEnabled(); +} + +bool SettingsUiController::isDevModeEnabled() +{ + return m_settingsController->isDevModeEnabled(); +} + +void SettingsUiController::resetGatewayEndpoint() +{ + m_settingsController->resetGatewayEndpoint(); + emit gatewayEndpointChanged(m_settingsController->getGatewayEndpoint()); +} + +void SettingsUiController::setGatewayEndpoint(const QString &endpoint) +{ + m_settingsController->setGatewayEndpoint(endpoint); + emit gatewayEndpointChanged(endpoint); +} + +QString SettingsUiController::getGatewayEndpoint() +{ + return m_settingsController->getGatewayEndpoint(); +} + +bool SettingsUiController::isDevGatewayEnv() +{ + return m_settingsController->isDevGatewayEnv(); +} + +void SettingsUiController::toggleDevGatewayEnv(bool enabled) +{ + m_settingsController->toggleDevGatewayEnv(enabled); + emit gatewayEndpointChanged(m_settingsController->getGatewayEndpoint()); + emit devGatewayEnvChanged(enabled); +} + +bool SettingsUiController::isOnTv() +{ +#ifdef Q_OS_ANDROID + return AndroidController::instance()->isOnTv(); +#else + return false; +#endif +} + +bool SettingsUiController::isHomeAdLabelVisible() +{ + return m_settingsController->isHomeAdLabelVisible(); +} + +void SettingsUiController::disableHomeAdLabel() +{ + m_settingsController->disableHomeAdLabel(); + emit isHomeAdLabelVisibleChanged(false); +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsUiController.h similarity index 64% rename from client/ui/controllers/settingsController.h rename to client/ui/controllers/settingsUiController.h index ed20a1e6b..157011e61 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsUiController.h @@ -1,24 +1,24 @@ -#ifndef SETTINGSCONTROLLER_H -#define SETTINGSCONTROLLER_H +#ifndef SETTINGSUICONTROLLER_H +#define SETTINGSUICONTROLLER_H #include -#include "ui/models/containers_model.h" +#include "core/controllers/settingsController.h" +#include "core/controllers/serversController.h" +#include "ui/controllers/languageUiController.h" #include "ui/models/languageModel.h" -#include "ui/models/servers_model.h" -#include "ui/models/sites_model.h" -#include "ui/models/appSplitTunnelingModel.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" -class SettingsController : public QObject +class SettingsUiController : public QObject { Q_OBJECT public: - explicit SettingsController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const QSharedPointer &languageModel, - const QSharedPointer &sitesModel, - const QSharedPointer &appSplitTunnelingModel, - const std::shared_ptr &settings, QObject *parent = nullptr); + explicit SettingsUiController(SettingsController* settingsController, + ServersController* serversController, + LanguageUiController* languageUiController, + QObject *parent = nullptr); Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) @@ -33,9 +33,6 @@ public: Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged) Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged) - Q_PROPERTY(int safeAreaTopMargin READ getSafeAreaTopMargin NOTIFY safeAreaTopMarginChanged) - Q_PROPERTY(int safeAreaBottomMargin READ getSafeAreaBottomMargin NOTIFY safeAreaBottomMarginChanged) - Q_PROPERTY(int imeHeight READ getImeHeight NOTIFY imeHeightChanged) public slots: void toggleAmneziaDns(bool enable); @@ -102,12 +99,6 @@ public slots: void toggleDevGatewayEnv(bool enabled); bool isOnTv(); - bool isEdgeToEdgeEnabled(); - int getStatusBarHeight(); - int getNavigationBarHeight(); - int getSafeAreaTopMargin(); - int getSafeAreaBottomMargin(); - int getImeHeight(); bool isHomeAdLabelVisible(); void disableHomeAdLabel(); @@ -121,7 +112,7 @@ signals: void restoreBackupFinished(); void changeSettingsFinished(const QString &finishedMessage); - void changeSettingsErrorOccurred(const QString &errorMessage); + void errorOccurred(ErrorCode errorCode); void saveFile(const QString &fileName, const QString &data); @@ -131,15 +122,14 @@ signals: void loggingDisableByWatcher(); + void appLanguageChanged(const LanguageSettings::AvailableLanguageEnum language); + void resetLanguageToSystem(); + void onNotificationStateChanged(); void devModeEnabled(); void gatewayEndpointChanged(const QString &endpoint); void devGatewayEnvChanged(bool enabled); - - void imeHeightChanged(int height); - void safeAreaTopMarginChanged(); - void safeAreaBottomMarginChanged(); void activityPaused(); void activityResumed(); @@ -148,28 +138,9 @@ signals: void startMinimizedChanged(); private: - QSharedPointer m_serversModel; - QSharedPointer m_containersModel; - QSharedPointer m_languageModel; - QSharedPointer m_sitesModel; - QSharedPointer m_appSplitTunnelingModel; - - mutable int m_cachedStatusBarHeight = -1; - mutable int m_cachedNavigationBarHeight = -1; - mutable bool m_cachedEdgeToEdgeEnabled = false; - mutable bool m_edgeToEdgeCached = false; - int m_imeHeight = 0; - std::shared_ptr m_settings; - - QString m_appVersion; - - QString getPlatform(); - - QDateTime m_loggingDisableDate; - - bool m_isDevModeEnabled = false; - - void checkIfNeedDisableLogs(); + SettingsController* m_settingsController; + ServersController* m_serversController; + LanguageUiController* m_languageUiController; }; -#endif // SETTINGSCONTROLLER_H +#endif diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp deleted file mode 100644 index 985ed5673..000000000 --- a/client/ui/controllers/sitesController.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include "sitesController.h" - -#include -#include -#include - -#include "systemController.h" -#include "core/networkUtilities.h" - -SitesController::SitesController(const std::shared_ptr &settings, const QSharedPointer &sitesModel, QObject *parent) - : QObject(parent), m_settings(settings), m_sitesModel(sitesModel) -{ -} - -void SitesController::addSite(QString hostname) -{ - if (hostname.isEmpty()) { - return; - } - - if (!hostname.contains(".")) { - emit errorOccurred(tr("Hostname not look like ip adress or domain name")); - return; - } - - if (!NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) { - // get domain name if it present - hostname.replace("https://", ""); - hostname.replace("http://", ""); - hostname.replace("ftp://", ""); - - hostname = hostname.split("/", Qt::SkipEmptyParts).first(); - } - - const auto &resolveCallback = [this](const QHostInfo &hostInfo) { - const QList &addresses = hostInfo.addresses(); - for (const QHostAddress &addr : hostInfo.addresses()) { - if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { - m_sitesModel->addSite(hostInfo.hostName(), addr.toString()); - break; - } - } - }; - - if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) { - m_sitesModel->addSite(hostname, ""); - } else { - m_sitesModel->addSite(hostname, ""); - QHostInfo::lookupHost(hostname, this, resolveCallback); - } - - emit finished(tr("New site added: %1").arg(hostname)); -} - -void SitesController::removeSite(int index) -{ - auto modelIndex = m_sitesModel->index(index); - auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString(); - m_sitesModel->removeSite(modelIndex); - - emit finished(tr("Site removed: %1").arg(hostname)); -} - -void SitesController::removeSites() -{ - m_sitesModel->removeSites(); - - emit finished(tr("Site list cleared!")); -} - -void SitesController::importSites(const QString &fileName, bool replaceExisting) -{ - QByteArray jsonData; - if (!SystemController::readFile(fileName, jsonData)) { - emit errorOccurred(tr("Can't open file: %1").arg(fileName)); - return; - } - - QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); - if (jsonDocument.isNull()) { - emit errorOccurred(tr("Failed to parse JSON data from file: %1").arg(fileName)); - return; - } - - if (!jsonDocument.isArray()) { - emit errorOccurred(tr("The JSON data is not an array in file: %1").arg(fileName)); - return; - } - - auto jsonArray = jsonDocument.array(); - QMap sites; - QStringList ips; - - for (auto jsonValue : jsonArray) { - auto jsonObject = jsonValue.toObject(); - auto hostname = jsonObject.value("hostname").toString(""); - auto ip = jsonObject.value("ip").toString(""); - - if (!hostname.contains(".") && !NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) { - qDebug() << hostname << " not look like ip adress or domain name"; - continue; - } - - if (ip.isEmpty()) { - ips.append(hostname); - } else { - ips.append(ip); - } - sites.insert(hostname, ip); - } - - m_sitesModel->addSites(sites, replaceExisting); - - emit finished(tr("Import completed")); -} - -void SitesController::exportSites(const QString &fileName) -{ - auto sites = m_sitesModel->getCurrentSites(); - - QJsonArray jsonArray; - - for (const auto &site : sites) { - QJsonObject jsonObject { { "hostname", site.first }, { "ip", site.second } }; - jsonArray.append(jsonObject); - } - - QJsonDocument jsonDocument(jsonArray); - QByteArray jsonData = jsonDocument.toJson(); - - SystemController::saveFile(fileName, jsonData); - - emit finished(tr("Export completed")); -} diff --git a/client/ui/controllers/sitesController.h b/client/ui/controllers/sitesController.h deleted file mode 100644 index fbbe383cd..000000000 --- a/client/ui/controllers/sitesController.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef SITESCONTROLLER_H -#define SITESCONTROLLER_H - -#include - -#include "settings.h" -#include "ui/models/sites_model.h" -#include "vpnconnection.h" - -class SitesController : public QObject -{ - Q_OBJECT -public: - explicit SitesController(const std::shared_ptr &settings, const QSharedPointer &sitesModel, - QObject *parent = nullptr); - -public slots: - void addSite(QString hostname); - void removeSite(int index); - - void removeSites(); - void importSites(const QString &fileName, bool replaceExisting); - void exportSites(const QString &fileName); - -signals: - void errorOccurred(const QString &errorMessage); - void finished(const QString &message); - - void saveFile(const QString &fileName, const QString &data); - -private: - std::shared_ptr m_settings; - QSharedPointer m_sitesModel; -}; - -#endif // SITESCONTROLLER_H diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index 12b869903..236643315 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -19,8 +19,8 @@ #include #endif -SystemController::SystemController(const std::shared_ptr &settings, QObject *parent) - : QObject(parent), m_settings(settings) +SystemController::SystemController(QObject *parent) + : QObject(parent) { } @@ -38,8 +38,9 @@ void SystemController::saveFile(const QString &fileName, const QString &data) QFile file(fileName); #endif - // todo check if save successful - file.open(QIODevice::WriteOnly); + if (!file.open(QIODevice::WriteOnly)) { + return; + } file.write(data.toUtf8()); file.close(); diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h index 8cb3a0d16..8f62ef6ee 100644 --- a/client/ui/controllers/systemController.h +++ b/client/ui/controllers/systemController.h @@ -3,13 +3,11 @@ #include -#include "settings.h" - class SystemController : public QObject { Q_OBJECT public: - explicit SystemController(const std::shared_ptr &setting, QObject *parent = nullptr); + explicit SystemController(QObject *parent = nullptr); static void saveFile(const QString &fileName, const QString &data); static bool readFile(const QString &fileName, QByteArray &data); @@ -28,8 +26,6 @@ signals: void fileDialogClosed(const bool isAccepted); private: - std::shared_ptr m_settings; - QObject *m_qmlRoot; }; diff --git a/client/ui/models/allowedDnsModel.cpp b/client/ui/models/allowedDnsModel.cpp new file mode 100644 index 000000000..cee2b9bd8 --- /dev/null +++ b/client/ui/models/allowedDnsModel.cpp @@ -0,0 +1,39 @@ +#include "allowedDnsModel.h" + +AllowedDnsModel::AllowedDnsModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +int AllowedDnsModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_dnsServers.size(); +} + +QVariant AllowedDnsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) + return QVariant(); + + switch (role) { + case IpRole: + return m_dnsServers.at(index.row()); + default: + return QVariant(); + } +} + +void AllowedDnsModel::updateModel(const QStringList &dnsServers) +{ + beginResetModel(); + m_dnsServers = dnsServers; + endResetModel(); +} + +QHash AllowedDnsModel::roleNames() const +{ + QHash roles; + roles[IpRole] = "ip"; + return roles; +} diff --git a/client/ui/models/allowed_dns_model.h b/client/ui/models/allowedDnsModel.h similarity index 58% rename from client/ui/models/allowed_dns_model.h rename to client/ui/models/allowedDnsModel.h index fdefcc0ed..b20ffd9af 100644 --- a/client/ui/models/allowed_dns_model.h +++ b/client/ui/models/allowedDnsModel.h @@ -2,7 +2,7 @@ #define ALLOWEDDNSMODEL_H #include -#include "settings.h" +#include class AllowedDnsModel : public QAbstractListModel { @@ -13,24 +13,18 @@ public: IpRole = Qt::UserRole + 1 }; - explicit AllowedDnsModel(std::shared_ptr settings, QObject *parent = nullptr); + explicit AllowedDnsModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - bool addDns(const QString &ip); - void addDnsList(const QStringList &dnsServers, bool replaceExisting); - void removeDns(QModelIndex index); - QStringList getCurrentDnsServers(); + void updateModel(const QStringList &dnsServers); protected: QHash roleNames() const override; private: - void fillDnsServers(); - - std::shared_ptr m_settings; QStringList m_dnsServers; }; diff --git a/client/ui/models/allowed_dns_model.cpp b/client/ui/models/allowed_dns_model.cpp deleted file mode 100644 index e3c59945a..000000000 --- a/client/ui/models/allowed_dns_model.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "allowed_dns_model.h" - -AllowedDnsModel::AllowedDnsModel(std::shared_ptr settings, QObject *parent) - : QAbstractListModel(parent), m_settings(settings) -{ - fillDnsServers(); -} - -int AllowedDnsModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - return m_dnsServers.size(); -} - -QVariant AllowedDnsModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) - return QVariant(); - - switch (role) { - case IpRole: - return m_dnsServers.at(index.row()); - default: - return QVariant(); - } -} - -bool AllowedDnsModel::addDns(const QString &ip) -{ - if (m_dnsServers.contains(ip)) { - return false; - } - - beginInsertRows(QModelIndex(), rowCount(), rowCount()); - m_dnsServers.append(ip); - m_settings->setAllowedDnsServers(m_dnsServers); - endInsertRows(); - return true; -} - -void AllowedDnsModel::addDnsList(const QStringList &dnsServers, bool replaceExisting) -{ - beginResetModel(); - - if (replaceExisting) { - m_dnsServers.clear(); - } - - for (const QString &ip : dnsServers) { - if (!m_dnsServers.contains(ip)) { - m_dnsServers.append(ip); - } - } - - m_settings->setAllowedDnsServers(m_dnsServers); - endResetModel(); -} - -void AllowedDnsModel::removeDns(QModelIndex index) -{ - if (!index.isValid() || index.row() >= m_dnsServers.size()) { - return; - } - - beginRemoveRows(QModelIndex(), index.row(), index.row()); - m_dnsServers.removeAt(index.row()); - m_settings->setAllowedDnsServers(m_dnsServers); - endRemoveRows(); -} - -QStringList AllowedDnsModel::getCurrentDnsServers() -{ - return m_dnsServers; -} - -QHash AllowedDnsModel::roleNames() const -{ - QHash roles; - roles[IpRole] = "ip"; - return roles; -} - -void AllowedDnsModel::fillDnsServers() -{ - m_dnsServers = m_settings->allowedDnsServers(); -} diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index 13b19cfc7..b9380ed51 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -3,7 +3,7 @@ #include #include -#include "core/api/apiUtils.h" +#include "core/utils/api/apiUtils.h" #include "logger.h" namespace diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h index b3f2270d7..379575dcb 100644 --- a/client/ui/models/api/apiAccountInfoModel.h +++ b/client/ui/models/api/apiAccountInfoModel.h @@ -5,7 +5,9 @@ #include #include -#include "core/api/apiDefs.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" class ApiAccountInfoModel : public QAbstractListModel { diff --git a/client/ui/models/api/apiCountryModel.cpp b/client/ui/models/api/apiCountryModel.cpp index 12f4658ef..4aec43366 100644 --- a/client/ui/models/api/apiCountryModel.cpp +++ b/client/ui/models/api/apiCountryModel.cpp @@ -2,7 +2,9 @@ #include -#include "core/api/apiDefs.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" #include "logger.h" namespace diff --git a/client/ui/models/api/apiDevicesModel.cpp b/client/ui/models/api/apiDevicesModel.cpp index 6c0d60d02..9d69c7b0b 100644 --- a/client/ui/models/api/apiDevicesModel.cpp +++ b/client/ui/models/api/apiDevicesModel.cpp @@ -2,7 +2,9 @@ #include -#include "core/api/apiDefs.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" #include "logger.h" namespace @@ -12,7 +14,7 @@ namespace constexpr QLatin1String gatewayAccount("gateway_account"); } -ApiDevicesModel::ApiDevicesModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) +ApiDevicesModel::ApiDevicesModel(QObject *parent) : QAbstractListModel(parent) { } @@ -43,17 +45,18 @@ QVariant ApiDevicesModel::data(const QModelIndex &index, int role) const return QDateTime::fromString(issuedConfigInfo.lastDownloaded, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); } case IsCurrentDeviceRole: { - return issuedConfigInfo.installationUuid == m_settings->getInstallationUuid(false); + return issuedConfigInfo.installationUuid == m_currentInstallationUuid; } } return QVariant(); } -void ApiDevicesModel::updateModel(const QJsonArray &issuedConfigs) +void ApiDevicesModel::updateModel(const QJsonArray &issuedConfigs, const QString ¤tInstallationUuid) { beginResetModel(); + m_currentInstallationUuid = currentInstallationUuid; m_issuedConfigs.clear(); for (int i = 0; i < issuedConfigs.size(); i++) { IssuedConfigInfo issuedConfigInfo; diff --git a/client/ui/models/api/apiDevicesModel.h b/client/ui/models/api/apiDevicesModel.h index e6a59dba6..7cf10dab3 100644 --- a/client/ui/models/api/apiDevicesModel.h +++ b/client/ui/models/api/apiDevicesModel.h @@ -5,8 +5,6 @@ #include #include -#include "settings.h" - class ApiDevicesModel : public QAbstractListModel { Q_OBJECT @@ -20,14 +18,14 @@ public: IsCurrentDeviceRole }; - explicit ApiDevicesModel(std::shared_ptr settings, QObject *parent = nullptr); + explicit ApiDevicesModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void updateModel(const QJsonArray &issuedConfigs); + void updateModel(const QJsonArray &issuedConfigs, const QString ¤tInstallationUuid); protected: QHash roleNames() const override; @@ -46,7 +44,6 @@ private: }; QVector m_issuedConfigs; - - std::shared_ptr m_settings; + QString m_currentInstallationUuid; }; #endif // APIDEVICESMODEL_H diff --git a/client/ui/models/api/apiServicesModel.cpp b/client/ui/models/api/apiServicesModel.cpp index 9f16edca9..b1f3dac3b 100644 --- a/client/ui/models/api/apiServicesModel.cpp +++ b/client/ui/models/api/apiServicesModel.cpp @@ -5,7 +5,7 @@ #include #include -#include "core/api/apiDefs.h" +#include "core/utils/constants/apiKeys.h" #include "logger.h" namespace diff --git a/client/ui/models/appSplitTunnelingModel.cpp b/client/ui/models/appSplitTunnelingModel.cpp index 77660c96d..636511279 100644 --- a/client/ui/models/appSplitTunnelingModel.cpp +++ b/client/ui/models/appSplitTunnelingModel.cpp @@ -1,17 +1,8 @@ #include "appSplitTunnelingModel.h" -#include - -AppSplitTunnelingModel::AppSplitTunnelingModel(std::shared_ptr settings, QObject *parent) - : QAbstractListModel(parent), m_settings(settings) +AppSplitTunnelingModel::AppSplitTunnelingModel(QObject *parent) + : QAbstractListModel(parent) { - m_isSplitTunnelingEnabled = m_settings->isAppsSplitTunnelingEnabled(); - m_currentRouteMode = m_settings->getAppsRouteMode(); - if (m_currentRouteMode == Settings::VpnAllApps) { // for old split tunneling configs - m_settings->setAppsRouteMode(static_cast(Settings::VpnAllExceptApps)); - m_currentRouteMode = Settings::VpnAllExceptApps; - } - m_apps = m_settings->getVpnApps(m_currentRouteMode); } int AppSplitTunnelingModel::rowCount(const QModelIndex &parent) const @@ -37,60 +28,11 @@ QVariant AppSplitTunnelingModel::data(const QModelIndex &index, int role) const return QVariant(); } -bool AppSplitTunnelingModel::addApp(const InstalledAppInfo &appInfo) -{ - if (m_apps.contains(appInfo)) { - return false; - } - - beginInsertRows(QModelIndex(), rowCount(), rowCount()); - m_apps.append(appInfo); - m_settings->setVpnApps(m_currentRouteMode, m_apps); - endInsertRows(); - - return true; -} - -void AppSplitTunnelingModel::removeApp(QModelIndex index) -{ - beginRemoveRows(QModelIndex(), index.row(), index.row()); - m_apps.removeAt(index.row()); - m_settings->setVpnApps(m_currentRouteMode, m_apps); - endRemoveRows(); -} - -void AppSplitTunnelingModel::clearAppsList() { - beginResetModel(); - m_apps.clear(); - m_settings->setVpnApps(m_currentRouteMode, m_apps); - endResetModel(); -} - -int AppSplitTunnelingModel::getRouteMode() -{ - return m_currentRouteMode; -} - -void AppSplitTunnelingModel::setRouteMode(int routeMode) +void AppSplitTunnelingModel::updateModel(const QVector &apps) { beginResetModel(); - m_settings->setAppsRouteMode(static_cast(routeMode)); - m_currentRouteMode = m_settings->getAppsRouteMode(); - m_apps = m_settings->getVpnApps(m_currentRouteMode); + m_apps = apps; endResetModel(); - emit routeModeChanged(); -} - -bool AppSplitTunnelingModel::isSplitTunnelingEnabled() -{ - return m_isSplitTunnelingEnabled; -} - -void AppSplitTunnelingModel::toggleSplitTunneling(bool enabled) -{ - m_settings->setAppsSplitTunnelingEnabled(enabled); - m_isSplitTunnelingEnabled = enabled; - emit splitTunnelingToggled(); } QHash AppSplitTunnelingModel::roleNames() const diff --git a/client/ui/models/appSplitTunnelingModel.h b/client/ui/models/appSplitTunnelingModel.h index 998ced494..6b9cca858 100644 --- a/client/ui/models/appSplitTunnelingModel.h +++ b/client/ui/models/appSplitTunnelingModel.h @@ -2,9 +2,11 @@ #define APPSPLITTUNNELINGMODEL_H #include +#include -#include "settings.h" -#include "core/defs.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" class AppSplitTunnelingModel: public QAbstractListModel { @@ -17,40 +19,20 @@ public: PackageAppIconRole }; - explicit AppSplitTunnelingModel(std::shared_ptr settings, QObject *parent = nullptr); + explicit AppSplitTunnelingModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - Q_PROPERTY(int routeMode READ getRouteMode WRITE setRouteMode NOTIFY routeModeChanged) - Q_PROPERTY(bool isTunnelingEnabled READ isSplitTunnelingEnabled NOTIFY splitTunnelingToggled) - public slots: - bool addApp(const InstalledAppInfo &appInfo); - void removeApp(QModelIndex index); - void clearAppsList(); - - int getRouteMode(); - void setRouteMode(int routeMode); - - bool isSplitTunnelingEnabled(); - void toggleSplitTunneling(bool enabled); - -signals: - void routeModeChanged(); - void splitTunnelingToggled(); + void updateModel(const QVector &apps); protected: QHash roleNames() const override; private: - std::shared_ptr m_settings; - - bool m_isSplitTunnelingEnabled; - Settings::AppsRouteMode m_currentRouteMode; - - QVector m_apps; + QVector m_apps; }; #endif // APPSPLITTUNNELINGMODEL_H diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 4e3cda7c6..ae982cc85 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -1,31 +1,13 @@ #include "clientManagementModel.h" -#include #include -#include "core/controllers/serverController.h" -#include "logger.h" +#include "core/utils/constants/configKeys.h" -namespace -{ - Logger logger("ClientManagementModel"); +using namespace amnezia; - namespace configKey - { - constexpr char clientId[] = "clientId"; - constexpr char clientName[] = "clientName"; - constexpr char container[] = "container"; - constexpr char userData[] = "userData"; - constexpr char creationDate[] = "creationDate"; - constexpr char latestHandshake[] = "latestHandshake"; - constexpr char dataReceived[] = "dataReceived"; - constexpr char dataSent[] = "dataSent"; - constexpr char allowedIps[] = "allowedIps"; - } -} - -ClientManagementModel::ClientManagementModel(std::shared_ptr settings, QObject *parent) - : m_settings(settings), QAbstractListModel(parent) +ClientManagementModel::ClientManagementModel(QObject *parent) + : QAbstractListModel(parent) { } @@ -56,880 +38,25 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const return QVariant(); } -void ClientManagementModel::migration(const QByteArray &clientsTableString) -{ - QJsonObject clientsTable = QJsonDocument::fromJson(clientsTableString).object(); - - for (auto &clientId : clientsTable.keys()) { - QJsonObject client; - client[configKey::clientId] = clientId; - - QJsonObject userData; - userData[configKey::clientName] = clientsTable.value(clientId).toObject().value(configKey::clientName); - client[configKey::userData] = userData; - - m_clientsTable.push_back(client); - } -} - -ErrorCode ClientManagementModel::updateModel(const DockerContainer container, const ServerCredentials &credentials, - const QSharedPointer &serverController) +void ClientManagementModel::updateModel(const QJsonArray &clients) { beginResetModel(); - m_clientsTable = QJsonArray(); + m_clientsTable = clients; endResetModel(); - - ErrorCode error = ErrorCode::NoError; - - QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); - } else { - clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); - } - - const QByteArray clientsTableString = serverController->getTextFileFromContainer(container, credentials, clientsTableFile, error); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to get the clientsTable file from the server"; - return error; - } - - beginResetModel(); - m_clientsTable = QJsonDocument::fromJson(clientsTableString).array(); - - if (m_clientsTable.isEmpty()) { - migration(clientsTableString); - - int count = 0; - - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - error = getOpenVpnClients(container, credentials, serverController, count); - } else if (container == DockerContainer::WireGuard || ContainerProps::isAwgContainer(container)) { - error = getWireGuardClients(container, credentials, serverController, count); - } else if (container == DockerContainer::Xray) { - error = getXrayClients(container, credentials, serverController, count); - } - if (error != ErrorCode::NoError) { - endResetModel(); - return error; - } - - const QByteArray newClientsTableString = QJsonDocument(m_clientsTable).toJson(); - if (clientsTableString != newClientsTableString) { - error = serverController->uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to upload the clientsTable file to the server"; - } - } - } - - std::vector data; - wgShow(container, credentials, serverController, data); - - for (const auto &client : data) { - int i = 0; - for (const auto &it : std::as_const(m_clientsTable)) { - if (it.isObject()) { - QJsonObject obj = it.toObject(); - if (obj.contains(configKey::clientId) && obj[configKey::clientId].toString() == client.clientId) { - QJsonObject userData = obj[configKey::userData].toObject(); - - if (!client.latestHandshake.isEmpty()) { - userData[configKey::latestHandshake] = client.latestHandshake; - } - - if (!client.dataReceived.isEmpty()) { - userData[configKey::dataReceived] = client.dataReceived; - } - - if (!client.dataSent.isEmpty()) { - userData[configKey::dataSent] = client.dataSent; - } - - if (!client.allowedIps.isEmpty()) { - userData[configKey::allowedIps] = client.allowedIps; - } - - obj[configKey::userData] = userData; - m_clientsTable.replace(i, obj); - break; - } - } - ++i; - } - } - - endResetModel(); - return error; } -ErrorCode ClientManagementModel::getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials, - const QSharedPointer &serverController, int &count) +void ClientManagementModel::updateClientName(int row, const QString &newName) { - ErrorCode error = ErrorCode::NoError; - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - const QString getOpenVpnClientsList = "sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'"; - QString script = serverController->replaceVars(getOpenVpnClientsList, serverController->genVarsForScript(credentials, container)); - error = serverController->runScript(credentials, script, cbReadStdOut); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to retrieve the list of issued certificates on the server"; - return error; + if (row < 0 || row >= m_clientsTable.size()) { + return; } - - if (!stdOut.isEmpty()) { - QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts); - certsIds.removeAll("AmneziaReq.crt"); - - for (auto &openvpnCertId : certsIds) { - openvpnCertId.replace(".crt", ""); - if (!isClientExists(openvpnCertId)) { - QJsonObject client; - client[configKey::clientId] = openvpnCertId; - - QJsonObject userData; - userData[configKey::clientName] = QString("Client %1").arg(count); - client[configKey::userData] = userData; - - m_clientsTable.push_back(client); - - count++; - } - } - } - return error; -} - -ErrorCode ClientManagementModel::getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials, - const QSharedPointer &serverController, int &count) -{ - ErrorCode error = ErrorCode::NoError; - - QString configPath; - if (container == DockerContainer::Awg) { - configPath = QString::fromLatin1(amnezia::protocols::awg::serverLegacyConfigPath); - } else if (container == DockerContainer::Awg2) { - configPath = QString::fromLatin1(amnezia::protocols::awg::serverConfigPath); - } else { - configPath = QString::fromLatin1(amnezia::protocols::wireguard::serverConfigPath); - } - const QString wireguardConfigString = serverController->getTextFileFromContainer(container, credentials, configPath, error); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to get the wg conf file from the server"; - return error; - } - - auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts); - QStringList wireguardKeys; - for (const auto &line : configLines) { - auto configPair = line.split(" = ", Qt::SkipEmptyParts); - if (configPair.front() == "PublicKey") { - wireguardKeys.push_back(configPair.back()); - } - } - - for (auto &wireguardKey : wireguardKeys) { - if (!isClientExists(wireguardKey)) { - QJsonObject client; - client[configKey::clientId] = wireguardKey; - - QJsonObject userData; - userData[configKey::clientName] = QString("Client %1").arg(count); - client[configKey::userData] = userData; - - m_clientsTable.push_back(client); - - count++; - } - } - return error; -} -ErrorCode ClientManagementModel::getXrayClients(const DockerContainer container, const ServerCredentials& credentials, - const QSharedPointer &serverController, int &count) -{ - ErrorCode error = ErrorCode::NoError; - - const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath; - const QString configString = serverController->getTextFileFromContainer(container, credentials, serverConfigPath, error); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to get the xray server config file from the server"; - return error; - } - - QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8()); - if (serverConfig.isNull()) { - logger.error() << "Failed to parse xray server config JSON"; - return ErrorCode::InternalError; - } - - if (!serverConfig.object().contains("inbounds") || serverConfig.object()["inbounds"].toArray().isEmpty()) { - logger.error() << "Invalid xray server config structure"; - return ErrorCode::InternalError; - } - - const QJsonObject inbound = serverConfig.object()["inbounds"].toArray()[0].toObject(); - if (!inbound.contains("settings")) { - logger.error() << "Missing settings in xray inbound config"; - return ErrorCode::InternalError; - } - - const QJsonObject settings = inbound["settings"].toObject(); - if (!settings.contains("clients")) { - logger.error() << "Missing clients in xray settings config"; - return ErrorCode::InternalError; - } - - const QJsonArray clients = settings["clients"].toArray(); - for (const auto &clientValue : clients) { - const QJsonObject clientObj = clientValue.toObject(); - if (!clientObj.contains("id")) { - logger.error() << "Missing id in xray client config"; - continue; - } - QString clientId = clientObj["id"].toString(); - - QString xrayDefaultUuid = serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, error); - xrayDefaultUuid.replace("\n", ""); - - if (!isClientExists(clientId) && clientId != xrayDefaultUuid) { - QJsonObject client; - client[configKey::clientId] = clientId; - - QJsonObject userData; - userData[configKey::clientName] = QString("Client %1").arg(count); - client[configKey::userData] = userData; - - m_clientsTable.push_back(client); - count++; - } - } - - return error; -} - -ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const ServerCredentials &credentials, - const QSharedPointer &serverController, std::vector &data) -{ - if (container != DockerContainer::WireGuard && !ContainerProps::isAwgContainer(container)) { - return ErrorCode::NoError; - } - - ErrorCode error = ErrorCode::NoError; - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - QString showBin = (container == DockerContainer::Awg2) - ? QStringLiteral("awg") - : QStringLiteral("wg"); - const QString command = QString("sudo docker exec -i $CONTAINER_NAME bash -c '%1 show all'") - .arg(showBin); - - QString script = serverController->replaceVars(command, serverController->genVarsForScript(credentials, container)); - error = serverController->runScript(credentials, script, cbReadStdOut); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to execute wg show command"; - return error; - } - - if (stdOut.isEmpty()) { - return error; - } - - const auto getStrValue = [](const auto str) { return str.mid(str.indexOf(":") + 1).trimmed(); }; - - const auto parts = stdOut.split('\n'); - const auto peerList = parts.filter("peer:"); - const auto latestHandshakeList = parts.filter("latest handshake:"); - const auto transferredDataList = parts.filter("transfer:"); - const auto allowedIpsList = parts.filter("allowed ips:"); - - if (allowedIpsList.isEmpty() || latestHandshakeList.isEmpty() || transferredDataList.isEmpty() || peerList.isEmpty()) { - return error; - } - - const auto changeHandshakeFormat = [](QString &latestHandshake) { - const std::vector> replaceMap = { { " days", "d" }, { " hours", "h" }, { " minutes", "m" }, - { " seconds", "s" }, { " day", "d" }, { " hour", "h" }, - { " minute", "m" }, { " second", "s" } }; - - for (const auto &item : replaceMap) { - latestHandshake.replace(item.first, item.second); - } - }; - - for (int i = 0; i < peerList.size() && i < transferredDataList.size() && i < latestHandshakeList.size() && i < allowedIpsList.size(); ++i) { - - const auto transferredData = getStrValue(transferredDataList[i]).split(","); - auto latestHandshake = getStrValue(latestHandshakeList[i]); - auto serverBytesReceived = transferredData.front().trimmed(); - auto serverBytesSent = transferredData.back().trimmed(); - auto allowedIps = getStrValue(allowedIpsList[i]); - - changeHandshakeFormat(latestHandshake); - - serverBytesReceived.chop(QStringLiteral(" received").length()); - serverBytesSent.chop(QStringLiteral(" sent").length()); - - data.push_back({ getStrValue(peerList[i]), latestHandshake, serverBytesSent, serverBytesReceived, allowedIps }); - } - - return error; -} - -bool ClientManagementModel::isClientExists(const QString &clientId) -{ - for (const QJsonValue &value : std::as_const(m_clientsTable)) { - if (value.isObject()) { - QJsonObject obj = value.toObject(); - if (obj.contains(configKey::clientId) && obj[configKey::clientId].toString() == clientId) { - return true; - } - } - } - return false; -} - -ErrorCode ClientManagementModel::appendClient(const DockerContainer container, const ServerCredentials &credentials, - const QJsonObject &containerConfig, const QString &clientName, - const QSharedPointer &serverController) -{ - Proto protocol; - switch (container) { - case DockerContainer::ShadowSocks: - case DockerContainer::Cloak: - protocol = Proto::OpenVpn; - break; - case DockerContainer::OpenVpn: - case DockerContainer::WireGuard: - case DockerContainer::Awg2: - case DockerContainer::Awg: - case DockerContainer::Xray: - protocol = ContainerProps::defaultProtocol(container); - break; - default: - return ErrorCode::NoError; - } - - auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); - return appendClient(protocolConfig, clientName, container, credentials, serverController); -} - -ErrorCode ClientManagementModel::appendClient(QJsonObject &protocolConfig, const QString &clientName, const DockerContainer container, - const ServerCredentials &credentials, const QSharedPointer &serverController) -{ - QString clientId; - if (container == DockerContainer::Xray) { - if (!protocolConfig.contains("outbounds")) { - return ErrorCode::InternalError; - } - QJsonArray outbounds = protocolConfig.value("outbounds").toArray(); - if (outbounds.isEmpty()) { - return ErrorCode::InternalError; - } - QJsonObject outbound = outbounds[0].toObject(); - if (!outbound.contains("settings")) { - return ErrorCode::InternalError; - } - QJsonObject settings = outbound["settings"].toObject(); - if (!settings.contains("vnext")) { - return ErrorCode::InternalError; - } - QJsonArray vnext = settings["vnext"].toArray(); - if (vnext.isEmpty()) { - return ErrorCode::InternalError; - } - QJsonObject vnextObj = vnext[0].toObject(); - if (!vnextObj.contains("users")) { - return ErrorCode::InternalError; - } - QJsonArray users = vnextObj["users"].toArray(); - if (users.isEmpty()) { - return ErrorCode::InternalError; - } - QJsonObject user = users[0].toObject(); - if (!user.contains("id")) { - return ErrorCode::InternalError; - } - clientId = user["id"].toString(); - } else { - clientId = protocolConfig.value(config_key::clientId).toString(); - } - - return appendClient(clientId, clientName, container, credentials, serverController); -} - -ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QString &clientName, const DockerContainer container, - const ServerCredentials &credentials, const QSharedPointer &serverController) -{ - ErrorCode error = ErrorCode::NoError; - - error = updateModel(container, credentials, serverController); - if (error != ErrorCode::NoError) { - return error; - } - - for (int i = 0; i < m_clientsTable.size(); i++) { - if (m_clientsTable.at(i).toObject().value(configKey::clientId) == clientId) { - return renameClient(i, clientName, container, credentials, serverController, true); - } - } - - beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1); - QJsonObject client; - client[configKey::clientId] = clientId; - - QJsonObject userData; - userData[configKey::clientName] = clientName; - userData[configKey::creationDate] = QDateTime::currentDateTime().toString(); + QJsonObject client = m_clientsTable.at(row).toObject(); + QJsonObject userData = client.value(configKey::userData).toObject(); + userData[configKey::clientName] = newName; client[configKey::userData] = userData; - m_clientsTable.push_back(client); - endInsertRows(); - - const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); - - QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); - } else { - clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); - } - - error = serverController->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to upload the clientsTable file to the server"; - } - - return error; -} - -ErrorCode ClientManagementModel::renameClient(const int row, const QString &clientName, - const DockerContainer container, - const ServerCredentials &credentials, - const QSharedPointer &serverController, bool addTimeStamp) -{ - auto client = m_clientsTable.at(row).toObject(); - auto userData = client[configKey::userData].toObject(); - userData[configKey::clientName] = clientName; - if (addTimeStamp) { - userData[configKey::creationDate] = QDateTime::currentDateTime().toString(); - } - client[configKey::userData] = userData; - m_clientsTable.replace(row, client); - emit dataChanged(index(row, 0), index(row, 0)); - - const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); - - QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); - } else { - clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); - } - - ErrorCode error = serverController->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to upload the clientsTable file to the server"; - } - - return error; -} - -ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container, - const ServerCredentials &credentials, - const int serverIndex, const QSharedPointer &serverController) -{ - ErrorCode errorCode = ErrorCode::NoError; - auto client = m_clientsTable.at(row).toObject(); - QString clientId = client.value(configKey::clientId).toString(); - - switch(container) - { - case DockerContainer::OpenVpn: - case DockerContainer::ShadowSocks: - case DockerContainer::Cloak: { - errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController); - break; - } - case DockerContainer::WireGuard: - case DockerContainer::Awg2: - case DockerContainer::Awg: { - errorCode = revokeWireGuard(row, container, credentials, serverController); - break; - } - case DockerContainer::Xray: { - errorCode = revokeXray(row, container, credentials, serverController); - break; - } - default: { - logger.error() << "Internal error: received unexpected container type"; - return ErrorCode::InternalError; - } - } - - if (errorCode == ErrorCode::NoError) { - const auto server = m_settings->server(serverIndex); - QJsonArray containers = server.value(config_key::containers).toArray(); - for (auto i = 0; i < containers.size(); i++) { - auto containerConfig = containers.at(i).toObject(); - auto containerType = ContainerProps::containerFromString(containerConfig.value(config_key::container).toString()); - if (containerType == container) { - QJsonObject protocolConfig; - if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)).toObject(); - } else { - protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(containerType)).toObject(); - } - - if (protocolConfig.value(config_key::last_config).toString().contains(clientId)) { - emit adminConfigRevoked(container); - } - } - } - } - - return errorCode; -} - -ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig, const DockerContainer container, - const ServerCredentials &credentials, const int serverIndex, - const QSharedPointer &serverController) -{ - ErrorCode errorCode = ErrorCode::NoError; - errorCode = updateModel(container, credentials, serverController); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - Proto protocol; - - switch(container) - { - case DockerContainer::ShadowSocks: - case DockerContainer::Cloak: { - protocol = Proto::OpenVpn; - break; - } - case DockerContainer::OpenVpn: - case DockerContainer::WireGuard: - case DockerContainer::Awg2: - case DockerContainer::Awg: - case DockerContainer::Xray: { - protocol = ContainerProps::defaultProtocol(container); - break; - } - default: { - logger.error() << "Internal error: received unexpected container type"; - return ErrorCode::InternalError; - } - } - - auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); - - QString clientId; - if (container == DockerContainer::Xray) { - if (!protocolConfig.contains("outbounds")) { - return ErrorCode::InternalError; - } - QJsonArray outbounds = protocolConfig.value("outbounds").toArray(); - if (outbounds.isEmpty()) { - return ErrorCode::InternalError; - } - QJsonObject outbound = outbounds[0].toObject(); - if (!outbound.contains("settings")) { - return ErrorCode::InternalError; - } - QJsonObject settings = outbound["settings"].toObject(); - if (!settings.contains("vnext")) { - return ErrorCode::InternalError; - } - QJsonArray vnext = settings["vnext"].toArray(); - if (vnext.isEmpty()) { - return ErrorCode::InternalError; - } - QJsonObject vnextObj = vnext[0].toObject(); - if (!vnextObj.contains("users")) { - return ErrorCode::InternalError; - } - QJsonArray users = vnextObj["users"].toArray(); - if (users.isEmpty()) { - return ErrorCode::InternalError; - } - QJsonObject user = users[0].toObject(); - if (!user.contains("id")) { - return ErrorCode::InternalError; - } - clientId = user["id"].toString(); - } else { - clientId = protocolConfig.value(config_key::clientId).toString(); - } - - int row; - bool clientExists = false; - for (row = 0; row < rowCount(); row++) { - auto client = m_clientsTable.at(row).toObject(); - if (clientId == client.value(configKey::clientId).toString()) { - clientExists = true; - break; - } - } - if (!clientExists) { - return errorCode; - } - - switch (container) - { - case DockerContainer::OpenVpn: - case DockerContainer::ShadowSocks: - case DockerContainer::Cloak: { - errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController); - break; - } - case DockerContainer::WireGuard: - case DockerContainer::Awg: - case DockerContainer::Awg2: { - errorCode = revokeWireGuard(row, container, credentials, serverController); - break; - } - case DockerContainer::Xray: { - errorCode = revokeXray(row, container, credentials, serverController); - break; - } - default: - logger.error() << "Internal error: received unexpected container type"; - return ErrorCode::InternalError; - } - - return errorCode; -} - -ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials, - const int serverIndex, const QSharedPointer &serverController) -{ - auto client = m_clientsTable.at(row).toObject(); - QString clientId = client.value(configKey::clientId).toString(); - - const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '" - "cd /opt/amnezia/openvpn ;\\" - "easyrsa revoke %1 ;\\" - "easyrsa gen-crl ;\\" - "chmod 666 pki/crl.pem ;\\" - "cp pki/crl.pem .'") - .arg(clientId); - - const QString script = serverController->replaceVars(getOpenVpnCertData, serverController->genVarsForScript(credentials, container)); - ErrorCode error = serverController->runScript(credentials, script); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to revoke the certificate"; - return error; - } - - beginRemoveRows(QModelIndex(), row, row); - m_clientsTable.removeAt(row); - endRemoveRows(); - - const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); - - QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); - clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); - error = serverController->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to upload the clientsTable file to the server"; - return error; - } - - return ErrorCode::NoError; -} - -ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials, - const QSharedPointer &serverController) -{ - ErrorCode error = ErrorCode::NoError; - - QString configPath; - if (container == DockerContainer::Awg) { - configPath = QString::fromLatin1(amnezia::protocols::awg::serverLegacyConfigPath); - } else if (container == DockerContainer::Awg2) { - configPath = QString::fromLatin1(amnezia::protocols::awg::serverConfigPath); - } else { - configPath = QString::fromLatin1(amnezia::protocols::wireguard::serverConfigPath); - } - const QString wireguardConfigString = serverController->getTextFileFromContainer(container, credentials, configPath, error); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to get the wg conf file from the server"; - return error; - } - - auto client = m_clientsTable.at(row).toObject(); - QString clientId = client.value(configKey::clientId).toString(); - - auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts); - for (auto §ion : configSections) { - if (section.contains(clientId)) { - configSections.removeOne(section); - break; - } - } - QString newWireGuardConfig = configSections.join("["); - newWireGuardConfig.insert(0, "["); - error = serverController->uploadTextFileToContainer(container, credentials, newWireGuardConfig, configPath); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to upload the wg conf file to the server"; - return error; - } - - beginRemoveRows(QModelIndex(), row, row); - m_clientsTable.removeAt(row); - endRemoveRows(); - - const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); - - QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); - } else { - clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); - } - error = serverController->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to upload the clientsTable file to the server"; - return error; - } - - bool isAwg = (container == DockerContainer::Awg2); - QString command = isAwg ? QStringLiteral("awg") : QStringLiteral("wg"); - QString iface = isAwg ? QStringLiteral("awg0") : QStringLiteral("wg0"); - QString script = QString( - "sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'" - ).arg(command, iface, configPath); - error = serverController->runScript( - credentials, - serverController->replaceVars(script, serverController->genVarsForScript(credentials, container)) - ); - if (error != ErrorCode::NoError) { - logger.error() << QString("Failed to execute command '%1 syncconf %2' on the server").arg(command, iface); - return error; - } - - return ErrorCode::NoError; -} - -ErrorCode ClientManagementModel::revokeXray(const int row, - const DockerContainer container, - const ServerCredentials &credentials, - const QSharedPointer &serverController) -{ - ErrorCode error = ErrorCode::NoError; - - // Get server config - const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath; - const QString configString = serverController->getTextFileFromContainer(container, credentials, serverConfigPath, error); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to get the xray server config file"; - return error; - } - - QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8()); - if (serverConfig.isNull()) { - logger.error() << "Failed to parse xray server config JSON"; - return ErrorCode::InternalError; - } - - // Get client ID to remove - auto client = m_clientsTable.at(row).toObject(); - QString clientId = client.value(configKey::clientId).toString(); - - // Remove client from server config - QJsonObject configObj = serverConfig.object(); - if (!configObj.contains("inbounds")) { - logger.error() << "Missing inbounds in xray config"; - return ErrorCode::InternalError; - } - - QJsonArray inbounds = configObj["inbounds"].toArray(); - if (inbounds.isEmpty()) { - logger.error() << "Empty inbounds array in xray config"; - return ErrorCode::InternalError; - } - - QJsonObject inbound = inbounds[0].toObject(); - if (!inbound.contains("settings")) { - logger.error() << "Missing settings in xray inbound config"; - return ErrorCode::InternalError; - } - - QJsonObject settings = inbound["settings"].toObject(); - if (!settings.contains("clients")) { - logger.error() << "Missing clients in xray settings"; - return ErrorCode::InternalError; - } - - QJsonArray clients = settings["clients"].toArray(); - if (clients.isEmpty()) { - logger.error() << "Empty clients array in xray config"; - return ErrorCode::InternalError; - } - - for (int i = 0; i < clients.size(); ++i) { - QJsonObject clientObj = clients[i].toObject(); - if (clientObj.contains("id") && clientObj["id"].toString() == clientId) { - clients.removeAt(i); - break; - } - } - - // Update server config - settings["clients"] = clients; - inbound["settings"] = settings; - inbounds[0] = inbound; - configObj["inbounds"] = inbounds; - - // Upload updated config - error = serverController->uploadTextFileToContainer( - container, - credentials, - QJsonDocument(configObj).toJson(), - serverConfigPath - ); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to upload updated xray config"; - return error; - } - - // Remove from local table - beginRemoveRows(QModelIndex(), row, row); - m_clientsTable.removeAt(row); - endRemoveRows(); - - // Update clients table file on server - const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); - QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable") - .arg(ContainerProps::containerTypeToString(container)); - - error = serverController->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to upload the clientsTable file"; - } - - // Restart container - QString restartScript = QString("sudo docker restart $CONTAINER_NAME"); - error = serverController->runScript( - credentials, - serverController->replaceVars(restartScript, serverController->genVarsForScript(credentials, container)) - ); - if (error != ErrorCode::NoError) { - logger.error() << "Failed to restart xray container"; - return error; - } - - return error; + const QModelIndex idx = index(row); + emit dataChanged(idx, idx, { ClientNameRole }); } QHash ClientManagementModel::roleNames() const diff --git a/client/ui/models/clientManagementModel.h b/client/ui/models/clientManagementModel.h index 2880a6d12..13c7eb146 100644 --- a/client/ui/models/clientManagementModel.h +++ b/client/ui/models/clientManagementModel.h @@ -4,9 +4,6 @@ #include #include -#include "core/controllers/serverController.h" -#include "settings.h" - class ClientManagementModel : public QAbstractListModel { Q_OBJECT @@ -21,69 +18,20 @@ public: AllowedIpsRole }; - struct WgShowData - { - QString clientId; - QString latestHandshake; - QString dataReceived; - QString dataSent; - QString allowedIps; - }; - - ClientManagementModel(std::shared_ptr settings, QObject *parent = nullptr); + explicit ClientManagementModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - ErrorCode updateModel(const DockerContainer container, const ServerCredentials &credentials, - const QSharedPointer &serverController); - ErrorCode appendClient(const DockerContainer container, const ServerCredentials &credentials, const QJsonObject &containerConfig, - const QString &clientName, const QSharedPointer &serverController); - ErrorCode appendClient(QJsonObject &protocolConfig, const QString &clientName,const DockerContainer container, - const ServerCredentials &credentials, const QSharedPointer &serverController); - ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container, - const ServerCredentials &credentials, const QSharedPointer &serverController); - ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, - const ServerCredentials &credentials, const QSharedPointer &serverController, bool addTimeStamp = false); - ErrorCode revokeClient(const int index, const DockerContainer container, const ServerCredentials &credentials, - const int serverIndex, const QSharedPointer &serverController); - ErrorCode revokeClient(const QJsonObject &containerConfig, const DockerContainer container, const ServerCredentials &credentials, - const int serverIndex, const QSharedPointer &serverController); + void updateModel(const QJsonArray &clients); + void updateClientName(int row, const QString &newName); protected: QHash roleNames() const override; -signals: - void adminConfigRevoked(const DockerContainer container); - private: - bool isClientExists(const QString &clientId); - - int clientIndexById(const QString &clientId); - - void migration(const QByteArray &clientsTableString); - - ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials, const int serverIndex, - const QSharedPointer &serverController); - ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials, - const QSharedPointer &serverController); - ErrorCode revokeXray(const int row, const DockerContainer container, const ServerCredentials &credentials, - const QSharedPointer &serverController); - - ErrorCode getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials, - const QSharedPointer &serverController, int &count); - ErrorCode getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials, - const QSharedPointer &serverController, int &count); - ErrorCode getXrayClients(const DockerContainer container, const ServerCredentials& credentials, - const QSharedPointer &serverController, int &count); - - ErrorCode wgShow(const DockerContainer container, const ServerCredentials &credentials, - const QSharedPointer &serverController, std::vector &data); - QJsonArray m_clientsTable; - - std::shared_ptr m_settings; }; #endif // CLIENTMANAGEMENTMODEL_H diff --git a/client/ui/models/containerProps.h b/client/ui/models/containerProps.h new file mode 100644 index 000000000..f44c185b2 --- /dev/null +++ b/client/ui/models/containerProps.h @@ -0,0 +1,29 @@ +#ifndef CONTAINERPROPS_H +#define CONTAINERPROPS_H + +#include +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" + +class ContainerProps : public QObject +{ + Q_OBJECT + +public: + explicit ContainerProps(QObject *parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE QString containerTypeToString(int containerIndex) const { + return amnezia::ContainerUtils::containerTypeToString(static_cast(containerIndex)); + } + + Q_INVOKABLE int defaultProtocol(int containerIndex) const { + return static_cast(amnezia::ContainerUtils::defaultProtocol(static_cast(containerIndex))); + } + + Q_INVOKABLE int containerFromString(const QString &container) const { + return static_cast(amnezia::ContainerUtils::containerFromString(container)); + } +}; + +#endif // CONTAINERPROPS_H + diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containersModel.cpp similarity index 51% rename from client/ui/models/containers_model.cpp rename to client/ui/models/containersModel.cpp index 69943544a..335ddbe7c 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containersModel.cpp @@ -1,7 +1,11 @@ -#include "containers_model.h" +#include "containersModel.h" #include +#include "core/models/protocolConfig.h" + +using namespace amnezia; + ContainersModel::ContainersModel(QObject *parent) : QAbstractListModel(parent) { } @@ -9,53 +13,68 @@ ContainersModel::ContainersModel(QObject *parent) : QAbstractListModel(parent) int ContainersModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return ContainerProps::allContainers().size(); + return ContainerUtils::allContainers().size(); } QVariant ContainersModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerUtils::allContainers().size()) { return QVariant(); } - DockerContainer container = ContainerProps::allContainers().at(index.row()); - QString protocolKey = ContainerProps::containerTypeToProtocolString(container); - auto isThirdPartyConfig = m_containers.value(container).value(protocolKey).toObject().value(config_key::isThirdPartyConfig).toBool(); + DockerContainer container = ContainerUtils::allContainers().at(index.row()); + bool isThirdPartyConfig = false; + if (m_containers.contains(container)) { + const ContainerConfig& config = m_containers.value(container); + isThirdPartyConfig = config.protocolConfig.isThirdPartyConfig(); + } switch (role) { case NameRole: { if (container == DockerContainer::Awg && !isThirdPartyConfig) { return "AmneziaWG Legacy"; } - return ContainerProps::containerHumanNames().value(container); + return ContainerUtils::containerHumanNames().value(container); } case DescriptionRole: { if (container == DockerContainer::Awg && !isThirdPartyConfig) { - return QObject::tr("AmneziaWG Legacy is a outdated version of AmneziaWG protocol. To upgrade, install AmneziaWG and recreate users."); + return QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. " + "It provides high connection speed and ensures stable operation even in the most challenging network conditions."); } - return ContainerProps::containerDescriptions().value(container); + return ContainerUtils::containerDescriptions().value(container); } - case DetailedDescriptionRole: return ContainerProps::containerDetailedDescriptions().value(container); + case DetailedDescriptionRole: return ContainerUtils::containerDetailedDescriptions().value(container); case ConfigRole: { if (container == DockerContainer::None) { return QJsonObject(); } - return m_containers.value(container); + if (m_containers.contains(container)) { + return m_containers.value(container).toJson(); + } + return QJsonObject(); } case IsThirdPartyConfigRole: return isThirdPartyConfig; - case ServiceTypeRole: return ContainerProps::containerService(container); + case ServiceTypeRole: return ContainerUtils::containerService(container); case DockerContainerRole: return container; - case IsEasySetupContainerRole: return ContainerProps::isEasySetupContainer(container); - case EasySetupHeaderRole: return ContainerProps::easySetupHeader(container); - case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container); - case EasySetupOrderRole: return ContainerProps::easySetupOrder(container); + case ContainerStringRole: return ContainerUtils::containerToString(container); + case IsEasySetupContainerRole: return ContainerUtils::isEasySetupContainer(container); + case EasySetupHeaderRole: return ContainerUtils::easySetupHeader(container); + case EasySetupDescriptionRole: return ContainerUtils::easySetupDescription(container); + case EasySetupOrderRole: return ContainerUtils::easySetupOrder(container); case IsInstallationAllowedRole: return ContainersModel::isInstallationAllowed(container); case IsInstalledRole: return m_containers.contains(container); case IsCurrentlyProcessedRole: return container == static_cast(m_processedContainerIndex); - case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container); - case IsShareableRole: return ContainerProps::isShareable(container); - case InstallPageOrderRole: return ContainerProps::installPageOrder(container); + case IsSupportedRole: return ContainerUtils::isSupportedByCurrentPlatform(container); + case IsShareableRole: return ContainerUtils::isShareable(container); + case IsVpnContainerRole: return ContainerUtils::containerService(container) == ServiceType::Vpn; + case IsServiceContainerRole: return ContainerUtils::containerService(container) == ServiceType::Other; + case IsIpsecRole: return container == DockerContainer::Ipsec; + case IsDnsRole: return container == DockerContainer::Dns; + case IsSftpRole: return container == DockerContainer::Sftp; + case IsTorWebsiteRole: return container == DockerContainer::TorWebSite; + case IsSocks5ProxyRole: return container == DockerContainer::Socks5Proxy; + case InstallPageOrderRole: return ContainerUtils::installPageOrder(container); } return QVariant(); @@ -67,13 +86,10 @@ QVariant ContainersModel::data(const int index, int role) const return data(modelIndex, role); } -void ContainersModel::updateModel(const QJsonArray &containers) +void ContainersModel::updateModel(const QMap &containers) { beginResetModel(); - m_containers.clear(); - for (const QJsonValue &val : containers) { - m_containers.insert(ContainerProps::containerFromString(val.toObject().value(config_key::container).toString()), val.toObject()); - } + m_containers = containers; endResetModel(); } @@ -82,14 +98,9 @@ void ContainersModel::setProcessedContainerIndex(int index) m_processedContainerIndex = index; } -int ContainersModel::getProcessedContainerIndex() -{ - return m_processedContainerIndex; -} - QString ContainersModel::getProcessedContainerName() { - return ContainerProps::containerHumanNames().value(static_cast(m_processedContainerIndex)); + return ContainerUtils::containerHumanNames().value(static_cast(m_processedContainerIndex)); } QJsonObject ContainersModel::getContainerConfig(const int containerIndex) @@ -104,13 +115,13 @@ bool ContainersModel::isSupportedByCurrentPlatform(const int containerIndex) bool ContainersModel::isServiceContainer(const int containerIndex) { - return qvariant_cast(data(index(containerIndex), ServiceTypeRole) == ServiceType::Other); + return qvariant_cast(data(index(containerIndex), ServiceTypeRole) == ServiceType::Other); } bool ContainersModel::hasInstalledServices() { for (const auto &container : m_containers.keys()) { - if (ContainerProps::containerService(container) == ServiceType::Other) { + if (ContainerUtils::containerService(container) == ServiceType::Other) { return true; } } @@ -120,7 +131,7 @@ bool ContainersModel::hasInstalledServices() bool ContainersModel::hasInstalledProtocols() { for (const auto &container : m_containers.keys()) { - if (ContainerProps::containerService(container) == ServiceType::Vpn) { + if (ContainerUtils::containerService(container) == ServiceType::Vpn) { return true; } } @@ -132,6 +143,16 @@ bool ContainersModel::isInstallationAllowed(DockerContainer container) return container != DockerContainer::Awg; } +void ContainersModel::openContainerSettings(int containerIndex) +{ + DockerContainer container = static_cast(containerIndex); + + // This method will be connected to QML signals to open appropriate settings page + // The actual navigation will be handled in QML based on container type + // For now, we emit a signal that QML can listen to + // In a full implementation, this would directly call PageController or emit a signal +} + QHash ContainersModel::roleNames() const { QHash roles; @@ -140,6 +161,7 @@ QHash ContainersModel::roleNames() const roles[DetailedDescriptionRole] = "detailedDescription"; roles[ServiceTypeRole] = "serviceType"; roles[DockerContainerRole] = "dockerContainer"; + roles[ContainerStringRole] = "containerString"; roles[ConfigRole] = "config"; roles[IsThirdPartyConfigRole] = "isThirdPartyConfig"; @@ -154,5 +176,13 @@ QHash ContainersModel::roleNames() const roles[IsShareableRole] = "isShareable"; roles[IsInstallationAllowedRole] = "isInstallationAllowed"; roles[InstallPageOrderRole] = "installPageOrder"; + + roles[IsVpnContainerRole] = "isVpnContainer"; + roles[IsServiceContainerRole] = "isServiceContainer"; + roles[IsIpsecRole] = "isIpsec"; + roles[IsDnsRole] = "isDns"; + roles[IsSftpRole] = "isSftp"; + roles[IsTorWebsiteRole] = "isTorWebsite"; + roles[IsSocks5ProxyRole] = "isSocks5Proxy"; return roles; } diff --git a/client/ui/models/containers_model.h b/client/ui/models/containersModel.h similarity index 65% rename from client/ui/models/containers_model.h rename to client/ui/models/containersModel.h index 9e19025ac..e5f71b01d 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containersModel.h @@ -6,7 +6,10 @@ #include #include -#include "containers/containers_defs.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/containerConfig.h" class ContainersModel : public QAbstractListModel { @@ -22,6 +25,7 @@ public: ConfigRole, IsThirdPartyConfigRole, DockerContainerRole, + ContainerStringRole, IsEasySetupContainerRole, EasySetupHeaderRole, @@ -35,8 +39,19 @@ public: IsSupportedRole, IsShareableRole, - InstallPageOrderRole + InstallPageOrderRole, + + // Container type check roles + IsVpnContainerRole, + IsServiceContainerRole, + IsIpsecRole, + IsDnsRole, + IsSftpRole, + IsTorWebsiteRole, + IsSocks5ProxyRole }; + + Q_INVOKABLE void openContainerSettings(int containerIndex); int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -44,10 +59,9 @@ public: QVariant data(const int index, int role) const; public slots: - void updateModel(const QJsonArray &containers); + void updateModel(const QMap &containers); void setProcessedContainerIndex(int containerIndex); - int getProcessedContainerIndex(); QString getProcessedContainerName(); @@ -59,7 +73,7 @@ public slots: bool hasInstalledServices(); bool hasInstalledProtocols(); - static bool isInstallationAllowed(DockerContainer container); + static bool isInstallationAllowed(amnezia::DockerContainer container); protected: QHash roleNames() const override; @@ -68,9 +82,9 @@ signals: void containersModelUpdated(); private: - QMap m_containers; + QMap m_containers; - int m_processedContainerIndex; + int m_processedContainerIndex = -1; }; #endif // CONTAINERS_MODEL_H diff --git a/client/ui/models/installedAppsModel.cpp b/client/ui/models/installedAppsModel.cpp index 19c60dbdb..ea32a2559 100644 --- a/client/ui/models/installedAppsModel.cpp +++ b/client/ui/models/installedAppsModel.cpp @@ -1,6 +1,7 @@ #include "installedAppsModel.h" #include +#include #include #ifdef Q_OS_ANDROID @@ -73,21 +74,21 @@ QVector> InstalledAppsModel::getSelectedAppsInfo() void InstalledAppsModel::updateModel() { - QFuture future = QtConcurrent::run([this]() { - beginResetModel(); #ifdef Q_OS_ANDROID - m_installedApps = AndroidController::instance()->getAppList(); -#endif - endResetModel(); + QFuture future = QtConcurrent::run([]() { + return AndroidController::instance()->getAppList(); }); - QFutureWatcher watcher; + QFutureWatcher watcher; QEventLoop wait; - connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); + connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); watcher.setFuture(future); wait.exec(); - return; + beginResetModel(); + m_installedApps = future.result(); + endResetModel(); +#endif } QHash InstalledAppsModel::roleNames() const diff --git a/client/ui/models/ipSplitTunnelingModel.cpp b/client/ui/models/ipSplitTunnelingModel.cpp new file mode 100644 index 000000000..d65668421 --- /dev/null +++ b/client/ui/models/ipSplitTunnelingModel.cpp @@ -0,0 +1,49 @@ +#include "ipSplitTunnelingModel.h" + +IpSplitTunnelingModel::IpSplitTunnelingModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +int IpSplitTunnelingModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_sites.size(); +} + +QVariant IpSplitTunnelingModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) + return QVariant(); + + switch (role) { + case UrlRole: { + return m_sites.at(index.row()).first; + break; + } + case IpRole: { + return m_sites.at(index.row()).second; + break; + } + default: { + return true; + } + } + + return QVariant(); +} + +void IpSplitTunnelingModel::updateModel(const QVector> &sites) +{ + beginResetModel(); + m_sites = sites; + endResetModel(); +} + +QHash IpSplitTunnelingModel::roleNames() const +{ + QHash roles; + roles[UrlRole] = "url"; + roles[IpRole] = "ip"; + return roles; +} diff --git a/client/ui/models/ipSplitTunnelingModel.h b/client/ui/models/ipSplitTunnelingModel.h new file mode 100644 index 000000000..3e8b0c0e6 --- /dev/null +++ b/client/ui/models/ipSplitTunnelingModel.h @@ -0,0 +1,34 @@ +#ifndef IPSPLITTUNNELINGMODEL_H +#define IPSPLITTUNNELINGMODEL_H + +#include +#include +#include + +class IpSplitTunnelingModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + UrlRole = Qt::UserRole + 1, + IpRole + }; + + explicit IpSplitTunnelingModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QVector> &sites); + +protected: + QHash roleNames() const override; + +private: + QVector> m_sites; +}; + +#endif // IPSPLITTUNNELINGMODEL_H diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index f1c9ebc23..f56fa9a5c 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -1,6 +1,6 @@ #include "languageModel.h" -LanguageModel::LanguageModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) +LanguageModel::LanguageModel(QObject *parent) : QAbstractListModel(parent) { QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); i++) { @@ -54,86 +54,3 @@ QString LanguageModel::getLocalLanguageName(const LanguageSettings::AvailableLan return strLanguage; } -void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum language) -{ - switch (language) { - case LanguageSettings::AvailableLanguageEnum::English: emit updateTranslations(QLocale::English); break; - case LanguageSettings::AvailableLanguageEnum::Russian: emit updateTranslations(QLocale::Russian); break; - case LanguageSettings::AvailableLanguageEnum::China_cn: emit updateTranslations(QLocale::Chinese); break; - case LanguageSettings::AvailableLanguageEnum::Ukrainian: emit updateTranslations(QLocale::Ukrainian); break; - case LanguageSettings::AvailableLanguageEnum::Persian: emit updateTranslations(QLocale::Persian); break; - case LanguageSettings::AvailableLanguageEnum::Arabic: emit updateTranslations(QLocale::Arabic); break; - case LanguageSettings::AvailableLanguageEnum::Burmese: emit updateTranslations(QLocale::Burmese); break; - case LanguageSettings::AvailableLanguageEnum::Urdu: emit updateTranslations(QLocale::Urdu); break; - case LanguageSettings::AvailableLanguageEnum::Hindi: emit updateTranslations(QLocale::Hindi); break; - default: emit updateTranslations(QLocale::English); break; - } -} - -int LanguageModel::getCurrentLanguageIndex() -{ - auto locale = m_settings->getAppLanguage(); - switch (locale.language()) { - case QLocale::English: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; - case QLocale::Russian: return static_cast(LanguageSettings::AvailableLanguageEnum::Russian); break; - case QLocale::Chinese: return static_cast(LanguageSettings::AvailableLanguageEnum::China_cn); break; - case QLocale::Ukrainian: return static_cast(LanguageSettings::AvailableLanguageEnum::Ukrainian); break; - case QLocale::Persian: return static_cast(LanguageSettings::AvailableLanguageEnum::Persian); break; - case QLocale::Arabic: return static_cast(LanguageSettings::AvailableLanguageEnum::Arabic); break; - case QLocale::Burmese: return static_cast(LanguageSettings::AvailableLanguageEnum::Burmese); break; - case QLocale::Urdu: return static_cast(LanguageSettings::AvailableLanguageEnum::Urdu); break; - case QLocale::Hindi: return static_cast(LanguageSettings::AvailableLanguageEnum::Hindi); break; - default: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; - } -} - -int LanguageModel::getLineHeightAppend() -{ - auto language = static_cast(getCurrentLanguageIndex()); - switch (language) { - case LanguageSettings::AvailableLanguageEnum::Burmese: return 10; break; - default: return 0; break; - } -} - -QString LanguageModel::getCurrentLanguageName() -{ - return m_availableLanguages[getCurrentLanguageIndex()].name; -} - -LanguageSettings::AvailableLanguageEnum LanguageModel::getSystemLanguageEnum() -{ - QLocale locale = QLocale::system(); - switch (locale.language()) { - case QLocale::Russian: return LanguageSettings::AvailableLanguageEnum::Russian; - case QLocale::Chinese: return LanguageSettings::AvailableLanguageEnum::China_cn; - case QLocale::Ukrainian: return LanguageSettings::AvailableLanguageEnum::Ukrainian; - case QLocale::Persian: return LanguageSettings::AvailableLanguageEnum::Persian; - case QLocale::Arabic: return LanguageSettings::AvailableLanguageEnum::Arabic; - case QLocale::Burmese: return LanguageSettings::AvailableLanguageEnum::Burmese; - case QLocale::Urdu: return LanguageSettings::AvailableLanguageEnum::Urdu; - case QLocale::Hindi: return LanguageSettings::AvailableLanguageEnum::Hindi; - case QLocale::English: return LanguageSettings::AvailableLanguageEnum::English; - default: return LanguageSettings::AvailableLanguageEnum::English; - } -} - -QString LanguageModel::getCurrentSiteUrl(const QString &path) -{ - auto language = static_cast(getCurrentLanguageIndex()); - switch (language) { - case LanguageSettings::AvailableLanguageEnum::Russian: - return "https://storage.googleapis.com/amnezia/amnezia.org" + (path.isEmpty() ? "" : (QString("?m-path=/%1").arg(path))); - default: return QString("https://amnezia.org") + (path.isEmpty() ? "" : (QString("/%1").arg(path))); - } -} - -QString LanguageModel::getCurrentDocsUrl(const QString &path) -{ - auto language = static_cast(getCurrentLanguageIndex()); - switch (language) { - case LanguageSettings::AvailableLanguageEnum::Russian: - return "https://storage.googleapis.com/amnezia/docs" + (path.isEmpty() ? "" : (QString("?m-path=/%1").arg(path))); - default: return QString("https://docs.amnezia.org") + (path.isEmpty() ? "" : (QString("/%1").arg(path))); - } -} diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h index 6c3241d81..3ff264066 100644 --- a/client/ui/models/languageModel.h +++ b/client/ui/models/languageModel.h @@ -4,8 +4,6 @@ #include #include -#include "settings.h" - namespace LanguageSettings { Q_NAMESPACE @@ -45,28 +43,11 @@ public: IndexRole }; - LanguageModel(std::shared_ptr settings, QObject *parent = nullptr); - LanguageSettings::AvailableLanguageEnum getSystemLanguageEnum(); + LanguageModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - Q_PROPERTY(QString currentLanguageName READ getCurrentLanguageName NOTIFY translationsUpdated) - Q_PROPERTY(int currentLanguageIndex READ getCurrentLanguageIndex NOTIFY translationsUpdated) - Q_PROPERTY(int lineHeightAppend READ getLineHeightAppend NOTIFY translationsUpdated) - -public slots: - void changeLanguage(const LanguageSettings::AvailableLanguageEnum language); - int getCurrentLanguageIndex(); - int getLineHeightAppend(); - QString getCurrentLanguageName(); - QString getCurrentSiteUrl(const QString &path = ""); - QString getCurrentDocsUrl(const QString &path = ""); - -signals: - void updateTranslations(const QLocale &locale); - void translationsUpdated(); - protected: QHash roleNames() const override; @@ -74,8 +55,6 @@ private: QString getLocalLanguageName(const LanguageSettings::AvailableLanguageEnum language); QVector m_availableLanguages; - - std::shared_ptr m_settings; }; #endif // LANGUAGEMODEL_H diff --git a/client/ui/models/newsModel.cpp b/client/ui/models/newsModel.cpp index be1a27b32..13bdb4ed0 100644 --- a/client/ui/models/newsModel.cpp +++ b/client/ui/models/newsModel.cpp @@ -1,4 +1,5 @@ #include "ui/models/newsModel.h" +#include "core/repositories/secureAppSettingsRepository.h" #include #include #include @@ -9,7 +10,8 @@ #include #include -NewsModel::NewsModel(const std::shared_ptr &settings, QObject *parent) : QAbstractListModel(parent), m_settings(settings) +NewsModel::NewsModel(SecureAppSettingsRepository* appSettingsRepository, QObject *parent) + : QAbstractListModel(parent), m_appSettingsRepository(appSettingsRepository) { loadReadIds(); } @@ -114,11 +116,11 @@ bool NewsModel::hasUnread() const void NewsModel::loadReadIds() { - QStringList ids = m_settings->readNewsIds(); + QStringList ids = m_appSettingsRepository->getReadNewsIds(); m_readIds = QSet(ids.begin(), ids.end()); } void NewsModel::saveReadIds() const { - m_settings->setReadNewsIds(QStringList(m_readIds.begin(), m_readIds.end())); + m_appSettingsRepository->setReadNewsIds(QStringList(m_readIds.begin(), m_readIds.end())); } diff --git a/client/ui/models/newsModel.h b/client/ui/models/newsModel.h index 6188a9818..c2d4e488c 100644 --- a/client/ui/models/newsModel.h +++ b/client/ui/models/newsModel.h @@ -1,14 +1,12 @@ #ifndef NEWSMODEL_H #define NEWSMODEL_H -#include "settings.h" #include #include #include #include #include #include -#include struct NewsItem { @@ -31,7 +29,7 @@ public: IsReadRole, IsProcessedRole }; - explicit NewsModel(const std::shared_ptr &settings, QObject *parent = nullptr); + explicit NewsModel(class SecureAppSettingsRepository* appSettingsRepository, QObject *parent = nullptr); Q_INVOKABLE void markAsRead(int index); Q_PROPERTY(int processedIndex READ processedIndex WRITE setProcessedIndex NOTIFY processedIndexChanged) @@ -53,7 +51,7 @@ signals: private: QVector m_items; int m_processedIndex = -1; - std::shared_ptr m_settings; + class SecureAppSettingsRepository* m_appSettingsRepository; QSet m_readIds; void loadReadIds(); void saveReadIds() const; diff --git a/client/ui/models/protocolProps.h b/client/ui/models/protocolProps.h new file mode 100644 index 000000000..393cae1b3 --- /dev/null +++ b/client/ui/models/protocolProps.h @@ -0,0 +1,17 @@ +#ifndef PROTOCOLPROPS_H +#define PROTOCOLPROPS_H + +#include +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" + +class ProtocolProps : public QObject +{ + Q_OBJECT + +public: + explicit ProtocolProps(QObject *parent = nullptr) : QObject(parent) {} +}; + +#endif // PROTOCOLPROPS_H + diff --git a/client/ui/models/protocols/awgConfigModel.cpp b/client/ui/models/protocols/awgConfigModel.cpp index 40de5bdde..5906fa53d 100644 --- a/client/ui/models/protocols/awgConfigModel.cpp +++ b/client/ui/models/protocols/awgConfigModel.cpp @@ -2,7 +2,14 @@ #include -#include "protocols/protocols_defs.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/models/protocols/awgProtocolConfig.h" + +using namespace amnezia; +using namespace ProtocolUtils; AwgConfigModel::AwgConfigModel(QObject *parent) : QAbstractListModel(parent) { @@ -16,51 +23,43 @@ int AwgConfigModel::rowCount(const QModelIndex &parent) const bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerUtils::allContainers().size()) { return false; } - switch (role) { - case Roles::SubnetAddressRole: m_serverProtocolConfig.insert(config_key::subnet_address, value.toString()); break; - case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break; + QString strValue = value.toString(); - case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break; - case Roles::ClientJunkPacketCountRole: m_clientProtocolConfig.insert(config_key::junkPacketCount, value.toString()); break; - case Roles::ClientJunkPacketMinSizeRole: m_clientProtocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break; - case Roles::ClientJunkPacketMaxSizeRole: m_clientProtocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break; - case Roles::ClientSpecialJunk1Role: m_clientProtocolConfig.insert(config_key::specialJunk1, value.toString()); break; - case Roles::ClientSpecialJunk2Role: m_clientProtocolConfig.insert(config_key::specialJunk2, value.toString()); break; - case Roles::ClientSpecialJunk3Role: m_clientProtocolConfig.insert(config_key::specialJunk3, value.toString()); break; - case Roles::ClientSpecialJunk4Role: m_clientProtocolConfig.insert(config_key::specialJunk4, value.toString()); break; - case Roles::ClientSpecialJunk5Role: m_clientProtocolConfig.insert(config_key::specialJunk5, value.toString()); break; - case Roles::ServerJunkPacketCountRole: m_serverProtocolConfig.insert(config_key::junkPacketCount, value.toString()); break; - case Roles::ServerJunkPacketMinSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break; - case Roles::ServerJunkPacketMaxSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break; - case Roles::ServerInitPacketJunkSizeRole: m_serverProtocolConfig.insert(config_key::initPacketJunkSize, value.toString()); break; - case Roles::ServerResponsePacketJunkSizeRole: - m_serverProtocolConfig.insert(config_key::responsePacketJunkSize, value.toString()); - break; - case Roles::ServerCookieReplyPacketJunkSizeRole: - m_serverProtocolConfig.insert(config_key::cookieReplyPacketJunkSize, value.toString()); - break; - case Roles::ServerTransportPacketJunkSizeRole: - m_serverProtocolConfig.insert(config_key::transportPacketJunkSize, value.toString()); - break; - case Roles::ServerInitPacketMagicHeaderRole: m_serverProtocolConfig.insert(config_key::initPacketMagicHeader, value.toString()); break; - case Roles::ServerResponsePacketMagicHeaderRole: - m_serverProtocolConfig.insert(config_key::responsePacketMagicHeader, value.toString()); - break; - case Roles::ServerUnderloadPacketMagicHeaderRole: - m_serverProtocolConfig.insert(config_key::underloadPacketMagicHeader, value.toString()); - break; - case Roles::ServerTransportPacketMagicHeaderRole: - m_serverProtocolConfig.insert(config_key::transportPacketMagicHeader, value.toString()); - break; - case Roles::ServerSpecialJunk1Role: m_serverProtocolConfig.insert(config_key::specialJunk1, value.toString()); break; - case Roles::ServerSpecialJunk2Role: m_serverProtocolConfig.insert(config_key::specialJunk2, value.toString()); break; - case Roles::ServerSpecialJunk3Role: m_serverProtocolConfig.insert(config_key::specialJunk3, value.toString()); break; - case Roles::ServerSpecialJunk4Role: m_serverProtocolConfig.insert(config_key::specialJunk4, value.toString()); break; - case Roles::ServerSpecialJunk5Role: m_serverProtocolConfig.insert(config_key::specialJunk5, value.toString()); break; + switch (role) { + case Roles::SubnetAddressRole: m_protocolConfig.serverConfig.subnetAddress = strValue; break; + case Roles::PortRole: m_protocolConfig.serverConfig.port = strValue; break; + + case Roles::ClientMtuRole: m_protocolConfig.clientConfig->mtu = strValue; break; + case Roles::ClientJunkPacketCountRole: m_protocolConfig.clientConfig->junkPacketCount = strValue; break; + case Roles::ClientJunkPacketMinSizeRole: m_protocolConfig.clientConfig->junkPacketMinSize = strValue; break; + case Roles::ClientJunkPacketMaxSizeRole: m_protocolConfig.clientConfig->junkPacketMaxSize = strValue; break; + case Roles::ClientSpecialJunk1Role: m_protocolConfig.clientConfig->specialJunk1 = strValue; break; + case Roles::ClientSpecialJunk2Role: m_protocolConfig.clientConfig->specialJunk2 = strValue; break; + case Roles::ClientSpecialJunk3Role: m_protocolConfig.clientConfig->specialJunk3 = strValue; break; + case Roles::ClientSpecialJunk4Role: m_protocolConfig.clientConfig->specialJunk4 = strValue; break; + case Roles::ClientSpecialJunk5Role: m_protocolConfig.clientConfig->specialJunk5 = strValue; break; + case Roles::ServerJunkPacketCountRole: m_protocolConfig.serverConfig.junkPacketCount = strValue; break; + case Roles::ServerJunkPacketMinSizeRole: m_protocolConfig.serverConfig.junkPacketMinSize = strValue; break; + case Roles::ServerJunkPacketMaxSizeRole: m_protocolConfig.serverConfig.junkPacketMaxSize = strValue; break; + case Roles::ServerInitPacketJunkSizeRole: m_protocolConfig.serverConfig.initPacketJunkSize = strValue; break; + case Roles::ServerResponsePacketJunkSizeRole: m_protocolConfig.serverConfig.responsePacketJunkSize = strValue; break; + case Roles::ServerCookieReplyPacketJunkSizeRole: m_protocolConfig.serverConfig.cookieReplyPacketJunkSize = strValue; break; + case Roles::ServerTransportPacketJunkSizeRole: m_protocolConfig.serverConfig.transportPacketJunkSize = strValue; break; + case Roles::ServerInitPacketMagicHeaderRole: m_protocolConfig.serverConfig.initPacketMagicHeader = strValue; break; + case Roles::ServerResponsePacketMagicHeaderRole: m_protocolConfig.serverConfig.responsePacketMagicHeader = strValue; break; + case Roles::ServerUnderloadPacketMagicHeaderRole: m_protocolConfig.serverConfig.underloadPacketMagicHeader = strValue; break; + case Roles::ServerTransportPacketMagicHeaderRole: m_protocolConfig.serverConfig.transportPacketMagicHeader = strValue; break; + case Roles::ServerSpecialJunk1Role: m_protocolConfig.serverConfig.specialJunk1 = strValue; break; + case Roles::ServerSpecialJunk2Role: m_protocolConfig.serverConfig.specialJunk2 = strValue; break; + case Roles::ServerSpecialJunk3Role: m_protocolConfig.serverConfig.specialJunk3 = strValue; break; + case Roles::ServerSpecialJunk4Role: m_protocolConfig.serverConfig.specialJunk4 = strValue; break; + case Roles::ServerSpecialJunk5Role: m_protocolConfig.serverConfig.specialJunk5 = strValue; break; + default: + return false; } emit dataChanged(index, index, QList { role }); @@ -70,190 +69,215 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in QVariant AwgConfigModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { - return false; + return QVariant(); } switch (role) { - case Roles::SubnetAddressRole: return m_serverProtocolConfig.value(config_key::subnet_address).toString(); - case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString(); + case Roles::SubnetAddressRole: return m_protocolConfig.serverConfig.subnetAddress; + case Roles::PortRole: return m_protocolConfig.serverConfig.port; - case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu); - case Roles::ClientJunkPacketCountRole: return m_clientProtocolConfig.value(config_key::junkPacketCount); - case Roles::ClientJunkPacketMinSizeRole: return m_clientProtocolConfig.value(config_key::junkPacketMinSize); - case Roles::ClientJunkPacketMaxSizeRole: return m_clientProtocolConfig.value(config_key::junkPacketMaxSize); - case Roles::ClientSpecialJunk1Role: return m_clientProtocolConfig.value(config_key::specialJunk1); - case Roles::ClientSpecialJunk2Role: return m_clientProtocolConfig.value(config_key::specialJunk2); - case Roles::ClientSpecialJunk3Role: return m_clientProtocolConfig.value(config_key::specialJunk3); - case Roles::ClientSpecialJunk4Role: return m_clientProtocolConfig.value(config_key::specialJunk4); - case Roles::ClientSpecialJunk5Role: return m_clientProtocolConfig.value(config_key::specialJunk5); + case Roles::ClientMtuRole: return m_protocolConfig.clientConfig->mtu; + case Roles::ClientJunkPacketCountRole: return m_protocolConfig.clientConfig->junkPacketCount; + case Roles::ClientJunkPacketMinSizeRole: return m_protocolConfig.clientConfig->junkPacketMinSize; + case Roles::ClientJunkPacketMaxSizeRole: return m_protocolConfig.clientConfig->junkPacketMaxSize; + case Roles::ClientSpecialJunk1Role: return m_protocolConfig.clientConfig->specialJunk1; + case Roles::ClientSpecialJunk2Role: return m_protocolConfig.clientConfig->specialJunk2; + case Roles::ClientSpecialJunk3Role: return m_protocolConfig.clientConfig->specialJunk3; + case Roles::ClientSpecialJunk4Role: return m_protocolConfig.clientConfig->specialJunk4; + case Roles::ClientSpecialJunk5Role: return m_protocolConfig.clientConfig->specialJunk5; - case Roles::ServerJunkPacketCountRole: return m_serverProtocolConfig.value(config_key::junkPacketCount); - case Roles::ServerJunkPacketMinSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMinSize); - case Roles::ServerJunkPacketMaxSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMaxSize); - case Roles::ServerInitPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::initPacketJunkSize); - case Roles::ServerResponsePacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::responsePacketJunkSize); - case Roles::ServerCookieReplyPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize); - case Roles::ServerTransportPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::transportPacketJunkSize); - case Roles::ServerInitPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::initPacketMagicHeader); - case Roles::ServerResponsePacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::responsePacketMagicHeader); - case Roles::ServerUnderloadPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::underloadPacketMagicHeader); - case Roles::ServerTransportPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::transportPacketMagicHeader); - case Roles::ServerSpecialJunk1Role: return m_serverProtocolConfig.value(config_key::specialJunk1); - case Roles::ServerSpecialJunk2Role: return m_serverProtocolConfig.value(config_key::specialJunk2); - case Roles::ServerSpecialJunk3Role: return m_serverProtocolConfig.value(config_key::specialJunk3); - case Roles::ServerSpecialJunk4Role: return m_serverProtocolConfig.value(config_key::specialJunk4); - case Roles::ServerSpecialJunk5Role: return m_serverProtocolConfig.value(config_key::specialJunk5); + case Roles::ServerJunkPacketCountRole: return m_protocolConfig.serverConfig.junkPacketCount; + case Roles::ServerJunkPacketMinSizeRole: return m_protocolConfig.serverConfig.junkPacketMinSize; + case Roles::ServerJunkPacketMaxSizeRole: return m_protocolConfig.serverConfig.junkPacketMaxSize; + case Roles::ServerInitPacketJunkSizeRole: return m_protocolConfig.serverConfig.initPacketJunkSize; + case Roles::ServerResponsePacketJunkSizeRole: return m_protocolConfig.serverConfig.responsePacketJunkSize; + case Roles::ServerCookieReplyPacketJunkSizeRole: return m_protocolConfig.serverConfig.cookieReplyPacketJunkSize; + case Roles::ServerTransportPacketJunkSizeRole: return m_protocolConfig.serverConfig.transportPacketJunkSize; + case Roles::ServerInitPacketMagicHeaderRole: return m_protocolConfig.serverConfig.initPacketMagicHeader; + case Roles::ServerResponsePacketMagicHeaderRole: return m_protocolConfig.serverConfig.responsePacketMagicHeader; + case Roles::ServerUnderloadPacketMagicHeaderRole: return m_protocolConfig.serverConfig.underloadPacketMagicHeader; + case Roles::ServerTransportPacketMagicHeaderRole: return m_protocolConfig.serverConfig.transportPacketMagicHeader; + case Roles::ServerSpecialJunk1Role: return m_protocolConfig.serverConfig.specialJunk1; + case Roles::ServerSpecialJunk2Role: return m_protocolConfig.serverConfig.specialJunk2; + case Roles::ServerSpecialJunk3Role: return m_protocolConfig.serverConfig.specialJunk3; + case Roles::ServerSpecialJunk4Role: return m_protocolConfig.serverConfig.specialJunk4; + case Roles::ServerSpecialJunk5Role: return m_protocolConfig.serverConfig.specialJunk5; - case Roles::IsAwg2Role: return ProtocolProps::getProtocolVersion(m_fullConfig.value(config_key::awg).toObject()) == protocols::awg::awgV2; + case Roles::IsAwg2Role: return m_protocolConfig.serverConfig.protocolVersion == protocols::awg::awgV2; } return QVariant(); } -void AwgConfigModel::updateModel(const QJsonObject &config) +void AwgConfigModel::updateModel(amnezia::DockerContainer container, const amnezia::AwgProtocolConfig &protocolConfig) { beginResetModel(); - m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); - - m_fullConfig = config; - - QJsonObject serverProtocolConfig = config.value(config_key::awg).toObject(); - - auto protocolVersion = serverProtocolConfig.value(config_key::protocolVersion).toString(); - if (!protocolVersion.isEmpty()) { - m_serverProtocolConfig[config_key::protocolVersion] = protocolVersion; + m_container = container; + + m_protocolConfig = protocolConfig; + + applyDefaultsToServerConfig(m_protocolConfig.serverConfig); + + if (!m_protocolConfig.clientConfig.has_value()) { + m_protocolConfig.clientConfig = amnezia::AwgClientConfig{}; } + applyDefaultsToClientConfig(m_protocolConfig.clientConfig.value()); - auto defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(Proto::Awg), Proto::Awg); - m_serverProtocolConfig.insert(config_key::transport_proto, - serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto)); - m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config); - m_serverProtocolConfig[config_key::subnet_address] = - serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); - m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); - m_serverProtocolConfig[config_key::junkPacketCount] = - serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); - m_serverProtocolConfig[config_key::junkPacketMinSize] = - serverProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize); - m_serverProtocolConfig[config_key::junkPacketMaxSize] = - serverProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize); - m_serverProtocolConfig[config_key::initPacketJunkSize] = - serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize); - m_serverProtocolConfig[config_key::responsePacketJunkSize] = - serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize); - if (protocolVersion == protocols::awg::awgV2) { - m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] = - serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize); - m_serverProtocolConfig[config_key::transportPacketJunkSize] = - serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize); - } - m_serverProtocolConfig[config_key::initPacketMagicHeader] = - serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader); - m_serverProtocolConfig[config_key::responsePacketMagicHeader] = - serverProtocolConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader); - m_serverProtocolConfig[config_key::underloadPacketMagicHeader] = - serverProtocolConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader); - m_serverProtocolConfig[config_key::transportPacketMagicHeader] = - serverProtocolConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader); + m_originalProtocolConfig = m_protocolConfig; - m_serverProtocolConfig[config_key::specialJunk1] = - serverProtocolConfig.value(config_key::specialJunk1).toString(); - m_serverProtocolConfig[config_key::specialJunk2] = - serverProtocolConfig.value(config_key::specialJunk2).toString(); - m_serverProtocolConfig[config_key::specialJunk3] = - serverProtocolConfig.value(config_key::specialJunk3).toString(); - m_serverProtocolConfig[config_key::specialJunk4] = - serverProtocolConfig.value(config_key::specialJunk4).toString(); - m_serverProtocolConfig[config_key::specialJunk5] = - serverProtocolConfig.value(config_key::specialJunk5).toString(); - - auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString(); - QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - m_clientProtocolConfig[config_key::mtu] = clientProtocolConfig[config_key::mtu].toString(protocols::awg::defaultMtu); - m_clientProtocolConfig[config_key::junkPacketCount] = - clientProtocolConfig.value(config_key::junkPacketCount).toString(m_serverProtocolConfig[config_key::junkPacketCount].toString()); - m_clientProtocolConfig[config_key::junkPacketMinSize] = - clientProtocolConfig.value(config_key::junkPacketMinSize).toString(m_serverProtocolConfig[config_key::junkPacketMinSize].toString()); - m_clientProtocolConfig[config_key::junkPacketMaxSize] = - clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(m_serverProtocolConfig[config_key::junkPacketMaxSize].toString()); - m_clientProtocolConfig[config_key::specialJunk1] = - clientProtocolConfig.value(config_key::specialJunk1).toString(); - m_clientProtocolConfig[config_key::specialJunk2] = - clientProtocolConfig.value(config_key::specialJunk2).toString(); - m_clientProtocolConfig[config_key::specialJunk3] = - clientProtocolConfig.value(config_key::specialJunk3).toString(); - m_clientProtocolConfig[config_key::specialJunk4] = - clientProtocolConfig.value(config_key::specialJunk4).toString(); - m_clientProtocolConfig[config_key::specialJunk5] = - clientProtocolConfig.value(config_key::specialJunk5).toString(); endResetModel(); } -QJsonObject AwgConfigModel::getConfig() +void AwgConfigModel::applyDefaultsToServerConfig(amnezia::AwgServerConfig& config) { - const AwgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject()); - const AwgConfig newConfig(m_serverProtocolConfig); - - if (!oldConfig.hasEqualServerSettings(newConfig)) { - m_serverProtocolConfig.remove(config_key::last_config); - } else { - auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString(); - QJsonObject jsonConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - jsonConfig[config_key::mtu] = m_clientProtocolConfig[config_key::mtu]; - jsonConfig[config_key::junkPacketCount] = m_clientProtocolConfig[config_key::junkPacketCount]; - jsonConfig[config_key::junkPacketMinSize] = m_clientProtocolConfig[config_key::junkPacketMinSize]; - jsonConfig[config_key::junkPacketMaxSize] = m_clientProtocolConfig[config_key::junkPacketMaxSize]; - jsonConfig[config_key::specialJunk1] = m_clientProtocolConfig[config_key::specialJunk1].toString().trimmed(); - jsonConfig[config_key::specialJunk2] = m_clientProtocolConfig[config_key::specialJunk2].toString().trimmed(); - jsonConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3].toString().trimmed(); - jsonConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4].toString().trimmed(); - jsonConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5].toString().trimmed(); - - m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson()); + if (config.subnetAddress.isEmpty()) { + config.subnetAddress = protocols::wireguard::defaultSubnetAddress; } - - QString currentProtocolVersion = m_serverProtocolConfig.value(config_key::protocolVersion).toString(); - - if (currentProtocolVersion != protocols::awg::awgV2) { - bool hasSpecialJunk = !m_serverProtocolConfig.value(config_key::specialJunk1).toString().trimmed().isEmpty() || - !m_serverProtocolConfig.value(config_key::specialJunk2).toString().trimmed().isEmpty() || - !m_serverProtocolConfig.value(config_key::specialJunk3).toString().trimmed().isEmpty() || - !m_serverProtocolConfig.value(config_key::specialJunk4).toString().trimmed().isEmpty() || - !m_serverProtocolConfig.value(config_key::specialJunk5).toString().trimmed().isEmpty(); - - if (hasSpecialJunk) { - m_serverProtocolConfig[config_key::protocolVersion] = protocols::awg::awgV1_5; - } else { - m_serverProtocolConfig.remove(config_key::protocolVersion); + if (config.port.isEmpty()) { + config.port = protocols::awg::defaultPort; + } + if (config.transportProto.isEmpty()) { + config.transportProto = ProtocolUtils::transportProtoToString( + ProtocolUtils::defaultTransportProto(amnezia::Proto::Awg), amnezia::Proto::Awg); + } + if (config.junkPacketCount.isEmpty()) { + config.junkPacketCount = protocols::awg::defaultJunkPacketCount; + } + if (config.junkPacketMinSize.isEmpty()) { + config.junkPacketMinSize = protocols::awg::defaultJunkPacketMinSize; + } + if (config.junkPacketMaxSize.isEmpty()) { + config.junkPacketMaxSize = protocols::awg::defaultJunkPacketMaxSize; + } + if (config.initPacketJunkSize.isEmpty()) { + config.initPacketJunkSize = protocols::awg::defaultInitPacketJunkSize; + } + if (config.responsePacketJunkSize.isEmpty()) { + config.responsePacketJunkSize = protocols::awg::defaultResponsePacketJunkSize; + } + if (config.protocolVersion == protocols::awg::awgV2) { + if (config.cookieReplyPacketJunkSize.isEmpty()) { + config.cookieReplyPacketJunkSize = protocols::awg::defaultCookieReplyPacketJunkSize; + } + if (config.transportPacketJunkSize.isEmpty()) { + config.transportPacketJunkSize = protocols::awg::defaultTransportPacketJunkSize; } } - - m_fullConfig.insert(config_key::awg, m_serverProtocolConfig); - return m_fullConfig; + if (config.initPacketMagicHeader.isEmpty()) { + config.initPacketMagicHeader = protocols::awg::defaultInitPacketMagicHeader; + } + if (config.responsePacketMagicHeader.isEmpty()) { + config.responsePacketMagicHeader = protocols::awg::defaultResponsePacketMagicHeader; + } + if (config.underloadPacketMagicHeader.isEmpty()) { + config.underloadPacketMagicHeader = protocols::awg::defaultUnderloadPacketMagicHeader; + } + if (config.transportPacketMagicHeader.isEmpty()) { + config.transportPacketMagicHeader = protocols::awg::defaultTransportPacketMagicHeader; + } + if (config.specialJunk1.isEmpty()) { + config.specialJunk1 = protocols::awg::defaultSpecialJunk1; + } + if (config.specialJunk2.isEmpty()) { + config.specialJunk2 = protocols::awg::defaultSpecialJunk2; + } + if (config.specialJunk3.isEmpty()) { + config.specialJunk3 = protocols::awg::defaultSpecialJunk3; + } + if (config.specialJunk4.isEmpty()) { + config.specialJunk4 = protocols::awg::defaultSpecialJunk4; + } + if (config.specialJunk5.isEmpty()) { + config.specialJunk5 = protocols::awg::defaultSpecialJunk5; + } } -bool AwgConfigModel::isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4) +void AwgConfigModel::applyDefaultsToClientConfig(amnezia::AwgClientConfig& config) { - return (h1 == h2) || (h1 == h3) || (h1 == h4) || (h2 == h3) || (h2 == h4) || (h3 == h4); + if (config.mtu.isEmpty()) { + config.mtu = protocols::awg::defaultMtu; + } + if (config.junkPacketCount.isEmpty()) { + config.junkPacketCount = m_protocolConfig.serverConfig.junkPacketCount.isEmpty() + ? protocols::awg::defaultJunkPacketCount + : m_protocolConfig.serverConfig.junkPacketCount; + } + if (config.junkPacketMinSize.isEmpty()) { + config.junkPacketMinSize = m_protocolConfig.serverConfig.junkPacketMinSize.isEmpty() + ? protocols::awg::defaultJunkPacketMinSize + : m_protocolConfig.serverConfig.junkPacketMinSize; + } + if (config.junkPacketMaxSize.isEmpty()) { + config.junkPacketMaxSize = m_protocolConfig.serverConfig.junkPacketMaxSize.isEmpty() + ? protocols::awg::defaultJunkPacketMaxSize + : m_protocolConfig.serverConfig.junkPacketMaxSize; + } + if (config.specialJunk1.isEmpty()) { + config.specialJunk1 = m_protocolConfig.serverConfig.specialJunk1.isEmpty() + ? protocols::awg::defaultSpecialJunk1 + : m_protocolConfig.serverConfig.specialJunk1; + } + if (config.specialJunk2.isEmpty()) { + config.specialJunk2 = m_protocolConfig.serverConfig.specialJunk2.isEmpty() + ? protocols::awg::defaultSpecialJunk2 + : m_protocolConfig.serverConfig.specialJunk2; + } + if (config.specialJunk3.isEmpty()) { + config.specialJunk3 = m_protocolConfig.serverConfig.specialJunk3.isEmpty() + ? protocols::awg::defaultSpecialJunk3 + : m_protocolConfig.serverConfig.specialJunk3; + } + if (config.specialJunk4.isEmpty()) { + config.specialJunk4 = m_protocolConfig.serverConfig.specialJunk4.isEmpty() + ? protocols::awg::defaultSpecialJunk4 + : m_protocolConfig.serverConfig.specialJunk4; + } + if (config.specialJunk5.isEmpty()) { + config.specialJunk5 = m_protocolConfig.serverConfig.specialJunk5.isEmpty() + ? protocols::awg::defaultSpecialJunk5 + : m_protocolConfig.serverConfig.specialJunk5; + } } -bool AwgConfigModel::isPacketSizeEqual(const int s1, const int s2, const int s3, const int s4) +amnezia::AwgProtocolConfig AwgConfigModel::getProtocolConfig() { - int initSize = AwgConstant::messageInitiationSize + s1; - int responseSize = AwgConstant::messageResponseSize + s2; - int cookieSize = AwgConstant::messageCookieReplySize + s3; - int transportSize = AwgConstant::messageTransportSize + s4; - - return (initSize == responseSize || initSize == cookieSize || initSize == transportSize || responseSize == cookieSize - || responseSize == transportSize || cookieSize == transportSize); + bool serverSettingsChanged = !m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig); + + if (serverSettingsChanged) { + m_protocolConfig.clearClientConfig(); + } + + if (m_protocolConfig.serverConfig.protocolVersion.isEmpty() || + m_protocolConfig.serverConfig.protocolVersion != protocols::awg::awgV2) { + bool hasSpecialJunk = !m_protocolConfig.serverConfig.specialJunk1.trimmed().isEmpty() || + !m_protocolConfig.serverConfig.specialJunk2.trimmed().isEmpty() || + !m_protocolConfig.serverConfig.specialJunk3.trimmed().isEmpty() || + !m_protocolConfig.serverConfig.specialJunk4.trimmed().isEmpty() || + !m_protocolConfig.serverConfig.specialJunk5.trimmed().isEmpty(); + + if (hasSpecialJunk) { + m_protocolConfig.serverConfig.protocolVersion = protocols::awg::awgV1_5; + } else if (m_protocolConfig.serverConfig.protocolVersion.isEmpty()) { + m_protocolConfig.serverConfig.protocolVersion = QString(); + } + } + + return m_protocolConfig; } bool AwgConfigModel::isServerSettingsEqual() { - const AwgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject()); - const AwgConfig newConfig(m_serverProtocolConfig); + return m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig); +} - return oldConfig.hasEqualServerSettings(newConfig); +bool AwgConfigModel::isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4) +{ + return amnezia::AwgProtocolConfig::isHeadersEqual(h1, h2, h3, h4); +} + +bool AwgConfigModel::isPacketSizeEqual(const int s1, const int s2, const int s3, const int s4) +{ + return amnezia::AwgProtocolConfig::isPacketSizeEqual(s1, s2, s3, s4); } QHash AwgConfigModel::roleNames() const @@ -296,86 +320,3 @@ QHash AwgConfigModel::roleNames() const return roles; } -AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig) -{ - m_isProtocolV2 = ProtocolProps::getProtocolVersion(serverProtocolConfig) == protocols::awg::awgV2; - - auto lastConfig = serverProtocolConfig.value(config_key::last_config).toString(); - QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - clientMtu = clientProtocolConfig[config_key::mtu].toString(protocols::awg::defaultMtu); - clientJunkPacketCount = clientProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); - clientJunkPacketMinSize = clientProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize); - clientJunkPacketMaxSize = clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize); - clientSpecialJunk1 = clientProtocolConfig.value(config_key::specialJunk1).toString(protocols::awg::defaultSpecialJunk1); - clientSpecialJunk2 = clientProtocolConfig.value(config_key::specialJunk2).toString(protocols::awg::defaultSpecialJunk2); - clientSpecialJunk3 = clientProtocolConfig.value(config_key::specialJunk3).toString(protocols::awg::defaultSpecialJunk3); - clientSpecialJunk4 = clientProtocolConfig.value(config_key::specialJunk4).toString(protocols::awg::defaultSpecialJunk4); - clientSpecialJunk5 = clientProtocolConfig.value(config_key::specialJunk5).toString(protocols::awg::defaultSpecialJunk5); - - subnetAddress = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); - port = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); - serverJunkPacketCount = serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); - serverJunkPacketMinSize = serverProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize); - serverJunkPacketMaxSize = serverProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize); - serverInitPacketJunkSize = serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize); - serverResponsePacketJunkSize = - serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize); - - if (m_isProtocolV2) { - serverCookieReplyPacketJunkSize = - serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize); - serverTransportPacketJunkSize = - serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize); - } - - serverInitPacketMagicHeader = - serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader); - serverResponsePacketMagicHeader = - serverProtocolConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader); - serverUnderloadPacketMagicHeader = - serverProtocolConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader); - serverTransportPacketMagicHeader = - serverProtocolConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader); - serverSpecialJunk1 = serverProtocolConfig.value(config_key::specialJunk1).toString(protocols::awg::defaultSpecialJunk1); - serverSpecialJunk2 = serverProtocolConfig.value(config_key::specialJunk2).toString(protocols::awg::defaultSpecialJunk2); - serverSpecialJunk3 = serverProtocolConfig.value(config_key::specialJunk3).toString(protocols::awg::defaultSpecialJunk3); - serverSpecialJunk4 = serverProtocolConfig.value(config_key::specialJunk4).toString(protocols::awg::defaultSpecialJunk4); - serverSpecialJunk5 = serverProtocolConfig.value(config_key::specialJunk5).toString(protocols::awg::defaultSpecialJunk5); -} - -bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const -{ - if (subnetAddress != other.subnetAddress || port != other.port || serverJunkPacketCount != other.serverJunkPacketCount - || serverJunkPacketMinSize != other.serverJunkPacketMinSize || serverJunkPacketMaxSize != other.serverJunkPacketMaxSize - || serverInitPacketJunkSize != other.serverInitPacketJunkSize || serverResponsePacketJunkSize != other.serverResponsePacketJunkSize - || serverInitPacketMagicHeader != other.serverInitPacketMagicHeader - || serverResponsePacketMagicHeader != other.serverResponsePacketMagicHeader - || serverUnderloadPacketMagicHeader != other.serverUnderloadPacketMagicHeader - || serverTransportPacketMagicHeader != other.serverTransportPacketMagicHeader - || serverSpecialJunk1 != other.serverSpecialJunk1 || serverSpecialJunk2 != other.serverSpecialJunk2 - || serverSpecialJunk3 != other.serverSpecialJunk3 || serverSpecialJunk4 != other.serverSpecialJunk4 - || serverSpecialJunk5 != other.serverSpecialJunk5) { - return false; - } - - if (m_isProtocolV2) { - if (serverCookieReplyPacketJunkSize != other.serverCookieReplyPacketJunkSize - || serverTransportPacketJunkSize != other.serverTransportPacketJunkSize) { - return false; - } - } - - return true; -} - -bool AwgConfig::hasEqualClientSettings(const AwgConfig &other) const -{ - if (clientMtu != other.clientMtu || clientJunkPacketCount != other.clientJunkPacketCount - || clientJunkPacketMinSize != other.clientJunkPacketMinSize || clientJunkPacketMaxSize != other.clientJunkPacketMaxSize - || clientSpecialJunk1 != other.clientSpecialJunk1 || clientSpecialJunk2 != other.clientSpecialJunk2 - || clientSpecialJunk3 != other.clientSpecialJunk3 || clientSpecialJunk4 != other.clientSpecialJunk4 - || clientSpecialJunk5 != other.clientSpecialJunk5) { - return false; - } - return true; -} diff --git a/client/ui/models/protocols/awgConfigModel.h b/client/ui/models/protocols/awgConfigModel.h index f10704fb0..592ed5123 100644 --- a/client/ui/models/protocols/awgConfigModel.h +++ b/client/ui/models/protocols/awgConfigModel.h @@ -2,58 +2,11 @@ #define AWGCONFIGMODEL_H #include -#include -#include "containers/containers_defs.h" - -namespace AwgConstant -{ - const int messageInitiationSize = 148; - const int messageResponseSize = 92; - const int messageCookieReplySize = 64; - const int messageTransportSize = 32; -} - -struct AwgConfig -{ - AwgConfig(const QJsonObject &serverProtocolConfig); - - QString subnetAddress; - QString port; - - QString clientMtu; - QString clientJunkPacketCount; - QString clientJunkPacketMinSize; - QString clientJunkPacketMaxSize; - QString clientSpecialJunk1; - QString clientSpecialJunk2; - QString clientSpecialJunk3; - QString clientSpecialJunk4; - QString clientSpecialJunk5; - - QString serverJunkPacketCount; - QString serverJunkPacketMinSize; - QString serverJunkPacketMaxSize; - QString serverInitPacketJunkSize; - QString serverResponsePacketJunkSize; - QString serverCookieReplyPacketJunkSize; - QString serverTransportPacketJunkSize; - QString serverInitPacketMagicHeader; - QString serverResponsePacketMagicHeader; - QString serverUnderloadPacketMagicHeader; - QString serverTransportPacketMagicHeader; - QString serverSpecialJunk1; - QString serverSpecialJunk2; - QString serverSpecialJunk3; - QString serverSpecialJunk4; - QString serverSpecialJunk5; - - bool hasEqualServerSettings(const AwgConfig &other) const; - bool hasEqualClientSettings(const AwgConfig &other) const; - -private: - bool m_isProtocolV2; -}; +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/protocols/awgProtocolConfig.h" class AwgConfigModel : public QAbstractListModel { @@ -103,22 +56,23 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void updateModel(const QJsonObject &config); - QJsonObject getConfig(); - + void updateModel(amnezia::DockerContainer container, const amnezia::AwgProtocolConfig &protocolConfig); + amnezia::AwgProtocolConfig getProtocolConfig(); + bool isServerSettingsEqual(); + bool isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4); bool isPacketSizeEqual(const int s1, const int s2, const int s3, const int s4); - bool isServerSettingsEqual(); - protected: QHash roleNames() const override; private: - DockerContainer m_container; - QJsonObject m_serverProtocolConfig; - QJsonObject m_clientProtocolConfig; - QJsonObject m_fullConfig; + amnezia::DockerContainer m_container; + amnezia::AwgProtocolConfig m_protocolConfig; + amnezia::AwgProtocolConfig m_originalProtocolConfig; + + void applyDefaultsToServerConfig(amnezia::AwgServerConfig& config); + void applyDefaultsToClientConfig(amnezia::AwgClientConfig& config); }; #endif // AWGCONFIGMODEL_H diff --git a/client/ui/models/protocols/cloakConfigModel.cpp b/client/ui/models/protocols/cloakConfigModel.cpp deleted file mode 100644 index a9f06f4da..000000000 --- a/client/ui/models/protocols/cloakConfigModel.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "cloakConfigModel.h" - -#include "protocols/protocols_defs.h" - -CloakConfigModel::CloakConfigModel(QObject *parent) : QAbstractListModel(parent) -{ -} - -int CloakConfigModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent); - return 1; -} - -bool CloakConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { - return false; - } - - switch (role) { - case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; - case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; - case Roles::SiteRole: m_protocolConfig.insert(config_key::site, value.toString()); break; - } - - emit dataChanged(index, index, QList { role }); - return true; -} - -QVariant CloakConfigModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { - return false; - } - - switch (role) { - case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::cloak::defaultPort); - case Roles::CipherRole: return m_protocolConfig.value(config_key::cipher).toString(protocols::cloak::defaultCipher); - case Roles::SiteRole: return m_protocolConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite); - } - - return QVariant(); -} - -void CloakConfigModel::updateModel(const QJsonObject &config) -{ - beginResetModel(); - m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); - - m_fullConfig = config; - QJsonObject protocolConfig = config.value(config_key::cloak).toObject(); - - auto defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(Proto::Cloak), Proto::Cloak); - m_protocolConfig.insert(config_key::transport_proto, protocolConfig.value(config_key::transport_proto).toString(defaultTransportProto)); - m_protocolConfig.insert(config_key::cipher, protocolConfig.value(config_key::cipher).toString(protocols::cloak::defaultCipher)); - m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString(protocols::cloak::defaultPort)); - m_protocolConfig.insert(config_key::site, protocolConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite)); - - endResetModel(); -} - -QJsonObject CloakConfigModel::getConfig() -{ - m_fullConfig.insert(config_key::cloak, m_protocolConfig); - return m_fullConfig; -} - -QHash CloakConfigModel::roleNames() const -{ - QHash roles; - - roles[PortRole] = "port"; - roles[CipherRole] = "cipher"; - roles[SiteRole] = "site"; - - return roles; -} diff --git a/client/ui/models/protocols/cloakConfigModel.h b/client/ui/models/protocols/cloakConfigModel.h deleted file mode 100644 index 31ff8c53f..000000000 --- a/client/ui/models/protocols/cloakConfigModel.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CLOAKCONFIGMODEL_H -#define CLOAKCONFIGMODEL_H - -#include -#include - -#include "containers/containers_defs.h" - -class CloakConfigModel : public QAbstractListModel -{ - Q_OBJECT - -public: - enum Roles { - PortRole = Qt::UserRole + 1, - CipherRole, - SiteRole - }; - - explicit CloakConfigModel(QObject *parent = nullptr); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - -public slots: - void updateModel(const QJsonObject &config); - QJsonObject getConfig(); - -protected: - QHash roleNames() const override; - -private: - DockerContainer m_container; - QJsonObject m_protocolConfig; - QJsonObject m_fullConfig; -}; - -#endif // CLOAKCONFIGMODEL_H diff --git a/client/ui/models/protocols/ikev2ConfigModel.cpp b/client/ui/models/protocols/ikev2ConfigModel.cpp index 05494a075..d42d3a0ff 100644 --- a/client/ui/models/protocols/ikev2ConfigModel.cpp +++ b/client/ui/models/protocols/ikev2ConfigModel.cpp @@ -1,6 +1,12 @@ #include "ikev2ConfigModel.h" -#include "protocols/protocols_defs.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/models/protocols/ikev2ProtocolConfig.h" + +using namespace amnezia; Ikev2ConfigModel::Ikev2ConfigModel(QObject *parent) : QAbstractListModel(parent) { @@ -14,13 +20,19 @@ int Ikev2ConfigModel::rowCount(const QModelIndex &parent) const bool Ikev2ConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { return false; } + QString strValue = value.toString(); + switch (role) { - case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; - case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + case Roles::PortRole: + break; + case Roles::CipherRole: + break; + default: + return false; } emit dataChanged(index, index, QList { role }); @@ -30,35 +42,32 @@ bool Ikev2ConfigModel::setData(const QModelIndex &index, const QVariant &value, QVariant Ikev2ConfigModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { - return false; + return QVariant(); } switch (role) { - case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); - case Roles::CipherRole: return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + case Roles::PortRole: + return QString(""); + case Roles::CipherRole: + return QString(""); } return QVariant(); } -void Ikev2ConfigModel::updateModel(const QJsonObject &config) +void Ikev2ConfigModel::updateModel(amnezia::DockerContainer container, const amnezia::Ikev2ProtocolConfig &protocolConfig) { beginResetModel(); - m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); - - m_fullConfig = config; - QJsonObject protocolConfig = config.value(config_key::shadowsocks).toObject(); - - m_protocolConfig.insert(config_key::cipher, protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher)); - m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)); - + m_container = container; + m_protocolConfig = protocolConfig; + m_originalProtocolConfig = m_protocolConfig; + endResetModel(); } -QJsonObject Ikev2ConfigModel::getConfig() +amnezia::Ikev2ProtocolConfig Ikev2ConfigModel::getProtocolConfig() { - m_fullConfig.insert(config_key::shadowsocks, m_protocolConfig); - return m_fullConfig; + return m_protocolConfig; } QHash Ikev2ConfigModel::roleNames() const diff --git a/client/ui/models/protocols/ikev2ConfigModel.h b/client/ui/models/protocols/ikev2ConfigModel.h index e005f6a4e..37c57dbd7 100644 --- a/client/ui/models/protocols/ikev2ConfigModel.h +++ b/client/ui/models/protocols/ikev2ConfigModel.h @@ -2,9 +2,11 @@ #define IKEV2CONFIGMODEL_H #include -#include -#include "containers/containers_defs.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/protocols/ikev2ProtocolConfig.h" class Ikev2ConfigModel : public QAbstractListModel { @@ -24,16 +26,16 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void updateModel(const QJsonObject &config); - QJsonObject getConfig(); + void updateModel(amnezia::DockerContainer container, const amnezia::Ikev2ProtocolConfig &protocolConfig); + amnezia::Ikev2ProtocolConfig getProtocolConfig(); protected: QHash roleNames() const override; private: - DockerContainer m_container; - QJsonObject m_protocolConfig; - QJsonObject m_fullConfig; + amnezia::DockerContainer m_container; + amnezia::Ikev2ProtocolConfig m_protocolConfig; + amnezia::Ikev2ProtocolConfig m_originalProtocolConfig; }; #endif // IKEV2CONFIGMODEL_H diff --git a/client/ui/models/protocols/openvpnConfigModel.cpp b/client/ui/models/protocols/openvpnConfigModel.cpp index a04c2b1ad..e94de45d3 100644 --- a/client/ui/models/protocols/openvpnConfigModel.cpp +++ b/client/ui/models/protocols/openvpnConfigModel.cpp @@ -1,6 +1,11 @@ #include "openvpnConfigModel.h" -#include "protocols/protocols_defs.h" +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" + +using namespace amnezia; OpenVpnConfigModel::OpenVpnConfigModel(QObject *parent) : QAbstractListModel(parent) { @@ -14,21 +19,32 @@ int OpenVpnConfigModel::rowCount(const QModelIndex &parent) const bool OpenVpnConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerUtils::allContainers().size()) { return false; } + QString strValue = value.toString(); + bool boolValue = value.toBool(); + switch (role) { - case Roles::SubnetAddressRole: m_protocolConfig.insert(amnezia::config_key::subnet_address, value.toString()); break; - case Roles::TransportProtoRole: m_protocolConfig.insert(config_key::transport_proto, value.toString()); break; - case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; - case Roles::AutoNegotiateEncryprionRole: m_protocolConfig.insert(config_key::ncp_disable, !value.toBool()); break; - case Roles::HashRole: m_protocolConfig.insert(config_key::hash, value.toString()); break; - case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; - case Roles::TlsAuthRole: m_protocolConfig.insert(config_key::tls_auth, value.toBool()); break; - case Roles::BlockDnsRole: m_protocolConfig.insert(config_key::block_outside_dns, value.toBool()); break; - case Roles::AdditionalClientCommandsRole: m_protocolConfig.insert(config_key::additional_client_config, value.toString()); break; - case Roles::AdditionalServerCommandsRole: m_protocolConfig.insert(config_key::additional_server_config, value.toString()); break; + case Roles::SubnetAddressRole: m_protocolConfig.serverConfig.subnetAddress = strValue; break; + case Roles::TransportProtoRole: m_protocolConfig.serverConfig.transportProto = strValue; break; + case Roles::PortRole: m_protocolConfig.serverConfig.port = strValue; break; + case Roles::AutoNegotiateEncryprionRole: m_protocolConfig.serverConfig.ncpDisable = !boolValue; break; + case Roles::HashRole: m_protocolConfig.serverConfig.hash = strValue; break; + case Roles::CipherRole: m_protocolConfig.serverConfig.cipher = strValue; break; + case Roles::TlsAuthRole: m_protocolConfig.serverConfig.tlsAuth = boolValue; break; + case Roles::BlockDnsRole: { + if (!m_protocolConfig.clientConfig.has_value()) { + m_protocolConfig.clientConfig = amnezia::OpenVpnClientConfig{}; + } + m_protocolConfig.clientConfig->blockOutsideDns = boolValue; + break; + } + case Roles::AdditionalClientCommandsRole: m_protocolConfig.serverConfig.additionalClientConfig = strValue; break; + case Roles::AdditionalServerCommandsRole: m_protocolConfig.serverConfig.additionalServerConfig = strValue; break; + default: + return false; } emit dataChanged(index, index, QList { role }); @@ -38,26 +54,25 @@ bool OpenVpnConfigModel::setData(const QModelIndex &index, const QVariant &value QVariant OpenVpnConfigModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { - return false; + return QVariant(); } switch (role) { - case Roles::SubnetAddressRole: - return m_protocolConfig.value(amnezia::config_key::subnet_address).toString(amnezia::protocols::openvpn::defaultSubnetAddress); - case Roles::TransportProtoRole: - return m_protocolConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto); - case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::openvpn::defaultPort); - case Roles::AutoNegotiateEncryprionRole: - return !m_protocolConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); - case Roles::HashRole: return m_protocolConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash); - case Roles::CipherRole: return m_protocolConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher); - case Roles::TlsAuthRole: return m_protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); - case Roles::BlockDnsRole: - return m_protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns); - case Roles::AdditionalClientCommandsRole: - return m_protocolConfig.value(config_key::additional_client_config).toString(protocols::openvpn::defaultAdditionalClientConfig); - case Roles::AdditionalServerCommandsRole: - return m_protocolConfig.value(config_key::additional_server_config).toString(protocols::openvpn::defaultAdditionalServerConfig); + case Roles::SubnetAddressRole: return m_protocolConfig.serverConfig.subnetAddress; + case Roles::TransportProtoRole: return m_protocolConfig.serverConfig.transportProto; + case Roles::PortRole: return m_protocolConfig.serverConfig.port; + case Roles::AutoNegotiateEncryprionRole: return !m_protocolConfig.serverConfig.ncpDisable; + case Roles::HashRole: return m_protocolConfig.serverConfig.hash; + case Roles::CipherRole: return m_protocolConfig.serverConfig.cipher; + case Roles::TlsAuthRole: return m_protocolConfig.serverConfig.tlsAuth; + case Roles::BlockDnsRole: { + if (m_protocolConfig.clientConfig.has_value()) { + return m_protocolConfig.clientConfig->blockOutsideDns; + } + return protocols::openvpn::defaultBlockOutsideDns; + } + case Roles::AdditionalClientCommandsRole: return m_protocolConfig.serverConfig.additionalClientConfig; + case Roles::AdditionalServerCommandsRole: return m_protocolConfig.serverConfig.additionalServerConfig; case Roles::IsPortEditable: return m_container == DockerContainer::OpenVpn ? true : false; case Roles::IsTransportProtoEditable: return m_container == DockerContainer::OpenVpn ? true : false; case Roles::HasRemoveButton: return m_container == DockerContainer::OpenVpn ? true : false; @@ -65,49 +80,64 @@ QVariant OpenVpnConfigModel::data(const QModelIndex &index, int role) const return QVariant(); } -void OpenVpnConfigModel::updateModel(const QJsonObject &config) +void OpenVpnConfigModel::updateModel(amnezia::DockerContainer container, const amnezia::OpenVpnProtocolConfig &protocolConfig) { beginResetModel(); - m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); - - m_fullConfig = config; - QJsonObject protocolConfig = config.value(config_key::openvpn).toObject(); - - m_protocolConfig.insert( - config_key::subnet_address, - protocolConfig.value(amnezia::config_key::subnet_address).toString(amnezia::protocols::openvpn::defaultSubnetAddress)); - - QString transportProto; - if (m_container == DockerContainer::OpenVpn) { - transportProto = protocolConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto); - } else { - transportProto = "tcp"; + m_container = container; + + m_protocolConfig = protocolConfig; + + applyDefaultsToServerConfig(m_protocolConfig.serverConfig); + + if (!m_protocolConfig.clientConfig.has_value()) { + m_protocolConfig.clientConfig = amnezia::OpenVpnClientConfig{}; } - - m_protocolConfig.insert(config_key::transport_proto, transportProto); - - m_protocolConfig.insert(config_key::ncp_disable, - protocolConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable)); - m_protocolConfig.insert(config_key::cipher, protocolConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher)); - m_protocolConfig.insert(config_key::hash, protocolConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash)); - m_protocolConfig.insert(config_key::block_outside_dns, - protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns)); - m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)); - m_protocolConfig.insert(config_key::tls_auth, protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth)); - m_protocolConfig.insert( - config_key::additional_client_config, - protocolConfig.value(config_key::additional_client_config).toString(protocols::openvpn::defaultAdditionalClientConfig)); - m_protocolConfig.insert( - config_key::additional_server_config, - protocolConfig.value(config_key::additional_server_config).toString(protocols::openvpn::defaultAdditionalServerConfig)); - + applyDefaultsToClientConfig(m_protocolConfig.clientConfig.value()); + + m_originalProtocolConfig = m_protocolConfig; + endResetModel(); } -QJsonObject OpenVpnConfigModel::getConfig() +void OpenVpnConfigModel::applyDefaultsToServerConfig(amnezia::OpenVpnServerConfig& config) { - m_fullConfig.insert(config_key::openvpn, m_protocolConfig); - return m_fullConfig; + if (config.subnetAddress.isEmpty()) { + config.subnetAddress = protocols::openvpn::defaultSubnetAddress; + } + if (config.port.isEmpty()) { + config.port = protocols::openvpn::defaultPort; + } + if (config.transportProto.isEmpty()) { + if (m_container == DockerContainer::OpenVpn) { + config.transportProto = protocols::openvpn::defaultTransportProto; + } else { + config.transportProto = "tcp"; + } + } + if (config.cipher.isEmpty()) { + config.cipher = protocols::openvpn::defaultCipher; + } + if (config.hash.isEmpty()) { + config.hash = protocols::openvpn::defaultHash; + } +} + +void OpenVpnConfigModel::applyDefaultsToClientConfig(amnezia::OpenVpnClientConfig& config) +{ + if (!config.blockOutsideDns && !m_protocolConfig.serverConfig.additionalClientConfig.isEmpty()) { + config.blockOutsideDns = protocols::openvpn::defaultBlockOutsideDns; + } +} + +amnezia::OpenVpnProtocolConfig OpenVpnConfigModel::getProtocolConfig() +{ + bool serverSettingsChanged = !m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig); + + if (serverSettingsChanged) { + m_protocolConfig.clearClientConfig(); + } + + return m_protocolConfig; } QHash OpenVpnConfigModel::roleNames() const diff --git a/client/ui/models/protocols/openvpnConfigModel.h b/client/ui/models/protocols/openvpnConfigModel.h index 0357700c8..fb09a59f1 100644 --- a/client/ui/models/protocols/openvpnConfigModel.h +++ b/client/ui/models/protocols/openvpnConfigModel.h @@ -2,9 +2,11 @@ #define OPENVPNCONFIGMODEL_H #include -#include -#include "containers/containers_defs.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/protocols/openVpnProtocolConfig.h" class OpenVpnConfigModel : public QAbstractListModel { @@ -37,16 +39,19 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void updateModel(const QJsonObject &config); - QJsonObject getConfig(); + void updateModel(amnezia::DockerContainer container, const amnezia::OpenVpnProtocolConfig &protocolConfig); + amnezia::OpenVpnProtocolConfig getProtocolConfig(); protected: QHash roleNames() const override; private: - DockerContainer m_container; - QJsonObject m_protocolConfig; - QJsonObject m_fullConfig; + amnezia::DockerContainer m_container; + amnezia::OpenVpnProtocolConfig m_protocolConfig; + amnezia::OpenVpnProtocolConfig m_originalProtocolConfig; + + void applyDefaultsToServerConfig(amnezia::OpenVpnServerConfig& config); + void applyDefaultsToClientConfig(amnezia::OpenVpnClientConfig& config); }; #endif // OPENVPNCONFIGMODEL_H diff --git a/client/ui/models/protocols/shadowsocksConfigModel.cpp b/client/ui/models/protocols/shadowsocksConfigModel.cpp deleted file mode 100644 index 769bef20a..000000000 --- a/client/ui/models/protocols/shadowsocksConfigModel.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "shadowsocksConfigModel.h" - -#include "protocols/protocols_defs.h" - -ShadowSocksConfigModel::ShadowSocksConfigModel(QObject *parent) : QAbstractListModel(parent) -{ -} - -int ShadowSocksConfigModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent); - return 1; -} - -bool ShadowSocksConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { - return false; - } - - switch (role) { - case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; - case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; - } - - emit dataChanged(index, index, QList { role }); - return true; -} - -QVariant ShadowSocksConfigModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { - return false; - } - - switch (role) { - case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); - case Roles::CipherRole: return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); - case Roles::IsPortEditableRole: return m_container == DockerContainer::ShadowSocks ? true : false; - case Roles::IsCipherEditableRole: return m_container == DockerContainer::ShadowSocks ? true : false; - } - - return QVariant(); -} - -void ShadowSocksConfigModel::updateModel(const QJsonObject &config) -{ - beginResetModel(); - m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); - - m_fullConfig = config; - QJsonObject protocolConfig = config.value(config_key::shadowsocks).toObject(); - - auto defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(Proto::ShadowSocks), Proto::ShadowSocks); - m_protocolConfig.insert(config_key::transport_proto, - protocolConfig.value(config_key::transport_proto).toString(defaultTransportProto)); - m_protocolConfig.insert(config_key::cipher, protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher)); - m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)); - - endResetModel(); -} - -QJsonObject ShadowSocksConfigModel::getConfig() -{ - m_fullConfig.insert(config_key::shadowsocks, m_protocolConfig); - return m_fullConfig; -} - -QHash ShadowSocksConfigModel::roleNames() const -{ - QHash roles; - - roles[PortRole] = "port"; - roles[CipherRole] = "cipher"; - roles[IsPortEditableRole] = "isPortEditable"; - roles[IsCipherEditableRole] = "isCipherEditable"; - - return roles; -} diff --git a/client/ui/models/protocols/shadowsocksConfigModel.h b/client/ui/models/protocols/shadowsocksConfigModel.h deleted file mode 100644 index 566df7683..000000000 --- a/client/ui/models/protocols/shadowsocksConfigModel.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef SHADOWSOCKSCONFIGMODEL_H -#define SHADOWSOCKSCONFIGMODEL_H - -#include -#include - -#include "containers/containers_defs.h" - -class ShadowSocksConfigModel : public QAbstractListModel -{ - Q_OBJECT - -public: - enum Roles { - PortRole = Qt::UserRole + 1, - CipherRole, - IsPortEditableRole, - IsCipherEditableRole - }; - - explicit ShadowSocksConfigModel(QObject *parent = nullptr); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - -public slots: - void updateModel(const QJsonObject &config); - QJsonObject getConfig(); - -protected: - QHash roleNames() const override; - -private: - DockerContainer m_container; - QJsonObject m_protocolConfig; - QJsonObject m_fullConfig; -}; - -#endif // SHADOWSOCKSCONFIGMODEL_H diff --git a/client/ui/models/protocols/wireguardConfigModel.cpp b/client/ui/models/protocols/wireguardConfigModel.cpp index 1c8e13416..245890e59 100644 --- a/client/ui/models/protocols/wireguardConfigModel.cpp +++ b/client/ui/models/protocols/wireguardConfigModel.cpp @@ -1,8 +1,12 @@ #include "wireguardConfigModel.h" -#include +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" -#include "protocols/protocols_defs.h" +using namespace amnezia; +using namespace ProtocolUtils; WireGuardConfigModel::WireGuardConfigModel(QObject *parent) : QAbstractListModel(parent) { @@ -16,14 +20,24 @@ int WireGuardConfigModel::rowCount(const QModelIndex &parent) const bool WireGuardConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerUtils::allContainers().size()) { return false; } + QString strValue = value.toString(); + switch (role) { - case Roles::SubnetAddressRole: m_serverProtocolConfig.insert(config_key::subnet_address, value.toString()); break; - case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break; - case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break; + case Roles::SubnetAddressRole: m_protocolConfig.serverConfig.subnetAddress = strValue; break; + case Roles::PortRole: m_protocolConfig.serverConfig.port = strValue; break; + case Roles::ClientMtuRole: { + if (!m_protocolConfig.clientConfig.has_value()) { + m_protocolConfig.clientConfig = amnezia::WireGuardClientConfig{}; + } + m_protocolConfig.clientConfig->mtu = strValue; + break; + } + default: + return false; } emit dataChanged(index, index, QList { role }); @@ -33,66 +47,77 @@ bool WireGuardConfigModel::setData(const QModelIndex &index, const QVariant &val QVariant WireGuardConfigModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { - return false; + return QVariant(); } switch (role) { - case Roles::SubnetAddressRole: return m_serverProtocolConfig.value(config_key::subnet_address).toString(); - case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString(); - case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu); + case Roles::SubnetAddressRole: return m_protocolConfig.serverConfig.subnetAddress; + case Roles::PortRole: return m_protocolConfig.serverConfig.port; + case Roles::ClientMtuRole: { + if (m_protocolConfig.clientConfig.has_value()) { + return m_protocolConfig.clientConfig->mtu; + } + return QString(protocols::wireguard::defaultMtu); + } } return QVariant(); } -void WireGuardConfigModel::updateModel(const QJsonObject &config) +void WireGuardConfigModel::updateModel(amnezia::DockerContainer container, const amnezia::WireGuardProtocolConfig &protocolConfig) { beginResetModel(); - m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); - - m_fullConfig = config; - QJsonObject serverProtocolConfig = config.value(config_key::wireguard).toObject(); - - auto defaultTransportProto = - ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(Proto::WireGuard), Proto::WireGuard); - m_serverProtocolConfig.insert(config_key::transport_proto, - serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto)); - m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config); - m_serverProtocolConfig[config_key::subnet_address] = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); - m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort); - - auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString(); - QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - m_clientProtocolConfig[config_key::mtu] = clientProtocolConfig[config_key::mtu].toString(protocols::wireguard::defaultMtu); - + m_container = container; + + m_protocolConfig = protocolConfig; + + applyDefaultsToServerConfig(m_protocolConfig.serverConfig); + + if (!m_protocolConfig.clientConfig.has_value()) { + m_protocolConfig.clientConfig = amnezia::WireGuardClientConfig{}; + } + applyDefaultsToClientConfig(m_protocolConfig.clientConfig.value()); + + m_originalProtocolConfig = m_protocolConfig; + endResetModel(); } -QJsonObject WireGuardConfigModel::getConfig() +void WireGuardConfigModel::applyDefaultsToServerConfig(amnezia::WireGuardServerConfig& config) { - const WgConfig oldConfig(m_fullConfig.value(config_key::wireguard).toObject()); - const WgConfig newConfig(m_serverProtocolConfig); - - if (!oldConfig.hasEqualServerSettings(newConfig)) { - m_serverProtocolConfig.remove(config_key::last_config); - } else { - auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString(); - QJsonObject jsonConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - jsonConfig[config_key::mtu] = m_clientProtocolConfig[config_key::mtu]; - - m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson()); + if (config.subnetAddress.isEmpty()) { + config.subnetAddress = protocols::wireguard::defaultSubnetAddress; } + if (config.port.isEmpty()) { + config.port = protocols::wireguard::defaultPort; + } + if (config.transportProto.isEmpty()) { + config.transportProto = ProtocolUtils::transportProtoToString( + ProtocolUtils::defaultTransportProto(amnezia::Proto::WireGuard), amnezia::Proto::WireGuard); + } +} - m_fullConfig.insert(config_key::wireguard, m_serverProtocolConfig); - return m_fullConfig; +void WireGuardConfigModel::applyDefaultsToClientConfig(amnezia::WireGuardClientConfig& config) +{ + if (config.mtu.isEmpty()) { + config.mtu = protocols::wireguard::defaultMtu; + } +} + +amnezia::WireGuardProtocolConfig WireGuardConfigModel::getProtocolConfig() +{ + bool serverSettingsChanged = !m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig); + + if (serverSettingsChanged) { + m_protocolConfig.clearClientConfig(); + } + + return m_protocolConfig; } bool WireGuardConfigModel::isServerSettingsEqual() { - const WgConfig oldConfig(m_fullConfig.value(config_key::wireguard).toObject()); - const WgConfig newConfig(m_serverProtocolConfig); - - return oldConfig.hasEqualServerSettings(newConfig); + return m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig); } QHash WireGuardConfigModel::roleNames() const @@ -106,28 +131,3 @@ QHash WireGuardConfigModel::roleNames() const return roles; } -WgConfig::WgConfig(const QJsonObject &serverProtocolConfig) -{ - auto lastConfig = serverProtocolConfig.value(config_key::last_config).toString(); - QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - clientMtu = clientProtocolConfig[config_key::mtu].toString(protocols::wireguard::defaultMtu); - - subnetAddress = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); - port = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort); -} - -bool WgConfig::hasEqualServerSettings(const WgConfig &other) const -{ - if (subnetAddress != other.subnetAddress || port != other.port) { - return false; - } - return true; -} - -bool WgConfig::hasEqualClientSettings(const WgConfig &other) const -{ - if (clientMtu != other.clientMtu) { - return false; - } - return true; -} diff --git a/client/ui/models/protocols/wireguardConfigModel.h b/client/ui/models/protocols/wireguardConfigModel.h index b1ce2d610..8ae131237 100644 --- a/client/ui/models/protocols/wireguardConfigModel.h +++ b/client/ui/models/protocols/wireguardConfigModel.h @@ -2,22 +2,11 @@ #define WIREGUARDCONFIGMODEL_H #include -#include -#include "containers/containers_defs.h" - -struct WgConfig -{ - WgConfig(const QJsonObject &jsonConfig); - - QString subnetAddress; - QString port; - QString clientMtu; - - bool hasEqualServerSettings(const WgConfig &other) const; - bool hasEqualClientSettings(const WgConfig &other) const; - -}; +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/protocols/wireGuardProtocolConfig.h" class WireGuardConfigModel : public QAbstractListModel { @@ -38,8 +27,8 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void updateModel(const QJsonObject &config); - QJsonObject getConfig(); + void updateModel(amnezia::DockerContainer container, const amnezia::WireGuardProtocolConfig &protocolConfig); + amnezia::WireGuardProtocolConfig getProtocolConfig(); bool isServerSettingsEqual(); @@ -47,10 +36,12 @@ protected: QHash roleNames() const override; private: - DockerContainer m_container; - QJsonObject m_serverProtocolConfig; - QJsonObject m_clientProtocolConfig; - QJsonObject m_fullConfig; + amnezia::DockerContainer m_container; + amnezia::WireGuardProtocolConfig m_protocolConfig; + amnezia::WireGuardProtocolConfig m_originalProtocolConfig; + + void applyDefaultsToServerConfig(amnezia::WireGuardServerConfig& config); + void applyDefaultsToClientConfig(amnezia::WireGuardClientConfig& config); }; #endif // WIREGUARDCONFIGMODEL_H diff --git a/client/ui/models/protocols/xrayConfigModel.cpp b/client/ui/models/protocols/xrayConfigModel.cpp index 3917b5442..462982073 100644 --- a/client/ui/models/protocols/xrayConfigModel.cpp +++ b/client/ui/models/protocols/xrayConfigModel.cpp @@ -1,6 +1,12 @@ #include "xrayConfigModel.h" -#include "protocols/protocols_defs.h" +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" + +using namespace amnezia; +using namespace ProtocolUtils; XrayConfigModel::XrayConfigModel(QObject *parent) : QAbstractListModel(parent) { @@ -14,13 +20,17 @@ int XrayConfigModel::rowCount(const QModelIndex &parent) const bool XrayConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerUtils::allContainers().size()) { return false; } + QString strValue = value.toString(); + switch (role) { - case Roles::SiteRole: m_protocolConfig.insert(config_key::site, value.toString()); break; - case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::SiteRole: m_protocolConfig.serverConfig.site = strValue; break; + case Roles::PortRole: m_protocolConfig.serverConfig.port = strValue; break; + default: + return false; } emit dataChanged(index, index, QList { role }); @@ -30,38 +40,54 @@ bool XrayConfigModel::setData(const QModelIndex &index, const QVariant &value, i QVariant XrayConfigModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { - return false; + return QVariant(); } switch (role) { - case Roles::SiteRole: return m_protocolConfig.value(config_key::site).toString(protocols::xray::defaultSite); - case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::xray::defaultPort); + case Roles::SiteRole: return m_protocolConfig.serverConfig.site; + case Roles::PortRole: return m_protocolConfig.serverConfig.port; } return QVariant(); } -void XrayConfigModel::updateModel(const QJsonObject &config) +void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amnezia::XrayProtocolConfig &protocolConfig) { beginResetModel(); - m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); - - m_fullConfig = config; - QJsonObject protocolConfig = config.value(config_key::xray).toObject(); - - auto defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(Proto::Xray), Proto::Xray); - m_protocolConfig.insert(config_key::transport_proto, - protocolConfig.value(config_key::transport_proto).toString(defaultTransportProto)); - m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString(protocols::xray::defaultPort)); - m_protocolConfig.insert(config_key::site, protocolConfig.value(config_key::site).toString(protocols::xray::defaultSite)); - + m_container = container; + + m_protocolConfig = protocolConfig; + + applyDefaultsToServerConfig(m_protocolConfig.serverConfig); + + m_originalProtocolConfig = m_protocolConfig; + endResetModel(); } -QJsonObject XrayConfigModel::getConfig() +void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig& config) { - m_fullConfig.insert(config_key::xray, m_protocolConfig); - return m_fullConfig; + if (config.port.isEmpty()) { + config.port = protocols::xray::defaultPort; + } + if (config.transportProto.isEmpty()) { + config.transportProto = ProtocolUtils::transportProtoToString( + ProtocolUtils::defaultTransportProto(amnezia::Proto::Xray), amnezia::Proto::Xray); + } + if (config.site.isEmpty()) { + config.site = protocols::xray::defaultSite; + } +} + +amnezia::XrayProtocolConfig XrayConfigModel::getProtocolConfig() +{ + bool serverSettingsChanged = !m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig); + + if (serverSettingsChanged) { + m_protocolConfig.clearClientConfig(); + } + + return m_protocolConfig; } QHash XrayConfigModel::roleNames() const diff --git a/client/ui/models/protocols/xrayConfigModel.h b/client/ui/models/protocols/xrayConfigModel.h index 41aac5894..5549cf446 100644 --- a/client/ui/models/protocols/xrayConfigModel.h +++ b/client/ui/models/protocols/xrayConfigModel.h @@ -2,9 +2,11 @@ #define XRAYCONFIGMODEL_H #include -#include -#include "containers/containers_defs.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/protocols/xrayProtocolConfig.h" class XrayConfigModel : public QAbstractListModel { @@ -24,16 +26,18 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void updateModel(const QJsonObject &config); - QJsonObject getConfig(); + void updateModel(amnezia::DockerContainer container, const amnezia::XrayProtocolConfig &protocolConfig); + amnezia::XrayProtocolConfig getProtocolConfig(); protected: QHash roleNames() const override; private: - DockerContainer m_container; - QJsonObject m_protocolConfig; - QJsonObject m_fullConfig; + amnezia::DockerContainer m_container; + amnezia::XrayProtocolConfig m_protocolConfig; + amnezia::XrayProtocolConfig m_originalProtocolConfig; + + void applyDefaultsToServerConfig(amnezia::XrayServerConfig& config); }; #endif // XRAYCONFIGMODEL_H diff --git a/client/ui/models/protocolsModel.cpp b/client/ui/models/protocolsModel.cpp new file mode 100644 index 000000000..773a92344 --- /dev/null +++ b/client/ui/models/protocolsModel.cpp @@ -0,0 +1,138 @@ +#include "protocolsModel.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/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/models/protocols/awgProtocolConfig.h" +#include "core/models/protocols/wireGuardProtocolConfig.h" +#include "core/models/protocols/openVpnProtocolConfig.h" +#include "core/models/protocols/xrayProtocolConfig.h" + +using namespace ProtocolUtils; + +ProtocolsModel::ProtocolsModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +int ProtocolsModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return m_containerConfig.container != DockerContainer::None ? 1 : 0; +} + +QHash ProtocolsModel::roleNames() const +{ + QHash roles; + + roles[ProtocolNameRole] = "protocolName"; + roles[ServerProtocolPageRole] = "serverProtocolPage"; + roles[ClientProtocolPageRole] = "clientProtocolPage"; + roles[ProtocolIndexRole] = "protocolIndex"; + roles[ProtocolStringRole] = "protocolString"; + roles[RawConfigRole] = "rawConfig"; + roles[IsClientProtocolExistsRole] = "isClientProtocolExists"; + roles[IsWireGuardRole] = "isWireGuard"; + roles[IsAwgRole] = "isAwg"; + roles[IsOpenVpnRole] = "isOpenVpn"; + roles[IsXrayRole] = "isXray"; + roles[IsSftpRole] = "isSftp"; + roles[IsIpsecRole] = "isIpsec"; + roles[IsSocks5ProxyRole] = "isSocks5Proxy"; + + return roles; +} + +QVariant ProtocolsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return QVariant(); + } + + Proto proto = getProtocolType(); + + switch (role) { + case ProtocolNameRole: { + return ProtocolUtils::protocolHumanNames().value(proto); + } + case ServerProtocolPageRole: + return static_cast(serverProtocolPage(proto)); + case ClientProtocolPageRole: + return static_cast(clientProtocolPage(proto)); + case ProtocolIndexRole: return static_cast(proto); + case ProtocolStringRole: return ProtocolUtils::protoToString(proto); + case IsWireGuardRole: return proto == Proto::WireGuard; + case IsAwgRole: return proto == Proto::Awg; + case IsOpenVpnRole: return proto == Proto::OpenVpn; + case IsXrayRole: return proto == Proto::Xray; + case IsSftpRole: return proto == Proto::Sftp; + case IsIpsecRole: return proto == Proto::Ikev2; + case IsSocks5ProxyRole: return proto == Proto::Socks5Proxy; + case RawConfigRole: + return getRawConfig(); + case IsClientProtocolExistsRole: + return isClientProtocolExists(); + } + + return QVariant(); +} + +void ProtocolsModel::updateModel(const amnezia::ContainerConfig &containerConfig) +{ + beginResetModel(); + m_containerConfig = containerConfig; + endResetModel(); +} + +Proto ProtocolsModel::getProtocolType() const +{ + return m_containerConfig.getProtocolType(); +} + +QString ProtocolsModel::getRawConfig() const +{ + QString configString = m_containerConfig.protocolConfig.nativeConfig(); + + QStringList lines = configString.replace("\r", "").split("\n"); + QString rawConfig; + for (const QString &l : lines) { + rawConfig.append(l + "\n"); + } + return rawConfig; +} + +bool ProtocolsModel::isClientProtocolExists() const +{ + return m_containerConfig.protocolConfig.hasClientConfig() && + !m_containerConfig.protocolConfig.nativeConfig().isEmpty(); +} + +PageLoader::PageEnum ProtocolsModel::serverProtocolPage(Proto protocol) const +{ + switch (protocol) { + case Proto::OpenVpn: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardSettings; + case Proto::Awg: return PageLoader::PageEnum::PageProtocolAwgSettings; + case Proto::Ikev2: return PageLoader::PageEnum::PageProtocolIKev2Settings; + case Proto::Xray: return PageLoader::PageEnum::PageProtocolXraySettings; + + // non-vpn + case Proto::TorWebSite: return PageLoader::PageEnum::PageServiceTorWebsiteSettings; + case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings; + case Proto::Sftp: return PageLoader::PageEnum::PageServiceSftpSettings; + case Proto::Socks5Proxy: return PageLoader::PageEnum::PageServiceSocksProxySettings; + default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + } +} + +PageLoader::PageEnum ProtocolsModel::clientProtocolPage(Proto protocol) const +{ + switch (protocol) { + case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardClientSettings; + case Proto::Awg: return PageLoader::PageEnum::PageProtocolAwgClientSettings; + default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + } +} diff --git a/client/ui/models/protocols_model.h b/client/ui/models/protocolsModel.h similarity index 54% rename from client/ui/models/protocols_model.h rename to client/ui/models/protocolsModel.h index 5c52ee86a..a10ab8731 100644 --- a/client/ui/models/protocols_model.h +++ b/client/ui/models/protocolsModel.h @@ -2,10 +2,9 @@ #define PROTOCOLS_MODEL_H #include -#include -#include "../controllers/pageController.h" -#include "settings.h" +#include "../controllers/qml/pageController.h" +#include "core/models/containerConfig.h" class ProtocolsModel : public QAbstractListModel { @@ -16,20 +15,27 @@ public: ServerProtocolPageRole, ClientProtocolPageRole, ProtocolIndexRole, + ProtocolStringRole, RawConfigRole, - IsClientProtocolExistsRole + IsClientProtocolExistsRole, + // Protocol type check roles + IsWireGuardRole, + IsAwgRole, + IsOpenVpnRole, + IsXrayRole, + IsSftpRole, + IsIpsecRole, + IsSocks5ProxyRole }; - ProtocolsModel(std::shared_ptr settings, QObject *parent = nullptr); + explicit ProtocolsModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void updateModel(const QJsonObject &content); - - QJsonObject getConfig(); + void updateModel(const amnezia::ContainerConfig &containerConfig); protected: QHash roleNames() const override; @@ -37,11 +43,11 @@ protected: private: PageLoader::PageEnum serverProtocolPage(Proto protocol) const; PageLoader::PageEnum clientProtocolPage(Proto protocol) const; + Proto getProtocolType() const; + QString getRawConfig() const; + bool isClientProtocolExists() const; - std::shared_ptr m_settings; - - DockerContainer m_container; - QJsonObject m_content; + amnezia::ContainerConfig m_containerConfig; }; #endif // PROTOCOLS_MODEL_H diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp deleted file mode 100644 index 8ddbaa81e..000000000 --- a/client/ui/models/protocols_model.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "protocols_model.h" - -ProtocolsModel::ProtocolsModel(std::shared_ptr settings, QObject *parent) - : m_settings(settings), QAbstractListModel(parent) -{ -} - -int ProtocolsModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent); - return m_content.size(); -} - -QHash ProtocolsModel::roleNames() const -{ - QHash roles; - - roles[ProtocolNameRole] = "protocolName"; - roles[ServerProtocolPageRole] = "serverProtocolPage"; - roles[ClientProtocolPageRole] = "clientProtocolPage"; - roles[ProtocolIndexRole] = "protocolIndex"; - roles[RawConfigRole] = "rawConfig"; - roles[IsClientProtocolExistsRole] = "isClientProtocolExists"; - - return roles; -} - -QVariant ProtocolsModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= m_content.size()) { - return QVariant(); - } - - switch (role) { - case ProtocolNameRole: { - amnezia::Proto proto = ProtocolProps::protoFromString(m_content.keys().at(index.row())); - return ProtocolProps::protocolHumanNames().value(proto); - } - case ServerProtocolPageRole: - return static_cast(serverProtocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row())))); - case ClientProtocolPageRole: - return static_cast(clientProtocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row())))); - case ProtocolIndexRole: return ProtocolProps::protoFromString(m_content.keys().at(index.row())); - case RawConfigRole: { - auto protocolConfig = m_content.value(ContainerProps::containerTypeToProtocolString(m_container)).toObject(); - auto lastConfigJsonDoc = - QJsonDocument::fromJson(protocolConfig.value(config_key::last_config).toString().toUtf8()); - auto lastConfigJson = lastConfigJsonDoc.object(); - - QString rawConfig; - QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); - for (const QString &l : lines) { - rawConfig.append(l + "\n"); - } - return rawConfig; - } - case IsClientProtocolExistsRole: { - QString protocolKey = ContainerProps::containerTypeToProtocolString(m_container); - auto protocolConfig = m_content.value(protocolKey).toObject(); - auto lastConfigJsonDoc = - QJsonDocument::fromJson(protocolConfig.value(config_key::last_config).toString().toUtf8()); - auto lastConfigJson = lastConfigJsonDoc.object(); - - auto configString = lastConfigJson.value(config_key::config).toString(); - return !configString.isEmpty(); - } - } - - return QVariant(); -} - -void ProtocolsModel::updateModel(const QJsonObject &content) -{ - m_container = ContainerProps::containerFromString(content.value(config_key::container).toString()); - - m_content = content; - m_content.remove(config_key::container); -} - -QJsonObject ProtocolsModel::getConfig() -{ - QJsonObject config = m_content; - config.insert(config_key::container, ContainerProps::containerToString(m_container)); - return config; -} - -PageLoader::PageEnum ProtocolsModel::serverProtocolPage(Proto protocol) const -{ - switch (protocol) { - case Proto::OpenVpn: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; - case Proto::Cloak: return PageLoader::PageEnum::PageProtocolCloakSettings; - case Proto::ShadowSocks: return PageLoader::PageEnum::PageProtocolShadowSocksSettings; - case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardSettings; - case Proto::Awg: return PageLoader::PageEnum::PageProtocolAwgSettings; - case Proto::Ikev2: return PageLoader::PageEnum::PageProtocolIKev2Settings; - case Proto::L2tp: return PageLoader::PageEnum::PageProtocolIKev2Settings; - case Proto::Xray: return PageLoader::PageEnum::PageProtocolXraySettings; - - // non-vpn - case Proto::TorWebSite: return PageLoader::PageEnum::PageServiceTorWebsiteSettings; - case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings; - case Proto::Sftp: return PageLoader::PageEnum::PageServiceSftpSettings; - case Proto::Socks5Proxy: return PageLoader::PageEnum::PageServiceSocksProxySettings; - default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; - } -} - -PageLoader::PageEnum ProtocolsModel::clientProtocolPage(Proto protocol) const -{ - switch (protocol) { - case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardClientSettings; - case Proto::Awg: return PageLoader::PageEnum::PageProtocolAwgClientSettings; - default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; - } -} diff --git a/client/ui/models/serversModel.cpp b/client/ui/models/serversModel.cpp new file mode 100644 index 000000000..131d241f8 --- /dev/null +++ b/client/ui/models/serversModel.cpp @@ -0,0 +1,425 @@ +#include "serversModel.h" + +#include +#include +#include + +#include "core/models/serverConfig.h" +#include "core/utils/api/apiEnums.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/apiConstants.h" +#include "core/utils/selfhosted/sshSession.h" +#include "core/utils/networkUtilities.h" + +#if defined(Q_OS_IOS) || defined(MACOS_NE) + #include +#endif + +#include "core/utils/api/apiUtils.h" + +using namespace amnezia; + +namespace +{ + namespace configKey + { + constexpr char apiConfig[] = "api_config"; + constexpr char serviceInfo[] = "service_info"; + constexpr char availableCountries[] = "available_countries"; + constexpr char serverCountryCode[] = "server_country_code"; + constexpr char serverCountryName[] = "server_country_name"; + constexpr char userCountryCode[] = "user_country_code"; + constexpr char serviceType[] = "service_type"; + constexpr char serviceProtocol[] = "service_protocol"; + + constexpr char publicKeyInfo[] = "public_key"; + constexpr char expiresAt[] = "expires_at"; + } + + QString normalizeVpnKey(const QString &vpnKey) + { + QString normalized = vpnKey.trimmed(); + if (normalized.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) { + normalized = normalized.mid(QStringLiteral("vpn://").size()); + } + return normalized; + } +} + +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_servers.size()) { + return; + } + auto defaultContainer = m_servers.at(serverIndex).defaultContainer(); + emit ServersModel::defaultServerDefaultContainerChanged(defaultContainer); + emit ServersModel::defaultServerNameChanged(); + }); + + connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged); +} + +int ServersModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return static_cast(m_servers.size()); +} + +QVariant ServersModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_servers.size())) { + return QVariant(); + } + + const ServerConfig &server = m_servers.at(index.row()); + const int configVersion = server.configVersion(); + + switch (role) { + case NameRole: { + if (configVersion) { + if (server.isApiV1()) { + return server.as()->name; + } else if (server.isApiV2()) { + return server.as()->name; + } + } + QString name = server.description(); + if (name.isEmpty()) { + return server.hostName(); + } + return name; + } + case ServerDescriptionRole: { + auto description = getServerDescription(server, index.row()); + return configVersion ? description : description + server.hostName(); + } + case HostNameRole: return server.hostName(); + case CredentialsRole: return QVariant::fromValue(serverCredentials(index.row())); + case CredentialsLoginRole: return serverCredentials(index.row()).userName; + case IsDefaultRole: return index.row() == m_defaultServerIndex; + case IsCurrentlyProcessedRole: return index.row() == m_processedServerIndex; + case HasWriteAccessRole: { + auto credentials = serverCredentials(index.row()); + return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); + } + case ContainsAmneziaDnsRole: { + QString primaryDns = server.dns1(); + return primaryDns == protocols::dns::amneziaDnsIp; + } + case DefaultContainerRole: { + return server.defaultContainer(); + } + case HasInstalledContainers: { + return serverHasInstalledContainers(index.row()); + } + case IsServerFromTelegramApiRole: { + return configVersion == apiDefs::ConfigSource::Telegram; + } + case IsServerFromGatewayApiRole: { + return configVersion == apiDefs::ConfigSource::AmneziaGateway; + } + case ApiConfigRole: { + return QVariant(); + } + case IsCountrySelectionAvailableRole: { + if (server.isApiV2()) { + return !server.as()->apiConfig.availableCountries.isEmpty(); + } + return false; + } + case ApiAvailableCountriesRole: { + if (server.isApiV2()) { + return server.as()->apiConfig.availableCountries; + } + return QJsonArray(); + } + case ApiServerCountryCodeRole: { + if (server.isApiV2()) { + return server.as()->apiConfig.serverCountryCode; + } + return QString(); + } + case HasAmneziaDns: { + QString primaryDns = server.dns1(); + return primaryDns == protocols::dns::amneziaDnsIp; + } + case IsAdVisibleRole: { + if (server.isApiV2()) { + return server.as()->apiConfig.serviceInfo.isAdVisible; + } + return false; + } + case AdHeaderRole: { + if (server.isApiV2()) { + return server.as()->apiConfig.serviceInfo.adHeader; + } + return QString(); + } + case AdDescriptionRole: { + if (server.isApiV2()) { + return server.as()->apiConfig.serviceInfo.adDescription; + } + return QString(); + } + case AdEndpointRole: { + if (server.isApiV2()) { + return server.as()->apiConfig.serviceInfo.adEndpoint; + } + return QString(); + } + case IsRenewalAvailableRole: { + if (server.isApiV2()) { + return server.as()->apiConfig.serviceInfo.isRenewalAvailable; + } + return false; + } + case IsSubscriptionExpiredRole: { + if (!server.isApiV2()) { + return false; + } + + const ApiConfig &apiConfig = server.as()->apiConfig; + if (apiConfig.isInAppPurchase) { + return false; + } + if (apiConfig.subscriptionExpiredByServer) { + return true; + } + if (apiConfig.subscription.endDate.isEmpty()) { + return false; + } + return apiUtils::isSubscriptionExpired(apiConfig.subscription.endDate); + } + case IsSubscriptionExpiringSoonRole: { + if (!server.isApiV2()) { + return false; + } + + const ApiConfig &apiConfig = server.as()->apiConfig; + if (apiConfig.isInAppPurchase) { + return false; + } + if (apiConfig.subscription.endDate.isEmpty()) { + return false; + } + return apiUtils::isSubscriptionExpiringSoon(apiConfig.subscription.endDate); + } + } + + return QVariant(); +} + +QVariant ServersModel::data(const int index, int role) const +{ + QModelIndex modelIndex = this->index(index); + return data(modelIndex, role); +} + +void ServersModel::updateModel(const QVector &servers, int defaultServerIndex, bool isAmneziaDnsEnabled) +{ + beginResetModel(); + m_servers = servers; + m_defaultServerIndex = defaultServerIndex; + m_isAmneziaDnsEnabled = isAmneziaDnsEnabled; + endResetModel(); + emit defaultServerIndexChanged(m_defaultServerIndex); + emit processedServerChanged(); +} + +const int ServersModel::getDefaultServerIndex() +{ + return m_defaultServerIndex; +} + +QString ServersModel::getServerDescription(const ServerConfig &server, const int index) const +{ + const int configVersion = server.configVersion(); + QString description; + + if (server.isApiV2()) { + const ApiV2ServerConfig *apiV2 = server.as(); + if (apiV2 && !apiV2->apiConfig.serverCountryCode.isEmpty()) { + return apiV2->apiConfig.serverCountryName; + } + return apiV2 ? apiV2->description : server.description(); + } else if (server.isApiV1()) { + const ApiV1ServerConfig *apiV1 = server.as(); + return apiV1 ? apiV1->description : server.description(); + } else if (data(index, HasWriteAccessRole).toBool()) { + QMap containers = server.containers(); + bool isDnsInstalled = containers.contains(DockerContainer::Dns); + if (m_isAmneziaDnsEnabled && isDnsInstalled) { + description += "Amnezia DNS | "; + } + } else { + if (data(index, HasAmneziaDns).toBool()) { + description += "Amnezia DNS | "; + } + } + return description; +} + +const int ServersModel::getServersCount() +{ + return m_servers.size(); +} + +bool ServersModel::hasServerWithWriteAccess() +{ + for (size_t i = 0; i < getServersCount(); i++) { + if (qvariant_cast(data(i, HasWriteAccessRole))) { + return true; + } + } + return false; +} + +void ServersModel::setProcessedServerIndex(const int index) +{ + if (m_processedServerIndex != index) { + m_processedServerIndex = index; + emit processedServerIndexChanged(m_processedServerIndex); + } +} + +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(data(m_processedServerIndex, HasWriteAccessRole)); +} + +bool ServersModel::isDefaultServerHasWriteAccess() +{ + return qvariant_cast(data(m_defaultServerIndex, HasWriteAccessRole)); +} + +QHash ServersModel::roleNames() const +{ + QHash roles; + + roles[NameRole] = "name"; + roles[ServerDescriptionRole] = "serverDescription"; + roles[CollapsedServerDescriptionRole] = "collapsedServerDescription"; + roles[ExpandedServerDescriptionRole] = "expandedServerDescription"; + + roles[HostNameRole] = "hostName"; + + 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"; + + return roles; +} + +ServerCredentials ServersModel::serverCredentials(int index) const +{ + if (index < 0 || index >= m_servers.size()) { + return ServerCredentials(); + } + const ServerConfig &server = m_servers.at(index); + + if (server.isSelfHosted()) { + const SelfHostedServerConfig *selfHosted = server.as(); + if (selfHosted) { + ServerCredentials credentials; + credentials.hostName = selfHosted->hostName; + credentials.userName = selfHosted->userName.value_or(""); + credentials.secretData = selfHosted->password.value_or(""); + credentials.port = selfHosted->port.value_or(22); + return credentials; + } + } + + return ServerCredentials(); +} + +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 +{ + const ServerConfig &server = m_servers.at(serverIndex); + QMap containers = server.containers(); + + for (auto it = containers.begin(); it != containers.end(); ++it) { + DockerContainer container = it.key(); + if (ContainerUtils::containerService(container) == ServiceType::Vpn) { + return true; + } + if (container == DockerContainer::SSXray) { + return true; + } + } + return false; +} + diff --git a/client/ui/models/serversModel.h b/client/ui/models/serversModel.h new file mode 100644 index 000000000..9bff2eadb --- /dev/null +++ b/client/ui/models/serversModel.h @@ -0,0 +1,112 @@ +#ifndef SERVERSMODEL_H +#define SERVERSMODEL_H + +#include +#include + +#include "core/utils/selfhosted/sshSession.h" +#include "core/models/serverConfig.h" + +class ServersModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Roles { + NameRole = Qt::UserRole + 1, + ServerDescriptionRole, + CollapsedServerDescriptionRole, + ExpandedServerDescriptionRole, + HostNameRole, + + 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); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + 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 &servers, int defaultServerIndex, bool isAmneziaDnsEnabled = false); + +protected: + QHash roleNames() const override; + +signals: + void processedServerIndexChanged(const int index); + // emitted when the processed server index or processed server data is changed + 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; + + QString getServerDescription(const ServerConfig &server, const int index) const; + + bool serverHasInstalledContainers(const int serverIndex) const; + + QVector m_servers; + + int m_defaultServerIndex; + int m_processedServerIndex; + + bool m_isAmneziaDnsEnabled = false; +}; + +#endif // SERVERSMODEL_H diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp deleted file mode 100644 index 510c16170..000000000 --- a/client/ui/models/servers_model.cpp +++ /dev/null @@ -1,1012 +0,0 @@ -#include "servers_model.h" - -#include "core/api/apiDefs.h" -#include "core/controllers/serverController.h" -#include "core/networkUtilities.h" - -#if defined(Q_OS_IOS) || defined(MACOS_NE) - #include -#endif - -#include "core/api/apiUtils.h" - -namespace -{ - namespace configKey - { - constexpr char apiConfig[] = "api_config"; - constexpr char serviceInfo[] = "service_info"; - constexpr char availableCountries[] = "available_countries"; - constexpr char serverCountryCode[] = "server_country_code"; - constexpr char serverCountryName[] = "server_country_name"; - constexpr char userCountryCode[] = "user_country_code"; - constexpr char serviceType[] = "service_type"; - constexpr char serviceProtocol[] = "service_protocol"; - - constexpr char publicKeyInfo[] = "public_key"; - constexpr char expiresAt[] = "expires_at"; - } - - QString normalizeVpnKey(const QString &vpnKey) - { - QString normalized = vpnKey.trimmed(); - if (normalized.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) { - normalized = normalized.mid(QStringLiteral("vpn://").size()); - } - return normalized; - } -} - -ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) -{ - m_isAmneziaDnsEnabled = m_settings->useAmneziaDns(); - - connect(this, &ServersModel::defaultServerIndexChanged, this, &ServersModel::defaultServerNameChanged); - - connect(this, &ServersModel::defaultServerIndexChanged, this, [this](const int serverIndex) { - auto defaultContainer = - ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString()); - emit ServersModel::defaultServerDefaultContainerChanged(defaultContainer); - emit ServersModel::defaultServerNameChanged(); - updateDefaultServerContainersModel(); - }); - - connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged); - connect(this, &ServersModel::dataChanged, this, &ServersModel::processedServerChanged); - - connect(this, &QAbstractItemModel::modelReset, this, &ServersModel::recomputeGatewayStacks); -} - -int ServersModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent); - return static_cast(m_servers.size()); -} - -bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_servers.size())) { - return false; - } - - QJsonObject server = m_servers.at(index.row()).toObject(); - const auto configVersion = server.value(config_key::configVersion).toInt(); - - switch (role) { - case NameRole: { - if (configVersion) { - server.insert(config_key::name, value.toString()); - } else { - server.insert(config_key::description, value.toString()); - } - server.insert(config_key::nameOverriddenByUser, true); - m_settings->editServer(index.row(), server); - m_servers.replace(index.row(), server); - if (index.row() == m_defaultServerIndex) { - emit defaultServerNameChanged(); - } - break; - } - default: { - return true; - } - } - - emit dataChanged(index, index); - return true; -} - -bool ServersModel::setData(const int index, const QVariant &value, int role) -{ - QModelIndex modelIndex = this->index(index); - return setData(modelIndex, value, role); -} - -QVariant ServersModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_servers.size())) { - return QVariant(); - } - - const QJsonObject server = m_servers.at(index.row()).toObject(); - const auto apiConfig = server.value(configKey::apiConfig).toObject(); - const auto configVersion = server.value(config_key::configVersion).toInt(); - switch (role) { - case NameRole: { - if (configVersion) { - return server.value(config_key::name).toString(); - } - auto name = server.value(config_key::description).toString(); - if (name.isEmpty()) { - return server.value(config_key::hostName).toString(); - } - return name; - } - case ServerDescriptionRole: { - auto description = getServerDescription(server, index.row()); - return configVersion ? description : description + server.value(config_key::hostName).toString(); - } - case HostNameRole: return server.value(config_key::hostName).toString(); - case CredentialsRole: return QVariant::fromValue(serverCredentials(index.row())); - case CredentialsLoginRole: return serverCredentials(index.row()).userName; - case IsDefaultRole: return index.row() == m_defaultServerIndex; - case IsCurrentlyProcessedRole: return index.row() == m_processedServerIndex; - case HasWriteAccessRole: { - auto credentials = serverCredentials(index.row()); - return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); - } - case ContainsAmneziaDnsRole: { - QString primaryDns = server.value(config_key::dns1).toString(); - return primaryDns == protocols::dns::amneziaDnsIp; - } - case DefaultContainerRole: { - return ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString()); - } - case HasInstalledContainers: { - return serverHasInstalledContainers(index.row()); - } - case IsServerFromTelegramApiRole: { - return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::Telegram; - } - case IsServerFromGatewayApiRole: { - return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway; - } - case ApiConfigRole: { - return apiConfig; - } - case IsCountrySelectionAvailableRole: { - return !apiConfig.value(configKey::availableCountries).toArray().isEmpty(); - } - case ApiAvailableCountriesRole: { - return apiConfig.value(configKey::availableCountries).toArray(); - } - case ApiServerCountryCodeRole: { - return apiConfig.value(configKey::serverCountryCode).toString(); - } - case HasAmneziaDns: { - QString primaryDns = server.value(config_key::dns1).toString(); - return primaryDns == protocols::dns::amneziaDnsIp; - } - case IsAdVisibleRole: { - return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::isAdVisible).toBool(false); - } - case AdHeaderRole: { - return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adHeader).toString(); - } - case AdDescriptionRole: { - return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adDescription).toString(); - } - case AdEndpointRole: { - return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adEndpoint).toString(); - } - case IsRenewalAvailableRole: { - return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::isRenewalAvailable).toBool(false); - } - case IsSubscriptionExpiredRole: { - if (configVersion != apiDefs::ConfigSource::AmneziaGateway) { - return false; - } - if (apiConfig.value(apiDefs::key::isInAppPurchase).toBool(false)) { - return false; - } - if (apiConfig.value(apiDefs::key::subscriptionExpiredByServer).toBool(false)) { - return true; - } - const QString endDate = - apiConfig.value(apiDefs::key::subscription).toObject().value(apiDefs::key::endDate).toString(); - if (endDate.isEmpty()) { - return false; - } - return apiUtils::isSubscriptionExpired(endDate); - } - case IsSubscriptionExpiringSoonRole: { - if (configVersion != apiDefs::ConfigSource::AmneziaGateway) { - return false; - } - if (apiConfig.value(apiDefs::key::isInAppPurchase).toBool(false)) { - return false; - } - const QString endDate = - apiConfig.value(apiDefs::key::subscription).toObject().value(apiDefs::key::endDate).toString(); - if (endDate.isEmpty()) { - return false; - } - return apiUtils::isSubscriptionExpiringSoon(endDate); - } - } - - return QVariant(); -} - -QVariant ServersModel::data(const int index, int role) const -{ - QModelIndex modelIndex = this->index(index); - return data(modelIndex, role); -} - -void ServersModel::resetModel() -{ - beginResetModel(); - m_servers = m_settings->serversArray(); - m_defaultServerIndex = m_settings->defaultServerIndex(); - m_processedServerIndex = m_defaultServerIndex; - m_isAmneziaDnsEnabled = m_settings->useAmneziaDns(); - endResetModel(); - emit defaultServerIndexChanged(m_defaultServerIndex); -} - -void ServersModel::setDefaultServerIndex(const int index) -{ - m_settings->setDefaultServer(index); - m_defaultServerIndex = m_settings->defaultServerIndex(); - emit defaultServerIndexChanged(m_defaultServerIndex); -} - -const int ServersModel::getDefaultServerIndex() -{ - return m_defaultServerIndex; -} - -const QString ServersModel::getDefaultServerName() -{ - return qvariant_cast(data(m_defaultServerIndex, NameRole)); -} - -QString ServersModel::getServerDescription(const QJsonObject &server, const int index) const -{ - const auto configVersion = server.value(config_key::configVersion).toInt(); - const auto apiConfig = server.value(configKey::apiConfig).toObject(); - - QString description; - - if (configVersion && !apiConfig.value(configKey::serverCountryCode).toString().isEmpty()) { - return apiConfig.value(configKey::serverCountryName).toString(); - } else if (configVersion) { - return server.value(config_key::description).toString(); - } else if (data(index, HasWriteAccessRole).toBool()) { - if (m_isAmneziaDnsEnabled && isAmneziaDnsContainerInstalled(index)) { - description += "Amnezia DNS | "; - } - } else { - if (data(index, HasAmneziaDns).toBool()) { - description += "Amnezia DNS | "; - } - } - return description; -} - -const QString ServersModel::getDefaultServerDescriptionCollapsed() -{ - const QJsonObject serverConfig = m_servers.at(m_defaultServerIndex).toObject(); - const auto configVersion = serverConfig.value(config_key::configVersion).toInt(); - auto description = getServerDescription(serverConfig, m_defaultServerIndex); - if (configVersion) { - return description; - } - - auto container = ContainerProps::containerFromString(serverConfig.value(config_key::defaultContainer).toString()); - QString protocolVersion; - QString containerName = ContainerProps::containerHumanNames().value(container); - - if (ContainerProps::isAwgContainer(container)) { - QJsonObject containerConfig = m_settings->containerConfig(m_defaultServerIndex, container); - QJsonObject serverProtocolConfig = containerConfig.value(ContainerProps::containerTypeToProtocolString(container)).toObject(); - protocolVersion = ProtocolProps::getProtocolVersionString(serverProtocolConfig); - - auto isThirdPartyConfig = serverProtocolConfig.value(config_key::isThirdPartyConfig).toBool(); - if (container == DockerContainer::Awg && !isThirdPartyConfig) { - containerName = "AmneziaWG Legacy"; - } - } - - return description += containerName + protocolVersion + " | " + serverConfig.value(config_key::hostName).toString(); -} - -const QString ServersModel::getDefaultServerDescriptionExpanded() -{ - const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); - const auto configVersion = server.value(config_key::configVersion).toInt(); - auto description = getServerDescription(server, m_defaultServerIndex); - if (configVersion) { - return description; - } - - return description += server.value(config_key::hostName).toString(); -} - -const int ServersModel::getServersCount() -{ - return m_servers.count(); -} - -bool ServersModel::hasServerWithWriteAccess() -{ - for (size_t i = 0; i < getServersCount(); i++) { - if (qvariant_cast(data(i, HasWriteAccessRole))) { - return true; - } - } - return false; -} - -void ServersModel::setProcessedServerIndex(const int index) -{ - m_processedServerIndex = index; - updateContainersModel(); - if (data(index, IsServerFromGatewayApiRole).toBool()) { - if (data(index, IsCountrySelectionAvailableRole).toBool()) { - emit updateApiCountryModel(); - } - emit updateApiServicesModel(); - } - emit processedServerIndexChanged(m_processedServerIndex); -} - -int ServersModel::getProcessedServerIndex() -{ - return m_processedServerIndex; -} - -const ServerCredentials ServersModel::getProcessedServerCredentials() -{ - return serverCredentials(m_processedServerIndex); -} - -const ServerCredentials ServersModel::getServerCredentials(const int index) -{ - return serverCredentials(index); -} - -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(data(m_processedServerIndex, HasWriteAccessRole)); -} - -bool ServersModel::isDefaultServerHasWriteAccess() -{ - return qvariant_cast(data(m_defaultServerIndex, HasWriteAccessRole)); -} - -void ServersModel::addServer(const QJsonObject &server) -{ - beginResetModel(); - m_settings->addServer(server); - m_servers = m_settings->serversArray(); - endResetModel(); -} - -void ServersModel::editServer(const QJsonObject &server, const int serverIndex) -{ - m_settings->editServer(serverIndex, server); - m_servers.replace(serverIndex, m_settings->serversArray().at(serverIndex)); - emit dataChanged(index(serverIndex, 0), index(serverIndex, 0)); - - if (serverIndex == m_defaultServerIndex) { - updateDefaultServerContainersModel(); - } - updateContainersModel(); - - if (serverIndex == m_defaultServerIndex) { - auto defaultContainer = qvariant_cast(getDefaultServerData("defaultContainer")); - emit defaultServerDefaultContainerChanged(defaultContainer); - } -} - -void ServersModel::removeServer() -{ - beginResetModel(); - m_settings->removeServer(m_processedServerIndex); - m_servers = m_settings->serversArray(); - - if (m_settings->defaultServerIndex() == m_processedServerIndex) { - setDefaultServerIndex(0); - } else if (m_settings->defaultServerIndex() > m_processedServerIndex) { - setDefaultServerIndex(m_settings->defaultServerIndex() - 1); - } - - if (m_settings->serversCount() == 0) { - setDefaultServerIndex(-1); - } - setProcessedServerIndex(m_defaultServerIndex); - endResetModel(); -} - -void ServersModel::removeServer(const int serverIndex) -{ - beginResetModel(); - m_settings->removeServer(serverIndex); - m_servers = m_settings->serversArray(); - - if (m_settings->defaultServerIndex() == serverIndex) { - setDefaultServerIndex(0); - } else if (m_settings->defaultServerIndex() > serverIndex) { - setDefaultServerIndex(m_settings->defaultServerIndex() - 1); - } - - if (m_settings->serversCount() == 0) { - setDefaultServerIndex(-1); - } - setProcessedServerIndex(m_defaultServerIndex); - endResetModel(); -} - -QHash ServersModel::roleNames() const -{ - QHash roles; - - roles[NameRole] = "name"; - roles[ServerDescriptionRole] = "serverDescription"; - roles[CollapsedServerDescriptionRole] = "collapsedServerDescription"; - roles[ExpandedServerDescriptionRole] = "expandedServerDescription"; - - roles[HostNameRole] = "hostName"; - - 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"; - - return roles; -} - -ServerCredentials ServersModel::serverCredentials(int index) const -{ - const QJsonObject &s = m_servers.at(index).toObject(); - - ServerCredentials credentials; - credentials.hostName = s.value(config_key::hostName).toString(); - credentials.userName = s.value(config_key::userName).toString(); - credentials.secretData = s.value(config_key::password).toString(); - credentials.port = s.value(config_key::port).toInt(); - - return credentials; -} - -void ServersModel::updateContainersModel() -{ - auto containers = m_servers.at(m_processedServerIndex).toObject().value(config_key::containers).toArray(); - emit containersUpdated(containers); -} - -void ServersModel::updateDefaultServerContainersModel() -{ - auto containers = m_servers.at(m_defaultServerIndex).toObject().value(config_key::containers).toArray(); - emit defaultServerContainersUpdated(containers); -} - -QJsonObject ServersModel::getServerConfig(const int serverIndex) const -{ - return m_servers.at(serverIndex).toObject(); -} - -void ServersModel::reloadDefaultServerContainerConfig() -{ - QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); - auto container = ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString()); - - auto containers = server.value(config_key::containers).toArray(); - - auto config = m_settings->containerConfig(m_defaultServerIndex, container); - for (auto i = 0; i < containers.size(); i++) { - auto c = ContainerProps::containerFromString(containers.at(i).toObject().value(config_key::container).toString()); - if (c == container) { - containers.replace(i, config); - break; - } - } - - server.insert(config_key::containers, containers); - editServer(server, m_defaultServerIndex); -} - -void ServersModel::updateContainerConfig(const int containerIndex, const QJsonObject config) -{ - auto container = static_cast(containerIndex); - QJsonObject server = m_servers.at(m_processedServerIndex).toObject(); - - auto containers = server.value(config_key::containers).toArray(); - for (auto i = 0; i < containers.size(); i++) { - auto c = ContainerProps::containerFromString(containers.at(i).toObject().value(config_key::container).toString()); - if (c == container) { - containers.replace(i, config); - break; - } - } - - server.insert(config_key::containers, containers); - editServer(server, m_processedServerIndex); -} - -void ServersModel::addContainerConfig(const int containerIndex, const QJsonObject config) -{ - auto container = static_cast(containerIndex); - QJsonObject server = m_servers.at(m_processedServerIndex).toObject(); - - auto containers = server.value(config_key::containers).toArray(); - containers.push_back(config); - - server.insert(config_key::containers, containers); - - auto defaultContainer = server.value(config_key::defaultContainer).toString(); - if (ContainerProps::containerFromString(defaultContainer) == DockerContainer::None - && ContainerProps::containerService(container) != ServiceType::Other && ContainerProps::isSupportedByCurrentPlatform(container)) { - server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - } - - editServer(server, m_processedServerIndex); -} - -void ServersModel::setDefaultContainer(const int serverIndex, const int containerIndex) -{ - auto container = static_cast(containerIndex); - QJsonObject s = m_servers.at(serverIndex).toObject(); - s.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - editServer(s, serverIndex); // check -} - -const QString ServersModel::getDefaultServerDefaultContainerName() -{ - auto defaultContainer = qvariant_cast(getDefaultServerData("defaultContainer")); - - QString protocolVersion; - QString containerName = ContainerProps::containerHumanNames().value(defaultContainer); - - if (ContainerProps::isAwgContainer(defaultContainer)) { - QJsonObject containerConfig = m_settings->containerConfig(m_defaultServerIndex, defaultContainer); - QJsonObject serverProtocolConfig = containerConfig.value(ContainerProps::containerTypeToProtocolString(defaultContainer)).toObject(); - protocolVersion = ProtocolProps::getProtocolVersionString(serverProtocolConfig); - - auto isThirdPartyConfig = serverProtocolConfig.value(config_key::isThirdPartyConfig).toBool(); - if (defaultContainer == DockerContainer::Awg && !isThirdPartyConfig) { - containerName = "AmneziaWG Legacy"; - } - } - - return containerName + protocolVersion; -} - -ErrorCode ServersModel::removeAllContainers(const QSharedPointer &serverController) -{ - - ErrorCode errorCode = serverController->removeAllContainers(m_settings->serverCredentials(m_processedServerIndex)); - - if (errorCode == ErrorCode::NoError) { - QJsonObject s = m_servers.at(m_processedServerIndex).toObject(); - s.insert(config_key::containers, {}); - s.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); - - editServer(s, m_processedServerIndex); - } - return errorCode; -} - -ErrorCode ServersModel::rebootServer(const QSharedPointer &serverController) -{ - - auto credentials = m_settings->serverCredentials(m_processedServerIndex); - - ErrorCode errorCode = serverController->rebootServer(credentials); - return errorCode; -} - -ErrorCode ServersModel::removeContainer(const QSharedPointer &serverController, const int containerIndex) -{ - - auto credentials = m_settings->serverCredentials(m_processedServerIndex); - auto dockerContainer = static_cast(containerIndex); - - ErrorCode errorCode = serverController->removeContainer(credentials, dockerContainer); - - if (errorCode == ErrorCode::NoError) { - QJsonObject server = m_servers.at(m_processedServerIndex).toObject(); - - auto containers = server.value(config_key::containers).toArray(); - for (auto it = containers.begin(); it != containers.end(); it++) { - if (it->toObject().value(config_key::container).toString() == ContainerProps::containerToString(dockerContainer)) { - containers.erase(it); - break; - } - } - - server.insert(config_key::containers, containers); - - auto defaultContainer = ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString()); - if (defaultContainer == containerIndex) { - if (containers.empty()) { - defaultContainer = DockerContainer::None; - } else { - defaultContainer = - ContainerProps::containerFromString(containers.begin()->toObject().value(config_key::container).toString()); - } - server.insert(config_key::defaultContainer, ContainerProps::containerToString(defaultContainer)); - } - - editServer(server, m_processedServerIndex); - } - return errorCode; -} - -void ServersModel::clearCachedProfile(const DockerContainer container) -{ - m_settings->clearLastConnectionConfig(m_processedServerIndex, container); - m_servers.replace(m_processedServerIndex, m_settings->server(m_processedServerIndex)); - if (m_processedServerIndex == m_defaultServerIndex) { - updateDefaultServerContainersModel(); - } - updateContainersModel(); -} - -bool ServersModel::isAmneziaDnsContainerInstalled(const int serverIndex) const -{ - QJsonObject server = m_servers.at(serverIndex).toObject(); - auto containers = server.value(config_key::containers).toArray(); - for (auto it = containers.begin(); it != containers.end(); it++) { - if (it->toObject().value(config_key::container).toString() == ContainerProps::containerToString(DockerContainer::Dns)) { - return true; - } - } - return false; -} - -QPair ServersModel::getDnsPair(int serverIndex) -{ - QPair dns; - - const QJsonObject &server = m_servers.at(m_processedServerIndex).toObject(); - const auto containers = server.value(config_key::containers).toArray(); - bool isDnsContainerInstalled = false; - for (const QJsonValue &container : containers) { - if (ContainerProps::containerFromString(container.toObject().value(config_key::container).toString()) == DockerContainer::Dns) { - isDnsContainerInstalled = true; - } - } - - dns.first = server.value(config_key::dns1).toString(); - dns.second = server.value(config_key::dns2).toString(); - - if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) { - if (m_isAmneziaDnsEnabled && isDnsContainerInstalled) { - dns.first = protocols::dns::amneziaDnsIp; - } else - dns.first = m_settings->primaryDns(); - } - if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) { - dns.second = m_settings->secondaryDns(); - } - - qDebug() << "VpnConfigurator::getDnsForConfig" << dns.first << dns.second; - return dns; -} - -QStringList ServersModel::getAllInstalledServicesName(const int serverIndex) -{ - QStringList servicesName; - QJsonObject server = m_servers.at(serverIndex).toObject(); - const auto containers = server.value(config_key::containers).toArray(); - for (auto it = containers.begin(); it != containers.end(); it++) { - auto container = ContainerProps::containerFromString(it->toObject().value(config_key::container).toString()); - if (ContainerProps::containerService(container) == ServiceType::Other) { - if (container == DockerContainer::Dns) { - servicesName.append("DNS"); - } else if (container == DockerContainer::Sftp) { - servicesName.append("SFTP"); - } else if (container == DockerContainer::TorWebSite) { - servicesName.append("TOR"); - } else if (container == DockerContainer::Socks5Proxy) { - servicesName.append("SOCKS5"); - } - } - } - servicesName.sort(); - return servicesName; -} - -void ServersModel::toggleAmneziaDns(bool enabled) -{ - m_isAmneziaDnsEnabled = enabled; - emit defaultServerDescriptionChanged(); -} - -bool ServersModel::isServerFromApiAlreadyExists(const quint16 crc) -{ - for (const auto &server : std::as_const(m_servers)) { - if (static_cast(server.toObject().value(config_key::crc).toInt()) == crc) { - return true; - } - } - return false; -} - -bool ServersModel::isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) -{ - for (const auto &server : std::as_const(m_servers)) { - const auto apiConfig = server.toObject().value(configKey::apiConfig).toObject(); - if (apiConfig.value(configKey::userCountryCode).toString() == userCountryCode - && apiConfig.value(configKey::serviceType).toString() == serviceType - && apiConfig.value(configKey::serviceProtocol).toString() == serviceProtocol) { - return true; - } - } - return false; -} - -int ServersModel::indexOfServerWithVpnKey(const QString &vpnKey) const -{ - const QString normalizedInput = normalizeVpnKey(vpnKey); - if (normalizedInput.isEmpty()) { - return -1; - } - - for (int i = 0; i < m_servers.size(); ++i) { - const auto apiConfig = m_servers.at(i).toObject().value(configKey::apiConfig).toObject(); - const QString existingKey = normalizeVpnKey(apiConfig.value(apiDefs::key::vpnKey).toString()); - if (!existingKey.isEmpty() && existingKey == normalizedInput) { - return i; - } - } - return -1; -} - -bool ServersModel::serverHasInstalledContainers(const int serverIndex) const -{ - QJsonObject server = m_servers.at(serverIndex).toObject(); - const auto containers = server.value(config_key::containers).toArray(); - for (auto it = containers.begin(); it != containers.end(); it++) { - auto container = ContainerProps::containerFromString(it->toObject().value(config_key::container).toString()); - if (ContainerProps::containerService(container) == ServiceType::Vpn) { - return true; - } - if (container == DockerContainer::SSXray) { - return true; - } - } - return false; -} - -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::setProcessedServerData(const QString &roleString, const QVariant &value) -{ - const auto roles = roleNames(); - for (auto it = roles.begin(); it != roles.end(); it++) { - if (QString(it.value()) == roleString) { - return setData(m_processedServerIndex, value, it.key()); - } - } - - return false; -} - -bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling() -{ - auto server = m_servers.at(m_defaultServerIndex).toObject(); - auto defaultContainer = ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString()); - - auto containers = server.value(config_key::containers).toArray(); - for (auto i = 0; i < containers.size(); i++) { - auto container = containers.at(i).toObject(); - if (container.value(config_key::container).toString() != ContainerProps::containerToString(defaultContainer)) { - continue; - } - if (ContainerProps::isAwgContainer(defaultContainer) || defaultContainer == DockerContainer::WireGuard) { - QJsonObject serverProtocolConfig = container.value(ContainerProps::containerTypeToProtocolString(defaultContainer)).toObject(); - QString clientProtocolConfigString = serverProtocolConfig.value(config_key::last_config).toString(); - QJsonObject clientProtocolConfig = QJsonDocument::fromJson(clientProtocolConfigString.toUtf8()).object(); - return (clientProtocolConfigString.contains("AllowedIPs") && !clientProtocolConfigString.contains("AllowedIPs = 0.0.0.0/0, ::/0")) - || (!clientProtocolConfig.value(config_key::allowed_ips).toArray().isEmpty() - && !clientProtocolConfig.value(config_key::allowed_ips).toArray().contains("0.0.0.0/0")); - } else if (defaultContainer == DockerContainer::Cloak || defaultContainer == DockerContainer::OpenVpn - || defaultContainer == DockerContainer::ShadowSocks) { - auto serverProtocolConfig = container.value(ContainerProps::containerTypeToProtocolString(DockerContainer::OpenVpn)).toObject(); - QString clientProtocolConfigString = serverProtocolConfig.value(config_key::last_config).toString(); - return !clientProtocolConfigString.isEmpty() && !clientProtocolConfigString.contains("redirect-gateway"); - } - } - return false; -} - -bool ServersModel::isServerFromApi(const int serverIndex) -{ - return data(serverIndex, IsServerFromTelegramApiRole).toBool() || data(serverIndex, IsServerFromGatewayApiRole).toBool(); -} - -bool ServersModel::hasServersFromGatewayApi() -{ - return !m_gatewayStacks.isEmpty(); -} - -bool ServersModel::GatewayStacks::operator==(const GatewayStacks &other) const -{ - return userCountryCodes == other.userCountryCodes && serviceTypes == other.serviceTypes; -} - -QJsonObject ServersModel::GatewayStacks::toJson() const -{ - QJsonObject obj; - if (!userCountryCodes.isEmpty()) { - obj.insert(configKey::userCountryCode, QJsonArray::fromStringList(userCountryCodes.values())); - } - if (!serviceTypes.isEmpty()) { - obj.insert(configKey::serviceType, QJsonArray::fromStringList(serviceTypes.values())); - } - return obj; -} - -void ServersModel::recomputeGatewayStacks() -{ - const bool wasEmpty = m_gatewayStacks.isEmpty(); - GatewayStacks computed; - bool hasNewTags = false; - - for (int i = 0; i < m_servers.count(); ++i) { - if (data(i, IsServerFromGatewayApiRole).toBool()) { - const QJsonObject server = m_servers.at(i).toObject(); - const QJsonObject apiConfig = server.value(configKey::apiConfig).toObject(); - - const QString userCountryCode = apiConfig.value(configKey::userCountryCode).toString(); - const QString serviceType = apiConfig.value(configKey::serviceType).toString(); - - if (!userCountryCode.isEmpty()) { - if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) { - hasNewTags = true; - } - computed.userCountryCodes.insert(userCountryCode); - } - - if (!serviceType.isEmpty()) { - if (!m_gatewayStacks.serviceTypes.contains(serviceType)) { - hasNewTags = true; - } - computed.serviceTypes.insert(serviceType); - } - } - } - - m_gatewayStacks = std::move(computed); - if (hasNewTags) { - emit gatewayStacksExpanded(); - } - - if (wasEmpty != m_gatewayStacks.isEmpty()) { - emit hasServersFromGatewayApiChanged(); - } -} - -bool ServersModel::isApiKeyExpired(const int serverIndex) -{ - auto serverConfig = m_servers.at(serverIndex).toObject(); - auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); - - auto publicKeyInfo = apiConfig.value(configKey::publicKeyInfo).toObject(); - const QString expiresAt = publicKeyInfo.value(configKey::expiresAt).toString(); - if (expiresAt.isEmpty()) { - publicKeyInfo.insert(configKey::expiresAt, QDateTime::currentDateTimeUtc().addDays(1).toString(Qt::ISODate)); - apiConfig.insert(configKey::publicKeyInfo, publicKeyInfo); - serverConfig.insert(configKey::apiConfig, apiConfig); - editServer(serverConfig, serverIndex); - - return false; - } - - auto expiresAtDateTime = QDateTime::fromString(expiresAt, Qt::ISODate).toUTC(); - if (expiresAtDateTime < QDateTime::currentDateTimeUtc()) { - return true; - } - return false; -} - -void ServersModel::removeApiConfig(const int serverIndex) -{ - auto serverConfig = getServerConfig(serverIndex); - -#if defined(Q_OS_IOS) || defined(MACOS_NE) - QString vpncName = QString("%1 (%2) %3") - .arg(serverConfig[config_key::description].toString()) - .arg(serverConfig[config_key::hostName].toString()) - .arg(serverConfig[config_key::vpnproto].toString()); - - AmneziaVPN::removeVPNC(vpncName.toStdString()); -#endif - - serverConfig.remove(config_key::dns1); - serverConfig.remove(config_key::dns2); - serverConfig.remove(config_key::containers); - serverConfig.remove(config_key::hostName); - - auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); - apiConfig.remove(configKey::publicKeyInfo); - serverConfig.insert(configKey::apiConfig, apiConfig); - - serverConfig.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); - - editServer(serverConfig, serverIndex); -} - -const QString ServersModel::getDefaultServerImagePathCollapsed() -{ - const auto server = m_servers.at(m_defaultServerIndex).toObject(); - const auto apiConfig = server.value(configKey::apiConfig).toObject(); - const auto countryCode = apiConfig.value(configKey::serverCountryCode).toString(); - - if (countryCode.isEmpty()) { - return ""; - } - return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode.toUpper()); -} - -bool ServersModel::processedServerIsPremium() const -{ - return apiUtils::isPremiumServer(getServerConfig(m_processedServerIndex)); -} - -bool ServersModel::isAdVisible() -{ - return data(m_defaultServerIndex, IsAdVisibleRole).toBool(); -} - -QString ServersModel::adHeader() -{ - return data(m_defaultServerIndex, AdHeaderRole).toString(); -} - -QString ServersModel::adDescription() -{ - return data(m_defaultServerIndex, AdDescriptionRole).toString(); -} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h deleted file mode 100644 index 548711a27..000000000 --- a/client/ui/models/servers_model.h +++ /dev/null @@ -1,211 +0,0 @@ -#ifndef SERVERSMODEL_H -#define SERVERSMODEL_H - -#include - -#include "core/controllers/serverController.h" -#include "settings.h" - -class ServersModel : public QAbstractListModel -{ - Q_OBJECT -public: - struct GatewayStacks - { - QSet userCountryCodes; - QSet serviceTypes; - - bool isEmpty() const { return userCountryCodes.isEmpty() && serviceTypes.isEmpty(); } - bool operator==(const GatewayStacks &other) const; - QJsonObject toJson() const; - }; - - enum Roles { - NameRole = Qt::UserRole + 1, - ServerDescriptionRole, - CollapsedServerDescriptionRole, - ExpandedServerDescriptionRole, - HostNameRole, - - CredentialsRole, - CredentialsLoginRole, - - IsDefaultRole, - IsCurrentlyProcessedRole, - - HasWriteAccessRole, - - ContainsAmneziaDnsRole, - - DefaultContainerRole, - - HasInstalledContainers, - - IsServerFromTelegramApiRole, - IsServerFromGatewayApiRole, - ApiConfigRole, - IsCountrySelectionAvailableRole, - ApiAvailableCountriesRole, - ApiServerCountryCodeRole, - IsAdVisibleRole, - AdHeaderRole, - AdDescriptionRole, - AdEndpointRole, - IsRenewalAvailableRole, - - IsSubscriptionExpiredRole, - IsSubscriptionExpiringSoonRole, - - HasAmneziaDns - }; - - ServersModel(std::shared_ptr settings, QObject *parent = nullptr); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - bool setData(const int index, const QVariant &value, int role = Qt::EditRole); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant data(const int index, int role = Qt::DisplayRole) const; - - void resetModel(); - - GatewayStacks gatewayStacks() const { return m_gatewayStacks; } - - Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged) - Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerNameChanged) - Q_PROPERTY(QString defaultServerDefaultContainerName READ getDefaultServerDefaultContainerName NOTIFY defaultServerDefaultContainerChanged) - Q_PROPERTY(QString defaultServerDescriptionCollapsed READ getDefaultServerDescriptionCollapsed NOTIFY defaultServerDefaultContainerChanged) - Q_PROPERTY(QString defaultServerImagePathCollapsed READ getDefaultServerImagePathCollapsed NOTIFY defaultServerDefaultContainerChanged) - Q_PROPERTY(QString defaultServerDescriptionExpanded READ getDefaultServerDescriptionExpanded NOTIFY defaultServerDefaultContainerChanged) - Q_PROPERTY(bool isDefaultServerDefaultContainerHasSplitTunneling READ isDefaultServerDefaultContainerHasSplitTunneling NOTIFY - defaultServerDefaultContainerChanged) - Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged) - - Q_PROPERTY(bool hasServersFromGatewayApi READ hasServersFromGatewayApi NOTIFY hasServersFromGatewayApiChanged) - - Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged) - Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerChanged) - - Q_PROPERTY(bool isAdVisible READ isAdVisible NOTIFY defaultServerIndexChanged) - Q_PROPERTY(QString adHeader READ adHeader NOTIFY defaultServerIndexChanged) - Q_PROPERTY(QString adDescription READ adDescription NOTIFY defaultServerIndexChanged) - - bool processedServerIsPremium() const; - -public slots: - void setDefaultServerIndex(const int index); - const int getDefaultServerIndex(); - const QString getDefaultServerName(); - const QString getDefaultServerDescriptionCollapsed(); - const QString getDefaultServerImagePathCollapsed(); - const QString getDefaultServerDescriptionExpanded(); - const QString getDefaultServerDefaultContainerName(); - bool isDefaultServerCurrentlyProcessed(); - bool isDefaultServerFromApi(); - - bool isProcessedServerHasWriteAccess(); - bool isDefaultServerHasWriteAccess(); - bool hasServerWithWriteAccess(); - - bool hasServersFromGatewayApi(); - - const int getServersCount(); - - void setProcessedServerIndex(const int index); - int getProcessedServerIndex(); - - const ServerCredentials getProcessedServerCredentials(); - const ServerCredentials getServerCredentials(const int index); - - void addServer(const QJsonObject &server); - void editServer(const QJsonObject &server, const int serverIndex); - void removeServer(); - void removeServer(const int serverIndex); - - QJsonObject getServerConfig(const int serverIndex) const; - - void reloadDefaultServerContainerConfig(); - void updateContainerConfig(const int containerIndex, const QJsonObject config); - void addContainerConfig(const int containerIndex, const QJsonObject config); - - void clearCachedProfile(const DockerContainer container); - - ErrorCode removeContainer(const QSharedPointer &serverController, const int containerIndex); - ErrorCode removeAllContainers(const QSharedPointer &serverController); - ErrorCode rebootServer(const QSharedPointer &serverController); - - void setDefaultContainer(const int serverIndex, const int containerIndex); - - QStringList getAllInstalledServicesName(const int serverIndex); - - void toggleAmneziaDns(bool enabled); - QPair getDnsPair(const int serverIndex); - - bool isServerFromApiAlreadyExists(const quint16 crc); - bool isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol); - int indexOfServerWithVpnKey(const QString &vpnKey) const; - - QVariant getDefaultServerData(const QString roleString); - - QVariant getProcessedServerData(const QString roleString); - bool setProcessedServerData(const QString &roleString, const QVariant &value); - - bool isDefaultServerDefaultContainerHasSplitTunneling(); - - bool isServerFromApi(const int serverIndex); - bool isApiKeyExpired(const int serverIndex); - void removeApiConfig(const int serverIndex); - - bool isAdVisible(); - QString adHeader(); - QString adDescription(); - -protected: - QHash roleNames() const override; - -signals: - void processedServerIndexChanged(const int index); - // emitted when the processed server index or processed server data is changed - void processedServerChanged(); - - void defaultServerIndexChanged(const int index); - void defaultServerNameChanged(); - void defaultServerDescriptionChanged(); - - void containersUpdated(const QJsonArray &containers); - void defaultServerContainersUpdated(const QJsonArray &containers); - void defaultServerDefaultContainerChanged(const int containerIndex); - - void updateApiCountryModel(); - void updateApiServicesModel(); - - void hasServersFromGatewayApiChanged(); - void gatewayStacksExpanded(); - -private: - ServerCredentials serverCredentials(int index) const; - - void updateContainersModel(); - void updateDefaultServerContainersModel(); - - QString getServerDescription(const QJsonObject &server, const int index) const; - - bool isAmneziaDnsContainerInstalled(const int serverIndex) const; - - bool serverHasInstalledContainers(const int serverIndex) const; - - QJsonArray m_servers; - - std::shared_ptr m_settings; - - int m_defaultServerIndex; - int m_processedServerIndex; - - bool m_isAmneziaDnsEnabled = m_settings->useAmneziaDns(); - - GatewayStacks m_gatewayStacks; - void recomputeGatewayStacks(); -}; - -#endif // SERVERSMODEL_H diff --git a/client/ui/models/services/sftpConfigModel.cpp b/client/ui/models/services/sftpConfigModel.cpp index 3cbb5ebcd..da6ba29fb 100644 --- a/client/ui/models/services/sftpConfigModel.cpp +++ b/client/ui/models/services/sftpConfigModel.cpp @@ -1,6 +1,11 @@ #include "sftpConfigModel.h" -#include "protocols/protocols_defs.h" +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" + +using namespace amnezia; SftpConfigModel::SftpConfigModel(QObject *parent) : QAbstractListModel(parent) { @@ -12,44 +17,61 @@ int SftpConfigModel::rowCount(const QModelIndex &parent) const return 1; } -QVariant SftpConfigModel::data(const QModelIndex &index, int role) const +bool SftpConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { return false; } + QString strValue = value.toString(); + switch (role) { - case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(); + case Roles::PortRole: m_protocolConfig.port = strValue; break; + case Roles::UserNameRole: m_protocolConfig.userName = strValue; break; + case Roles::PasswordRole: m_protocolConfig.password = strValue; break; + default: + return false; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant SftpConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return QVariant(); + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.port; case Roles::UserNameRole: - return m_protocolConfig.value(config_key::userName).toString(protocols::sftp::defaultUserName); - case Roles::PasswordRole: return m_protocolConfig.value(config_key::password).toString(); + return m_protocolConfig.userName.isEmpty() ? QString(protocols::sftp::defaultUserName) : m_protocolConfig.userName; + case Roles::PasswordRole: return m_protocolConfig.password; } return QVariant(); } -void SftpConfigModel::updateModel(const QJsonObject &config) +void SftpConfigModel::updateModel(amnezia::DockerContainer container, const amnezia::SftpProtocolConfig &protocolConfig) { beginResetModel(); - m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); - - m_fullConfig = config; - QJsonObject protocolConfig = config.value(config_key::sftp).toObject(); - - m_protocolConfig.insert(config_key::userName, - protocolConfig.value(config_key::userName).toString(protocols::sftp::defaultUserName)); - - m_protocolConfig.insert(config_key::password, protocolConfig.value(config_key::password).toString()); - - m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString()); - + m_container = container; + m_protocolConfig = protocolConfig; + applyDefaults(m_protocolConfig); endResetModel(); } -QJsonObject SftpConfigModel::getConfig() +amnezia::SftpProtocolConfig SftpConfigModel::getProtocolConfig() { - m_fullConfig.insert(config_key::sftp, m_protocolConfig); - return m_fullConfig; + return m_protocolConfig; +} + +void SftpConfigModel::applyDefaults(amnezia::SftpProtocolConfig& config) +{ + if (config.userName.isEmpty()) { + config.userName = protocols::sftp::defaultUserName; + } } QHash SftpConfigModel::roleNames() const @@ -62,3 +84,4 @@ QHash SftpConfigModel::roleNames() const return roles; } + diff --git a/client/ui/models/services/sftpConfigModel.h b/client/ui/models/services/sftpConfigModel.h index e948591e5..8c18a7094 100644 --- a/client/ui/models/services/sftpConfigModel.h +++ b/client/ui/models/services/sftpConfigModel.h @@ -2,9 +2,11 @@ #define SFTPCONFIGMODEL_H #include -#include -#include "containers/containers_defs.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/protocols/sftpProtocolConfig.h" class SftpConfigModel : public QAbstractListModel { @@ -21,19 +23,21 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void updateModel(const QJsonObject &config); - QJsonObject getConfig(); + void updateModel(amnezia::DockerContainer container, const amnezia::SftpProtocolConfig &protocolConfig); + amnezia::SftpProtocolConfig getProtocolConfig(); protected: QHash roleNames() const override; private: - DockerContainer m_container; - QJsonObject m_protocolConfig; - QJsonObject m_fullConfig; + amnezia::DockerContainer m_container; + amnezia::SftpProtocolConfig m_protocolConfig; + + void applyDefaults(amnezia::SftpProtocolConfig& config); }; #endif // SFTPCONFIGMODEL_H diff --git a/client/ui/models/services/socks5ProxyConfigModel.cpp b/client/ui/models/services/socks5ProxyConfigModel.cpp index f68670df8..220ce2e9a 100644 --- a/client/ui/models/services/socks5ProxyConfigModel.cpp +++ b/client/ui/models/services/socks5ProxyConfigModel.cpp @@ -1,6 +1,11 @@ #include "socks5ProxyConfigModel.h" -#include "protocols/protocols_defs.h" +#include "core/utils/protocolEnum.h" +#include "core/protocols/protocolUtils.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/constants/protocolConstants.h" + +using namespace amnezia; Socks5ProxyConfigModel::Socks5ProxyConfigModel(QObject *parent) : QAbstractListModel(parent) { @@ -14,14 +19,18 @@ int Socks5ProxyConfigModel::rowCount(const QModelIndex &parent) const bool Socks5ProxyConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { return false; } + QString strValue = value.toString(); + switch (role) { - case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; - case Roles::UserNameRole: m_protocolConfig.insert(config_key::userName, value.toString()); break; - case Roles::PasswordRole: m_protocolConfig.insert(config_key::password, value.toString()); break; + case Roles::PortRole: m_protocolConfig.port = strValue; break; + case Roles::UserNameRole: m_protocolConfig.userName = strValue; break; + case Roles::PasswordRole: m_protocolConfig.password = strValue; break; + default: + return false; } emit dataChanged(index, index, QList { role }); @@ -31,41 +40,29 @@ bool Socks5ProxyConfigModel::setData(const QModelIndex &index, const QVariant &v QVariant Socks5ProxyConfigModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { - return false; + return QVariant(); } switch (role) { - case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(); - case Roles::UserNameRole: - return m_protocolConfig.value(config_key::userName).toString(); - case Roles::PasswordRole: return m_protocolConfig.value(config_key::password).toString(); + case Roles::PortRole: return m_protocolConfig.port; + case Roles::UserNameRole: return m_protocolConfig.userName; + case Roles::PasswordRole: return m_protocolConfig.password; } return QVariant(); } -void Socks5ProxyConfigModel::updateModel(const QJsonObject &config) +void Socks5ProxyConfigModel::updateModel(amnezia::DockerContainer container, const amnezia::Socks5ProxyProtocolConfig &protocolConfig) { beginResetModel(); - m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); - - m_fullConfig = config; - QJsonObject protocolConfig = config.value(config_key::socks5proxy).toObject(); - - m_protocolConfig.insert(config_key::userName, - protocolConfig.value(config_key::userName).toString()); - - m_protocolConfig.insert(config_key::password, protocolConfig.value(config_key::password).toString()); - - m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString()); - + m_container = container; + m_protocolConfig = protocolConfig; endResetModel(); } -QJsonObject Socks5ProxyConfigModel::getConfig() +amnezia::Socks5ProxyProtocolConfig Socks5ProxyConfigModel::getProtocolConfig() { - m_fullConfig.insert(config_key::socks5proxy, m_protocolConfig); - return m_fullConfig; + return m_protocolConfig; } QHash Socks5ProxyConfigModel::roleNames() const diff --git a/client/ui/models/services/socks5ProxyConfigModel.h b/client/ui/models/services/socks5ProxyConfigModel.h index fc6f2fd4c..847806d33 100644 --- a/client/ui/models/services/socks5ProxyConfigModel.h +++ b/client/ui/models/services/socks5ProxyConfigModel.h @@ -2,9 +2,11 @@ #define SOCKS5PROXYCONFIGMODEL_H #include -#include -#include "containers/containers_defs.h" +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/protocols/socks5ProxyProtocolConfig.h" class Socks5ProxyConfigModel : public QAbstractListModel { @@ -25,16 +27,15 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void updateModel(const QJsonObject &config); - QJsonObject getConfig(); + void updateModel(amnezia::DockerContainer container, const amnezia::Socks5ProxyProtocolConfig &protocolConfig); + amnezia::Socks5ProxyProtocolConfig getProtocolConfig(); protected: QHash roleNames() const override; private: - DockerContainer m_container; - QJsonObject m_protocolConfig; - QJsonObject m_fullConfig; + amnezia::DockerContainer m_container; + amnezia::Socks5ProxyProtocolConfig m_protocolConfig; }; #endif // SOCKS5PROXYCONFIGMODEL_H diff --git a/client/ui/models/services/torConfigModel.cpp b/client/ui/models/services/torConfigModel.cpp new file mode 100644 index 000000000..c430d7dde --- /dev/null +++ b/client/ui/models/services/torConfigModel.cpp @@ -0,0 +1,77 @@ +#include "torConfigModel.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/models/containerConfig.h" +#include "core/models/protocols/torProtocolConfig.h" + +using namespace amnezia; +using namespace ProtocolUtils; + +TorConfigModel::TorConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int TorConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool TorConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + QString strValue = value.toString(); + + switch (role) { + case Roles::SiteRole: m_protocolConfig.serverConfig.site = strValue; break; + default: + return false; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant TorConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return QVariant(); + } + + switch (role) { + case Roles::SiteRole: return m_protocolConfig.serverConfig.site; + } + + return QVariant(); +} + +void TorConfigModel::updateModel(amnezia::DockerContainer container, const amnezia::TorProtocolConfig &protocolConfig) +{ + beginResetModel(); + m_container = container; + m_protocolConfig = protocolConfig; + m_originalProtocolConfig = m_protocolConfig; + + endResetModel(); +} + +amnezia::TorProtocolConfig TorConfigModel::getProtocolConfig() +{ + return m_protocolConfig; +} + +QHash TorConfigModel::roleNames() const +{ + QHash roles; + + roles[SiteRole] = "site"; + + return roles; +} + diff --git a/client/ui/models/services/torConfigModel.h b/client/ui/models/services/torConfigModel.h new file mode 100644 index 000000000..6a1834d08 --- /dev/null +++ b/client/ui/models/services/torConfigModel.h @@ -0,0 +1,41 @@ +#ifndef TORCONFIGMODEL_H +#define TORCONFIGMODEL_H + +#include + +#include "core/utils/containerEnum.h" +#include "core/utils/containers/containerUtils.h" +#include "core/utils/protocolEnum.h" +#include "core/models/protocols/torProtocolConfig.h" + +class TorConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + SiteRole + }; + + explicit TorConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(amnezia::DockerContainer container, const amnezia::TorProtocolConfig &protocolConfig); + amnezia::TorProtocolConfig getProtocolConfig(); + +protected: + QHash roleNames() const override; + +private: + amnezia::DockerContainer m_container; + amnezia::TorProtocolConfig m_protocolConfig; + amnezia::TorProtocolConfig m_originalProtocolConfig; +}; + +#endif // TORCONFIGMODEL_H + diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp deleted file mode 100644 index a3bc0009e..000000000 --- a/client/ui/models/sites_model.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "sites_model.h" - -SitesModel::SitesModel(std::shared_ptr settings, QObject *parent) - : QAbstractListModel(parent), m_settings(settings) -{ - m_isSplitTunnelingEnabled = m_settings->isSitesSplitTunnelingEnabled(); - m_currentRouteMode = m_settings->routeMode(); - if (m_currentRouteMode == Settings::VpnAllSites) { // for old split tunneling configs - m_settings->setRouteMode(static_cast(Settings::VpnOnlyForwardSites)); - m_currentRouteMode = Settings::VpnOnlyForwardSites; - } - fillSites(); -} - -int SitesModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - return m_sites.size(); -} - -QVariant SitesModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) - return QVariant(); - - switch (role) { - case UrlRole: { - return m_sites.at(index.row()).first; - break; - } - case IpRole: { - return m_sites.at(index.row()).second; - break; - } - default: { - return true; - } - } - - return QVariant(); -} - -bool SitesModel::addSite(const QString &hostname, const QString &ip) -{ - if (!m_settings->addVpnSite(m_currentRouteMode, hostname, ip)) { - return false; - } - for (int i = 0; i < m_sites.size(); i++) { - if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) { - m_sites[i].second = ip; - QModelIndex index = createIndex(i, i); - emit dataChanged(index, index); - return true; - } else if (m_sites[i].first == hostname && (m_sites[i].second == ip)) { - return false; - } - } - beginInsertRows(QModelIndex(), rowCount(), rowCount()); - m_sites.append(qMakePair(hostname, ip)); - endInsertRows(); - return true; -} - -void SitesModel::addSites(const QMap &sites, bool replaceExisting) -{ - beginResetModel(); - - if (replaceExisting) { - m_settings->removeAllVpnSites(m_currentRouteMode); - } - m_settings->addVpnSites(m_currentRouteMode, sites); - fillSites(); - - endResetModel(); -} - -void SitesModel::removeSite(QModelIndex index) -{ - auto hostname = m_sites.at(index.row()).first; - beginRemoveRows(QModelIndex(), index.row(), index.row()); - m_settings->removeVpnSite(m_currentRouteMode, hostname); - m_sites.removeAt(index.row()); - endRemoveRows(); -} - -void SitesModel::removeSites() -{ - beginResetModel(); - - m_settings->removeAllVpnSites(m_currentRouteMode); - fillSites(); - - endResetModel(); -} - -int SitesModel::getRouteMode() -{ - return m_currentRouteMode; -} - -void SitesModel::setRouteMode(int routeMode) -{ - beginResetModel(); - m_settings->setRouteMode(static_cast(routeMode)); - m_currentRouteMode = m_settings->routeMode(); - fillSites(); - endResetModel(); - emit routeModeChanged(); -} - -bool SitesModel::isSplitTunnelingEnabled() -{ - return m_isSplitTunnelingEnabled; -} - -void SitesModel::toggleSplitTunneling(bool enabled) -{ - m_settings->setSitesSplitTunnelingEnabled(enabled); - m_isSplitTunnelingEnabled = enabled; - emit splitTunnelingToggled(); -} - -QVector > SitesModel::getCurrentSites() -{ - return m_sites; -} - -QHash SitesModel::roleNames() const -{ - QHash roles; - roles[UrlRole] = "url"; - roles[IpRole] = "ip"; - return roles; -} - -void SitesModel::fillSites() -{ - m_sites.clear(); - const QVariantMap &sites = m_settings->vpnSites(m_currentRouteMode); - auto i = sites.constBegin(); - while (i != sites.constEnd()) { - m_sites.append(qMakePair(i.key(), i.value().toString())); - ++i; - } -} diff --git a/client/ui/models/sites_model.h b/client/ui/models/sites_model.h deleted file mode 100644 index fc0628878..000000000 --- a/client/ui/models/sites_model.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef SITESMODEL_H -#define SITESMODEL_H - -#include - -#include "settings.h" - -class SitesModel : public QAbstractListModel -{ - Q_OBJECT - -public: - enum Roles { - UrlRole = Qt::UserRole + 1, - IpRole - }; - - explicit SitesModel(std::shared_ptr settings, QObject *parent = nullptr); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - Q_PROPERTY(int routeMode READ getRouteMode WRITE setRouteMode NOTIFY routeModeChanged) - Q_PROPERTY(bool isTunnelingEnabled READ isSplitTunnelingEnabled NOTIFY splitTunnelingToggled) - -public slots: - bool addSite(const QString &hostname, const QString &ip); - void addSites(const QMap &sites, bool replaceExisting); - void removeSite(QModelIndex index); - void removeSites(); - - int getRouteMode(); - void setRouteMode(int routeMode); - - bool isSplitTunnelingEnabled(); - void toggleSplitTunneling(bool enabled); - - QVector> getCurrentSites(); - -signals: - void routeModeChanged(); - void splitTunnelingToggled(); - -protected: - QHash roleNames() const override; - -private: - void fillSites(); - - std::shared_ptr m_settings; - - bool m_isSplitTunnelingEnabled; - Settings::RouteMode m_currentRouteMode; - - QVector> m_sites; -}; - -#endif // SITESMODEL_H diff --git a/client/ui/qml/Components/AdLabel.qml b/client/ui/qml/Components/AdLabel.qml index fedaa0e56..cf2073fac 100644 --- a/client/ui/qml/Components/AdLabel.qml +++ b/client/ui/qml/Components/AdLabel.qml @@ -24,7 +24,7 @@ Rectangle { border.color: AmneziaStyle.color.onyxBlack radius: 13 - visible: ServersModel.isAdVisible + visible: ServersUiController.isAdVisible Keys.onTabPressed: { FocusController.nextKeyTabItem() @@ -73,7 +73,7 @@ Rectangle { CaptionTextType { Layout.fillWidth: true - text: ServersModel.adHeader + text: ServersUiController.adHeader color: AmneziaStyle.color.paleGray font.pixelSize: 14 font.weight: 700 @@ -83,7 +83,7 @@ Rectangle { CaptionTextType { Layout.fillWidth: true - text: ServersModel.adDescription + text: ServersUiController.adDescription color: AmneziaStyle.color.mutedGray wrapMode: Text.WordWrap lineHeight: 18 diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index b90891a0b..153aef122 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -182,7 +182,7 @@ Button { } onClicked: { - ServersModel.setProcessedServerIndex(ServersModel.defaultIndex) + ServersUiController.setProcessedServerIndex(ServersUiController.defaultIndex) ConnectionController.connectButtonClicked() } diff --git a/client/ui/qml/Components/GamepadLoader.qml b/client/ui/qml/Components/GamepadLoader.qml index 069e15490..d7853582c 100644 --- a/client/ui/qml/Components/GamepadLoader.qml +++ b/client/ui/qml/Components/GamepadLoader.qml @@ -13,7 +13,7 @@ Item { onButtonStartChanged: { if (buttonStart) { - ServersModel.setProcessedServerIndex(ServersModel.defaultIndex) + ServersUiController.setProcessedServerIndex(ServersUiController.defaultIndex) ConnectionController.connectButtonClicked() } } diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 54cb27e24..bd24f9f3b 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -5,7 +5,6 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import "../Controls2" import "../Controls2/TextTypes" @@ -59,10 +58,9 @@ ListViewType { if (checked) { containersDropDown.closeTriggered() - ServersModel.setDefaultContainer(ServersModel.defaultIndex, proxyDefaultServerContainersModel.mapToSource(index)) + ServersUiController.setDefaultContainer(ServersUiController.defaultIndex, proxyDefaultServerContainersModel.mapToSource(index)) } else { - ContainersModel.setProcessedContainerIndex(proxyDefaultServerContainersModel.mapToSource(index)) - InstallController.setShouldCreateServer(false) + ServersUiController.processedContainerIndex = proxyDefaultServerContainersModel.mapToSource(index) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.closeTriggered() } diff --git a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml index 6fdb6852d..36e8be689 100644 --- a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml +++ b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml @@ -1,97 +1,97 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import PageEnum 1.0 - -import "../Controls2" -import "../Controls2/TextTypes" -import "../Config" - -DrawerType2 { - id: root - - property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android" - - anchors.fill: parent - expandedHeight: parent.height * 0.9 - - expandedStateContent: ColumnLayout { - id: content - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - spacing: 0 - - Header2Type { - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 - - headerText: qsTr("Split tunneling") - descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others") - } - - LabelWithButtonType { - id: splitTunnelingSwitch - Layout.fillWidth: true - Layout.topMargin: 16 - - visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling - - text: qsTr("Split tunneling on the server") - descriptionText: qsTr("Enabled \nCan't be disabled for current server") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - - clickedFunction: function() { - PageController.goToPage(PageEnum.PageSettingsSplitTunneling) - root.closeTriggered() - } - } - - DividerType { - visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling - } - - LabelWithButtonType { - id: siteBasedSplitTunnelingSwitch - Layout.fillWidth: true - Layout.topMargin: 16 - - text: qsTr("Site-based split tunneling") - descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - - clickedFunction: function() { - PageController.goToPage(PageEnum.PageSettingsSplitTunneling) - root.closeTriggered() - } - } - - DividerType { - } - - LabelWithButtonType { - id: appSplitTunnelingSwitch - visible: isAppSplitTinnelingEnabled - - Layout.fillWidth: true - - text: qsTr("App-based split tunneling") - descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - - clickedFunction: function() { - PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling) - root.closeTriggered() - } - } - - DividerType { - visible: isAppSplitTinnelingEnabled - } - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +DrawerType2 { + id: root + + property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android" + + anchors.fill: parent + expandedHeight: parent.height * 0.9 + + expandedStateContent: ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + spacing: 0 + + Header2Type { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + + headerText: qsTr("Split tunneling") + descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others") + } + + LabelWithButtonType { + id: splitTunnelingSwitch + Layout.fillWidth: true + Layout.topMargin: 16 + + visible: ServersUiController.isDefaultServerDefaultContainerHasSplitTunneling + + text: qsTr("Split tunneling on the server") + descriptionText: qsTr("Enabled \nCan't be disabled for current server") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsSplitTunneling) + root.closeTriggered() + } + } + + DividerType { + visible: ServersUiController.isDefaultServerDefaultContainerHasSplitTunneling + } + + LabelWithButtonType { + id: siteBasedSplitTunnelingSwitch + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Site-based split tunneling") + descriptionText: enabled && IpSplitTunnelingController.isSplitTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsSplitTunneling) + root.closeTriggered() + } + } + + DividerType { + } + + LabelWithButtonType { + id: appSplitTunnelingSwitch + visible: isAppSplitTinnelingEnabled + + Layout.fillWidth: true + + text: qsTr("App-based split tunneling") + descriptionText: AppSplitTunnelingController.isSplitTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling) + root.closeTriggered() + } + } + + DividerType { + visible: isAppSplitTinnelingEnabled + } + } +} diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index bb54ed07e..05cf2b90c 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -32,7 +32,7 @@ DrawerType2 { spacing: 8 onImplicitHeightChanged: { - root.expandedHeight = content.implicitHeight + 32 + SettingsController.safeAreaBottomMargin + root.expandedHeight = content.implicitHeight + 32 + PageController.safeAreaBottomMargin } Header2TextType { diff --git a/client/ui/qml/Components/RenameServerDrawer.qml b/client/ui/qml/Components/RenameServerDrawer.qml index d65b9bbaa..a067835e5 100644 --- a/client/ui/qml/Components/RenameServerDrawer.qml +++ b/client/ui/qml/Components/RenameServerDrawer.qml @@ -46,7 +46,7 @@ DrawerType2 { } if (serverName.textField.text !== root.serverNameText) { - ServersModel.setProcessedServerData("name", serverName.textField.text); + ServersUiController.editServerName(ServersUiController.processedIndex, serverName.textField.text); } root.closeTriggered() } diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index 09829fa70..f24002775 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -57,7 +57,7 @@ DrawerType2 { anchors.right: parent.right anchors.bottom: parent.bottom - property int selectedIndex: LanguageModel.currentLanguageIndex + property int selectedIndex: LanguageUiController.currentLanguageIndex model: LanguageModel @@ -158,7 +158,7 @@ DrawerType2 { onClicked: { listView.selectedIndex = index - LanguageModel.changeLanguage(languageIndex) + LanguageUiController.changeLanguage(languageIndex) root.closeTriggered() } } diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index 0cc5b0913..b96dfb1d2 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -5,7 +5,6 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import ContainerProps 1.0 import ContainersModelFilters 1.0 import Style 1.0 @@ -18,7 +17,7 @@ import "../Config" ListViewType { id: root - property int selectedIndex: ServersModel.defaultIndex + property int selectedIndex: ServersUiController.defaultIndex anchors.top: serversMenuHeader.bottom anchors.right: parent.right @@ -29,7 +28,7 @@ ListViewType { model: ServersModel Connections { - target: ServersModel + target: ServersUiController function onDefaultServerIndexChanged(serverIndex) { root.selectedIndex = serverIndex } @@ -87,7 +86,7 @@ ListViewType { root.selectedIndex = index - ServersModel.defaultIndex = index + ServersUiController.setDefaultServerIndex(index) } Keys.onEnterPressed: serverRadioButton.clicked() @@ -107,14 +106,14 @@ ListViewType { z: 1 onClicked: function() { - ServersModel.processedIndex = index + ServersUiController.processedIndex = index if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) } else { PageController.showBusyIndicator(true) - let result = ApiSettingsController.getAccountInfo(false) + let result = SubscriptionUiController.getAccountInfo(ServersUiController.getProcessedServerIndex(), false) PageController.showBusyIndicator(false) if (!result) { return diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 73c0bdb4d..c5dd130e1 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -5,8 +5,6 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 -import ContainerEnum 1.0 import ContainerProps 1.0 import "../Controls2" @@ -31,33 +29,30 @@ ListViewType { clickedFunction: function() { if (isInstalled) { var containerIndex = root.model.mapToSource(index) - ContainersModel.setProcessedContainerIndex(containerIndex) + ServersUiController.processedContainerIndex = containerIndex - if (serviceType !== ProtocolEnum.Other && isThirdPartyConfig) { - ProtocolsModel.updateModel(config) - PageController.goToPage(PageEnum.PageProtocolRaw) - return + if (isVpnContainer) { + // var isThirdPartyConfig = root.model.data(index, ContainersModel.IsThirdPartyConfigRole) + if (isThirdPartyConfig) { + InstallController.updateProtocols(ServersUiController.processedIndex, containerIndex) + PageController.goToPage(PageEnum.PageProtocolRaw) + return + } } - switch (containerIndex) { - case ContainerEnum.Ipsec: { - ProtocolsModel.updateModel(config) + if (isIpsec) { + InstallController.updateProtocols(ServersUiController.processedIndex, containerIndex) PageController.goToPage(PageEnum.PageProtocolRaw) - break - } - case ContainerEnum.Dns: { + } else if (isDns) { PageController.goToPage(PageEnum.PageServiceDnsSettings) - break - } - default: { - ProtocolsModel.updateModel(config) + } else { + InstallController.updateProtocols(ServersUiController.processedIndex, containerIndex) PageController.goToPage(PageEnum.PageSettingsServerProtocol) } - } } else { - ContainersModel.setProcessedContainerIndex(root.model.mapToSource(index)) - InstallController.setShouldCreateServer(false) + var containerIndex = root.model.mapToSource(index) + ServersUiController.processedContainerIndex = containerIndex PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Components/SmartScroll.qml b/client/ui/qml/Components/SmartScroll.qml index 374f7f4aa..70ba7f90b 100644 --- a/client/ui/qml/Components/SmartScroll.qml +++ b/client/ui/qml/Components/SmartScroll.qml @@ -8,9 +8,9 @@ QtObject { property var scrollToItemTarget: null property Connections imeConnection: Connections { - target: SettingsController + target: PageController function onImeHeightChanged() { - if (root.scrollToItemTarget && SettingsController.imeHeight > 0) { + if (root.scrollToItemTarget && PageController.imeHeight > 0) { scrollTimer.restart() } } @@ -21,11 +21,11 @@ QtObject { repeat: false onTriggered: { if (root.scrollToItemTarget && root.listView) { - if (SettingsController.imeHeight > 0) { + if (PageController.imeHeight > 0) { var item = root.scrollToItemTarget var itemY = item.mapToItem(root.listView.contentItem, 0, 0).y var itemHeight = item.height - var keyboardHeight = SettingsController.imeHeight + SettingsController.safeAreaBottomMargin + var keyboardHeight = PageController.imeHeight + PageController.safeAreaBottomMargin var visibleHeight = root.listView.height - keyboardHeight var desiredTopOffset = visibleHeight * 0.25 diff --git a/client/ui/qml/Components/SubscriptionExpiredDrawer.qml b/client/ui/qml/Components/SubscriptionExpiredDrawer.qml index 0d29cf75e..d9b6b29c8 100644 --- a/client/ui/qml/Components/SubscriptionExpiredDrawer.qml +++ b/client/ui/qml/Components/SubscriptionExpiredDrawer.qml @@ -76,7 +76,7 @@ DrawerType2 { textColor: AmneziaStyle.color.midnightBlack clickedFunc: function() { - ApiSettingsController.getRenewalLink() + SubscriptionUiController.getRenewalLink(ServersUiController.getProcessedServerIndex()) } } diff --git a/client/ui/qml/Components/SubscriptionPlanCard.qml b/client/ui/qml/Components/SubscriptionPlanCard.qml index f69ece34c..7f31a2776 100644 --- a/client/ui/qml/Components/SubscriptionPlanCard.qml +++ b/client/ui/qml/Components/SubscriptionPlanCard.qml @@ -26,9 +26,10 @@ Rectangle { ColumnLayout { id: cardLayout + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter + anchors.topMargin: 14 anchors.leftMargin: 16 anchors.rightMargin: 16 diff --git a/client/ui/qml/Controls2/BaseHeaderType.qml b/client/ui/qml/Controls2/BaseHeaderType.qml index eb7fe36f5..f46a925a3 100644 --- a/client/ui/qml/Controls2/BaseHeaderType.qml +++ b/client/ui/qml/Controls2/BaseHeaderType.qml @@ -19,7 +19,9 @@ Item { ColumnLayout { id: content - anchors.fill: parent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right RowLayout { id: headerRow diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index 38e99eb8d..b52e0fc58 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -15,7 +15,7 @@ Popup { leftMargin: 25 rightMargin: 25 - bottomMargin: 70 + SettingsController.safeAreaBottomMargin + bottomMargin: 70 + PageController.safeAreaBottomMargin width: parent.width - leftMargin - rightMargin @@ -72,7 +72,7 @@ Popup { Layout.fillWidth: true onLinkActivated: function(link) { - Qt.openUrlExternally(LanguageModel.getCurrentDocsUrl(link)) + Qt.openUrlExternally(LanguageUiController.getCurrentDocsUrl(link)) } text: root.text diff --git a/client/ui/qml/Controls2/TextTypes/BadgeTextType.qml b/client/ui/qml/Controls2/TextTypes/BadgeTextType.qml index 86327f3d0..da3d095c7 100644 --- a/client/ui/qml/Controls2/TextTypes/BadgeTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/BadgeTextType.qml @@ -3,7 +3,7 @@ import QtQuick import Style 1.0 Text { - lineHeight: 10 + LanguageModel.getLineHeightAppend() + lineHeight: 10 + LanguageUiController.getLineHeightAppend() lineHeightMode: Text.FixedHeight color: AmneziaStyle.color.midnightBlack diff --git a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml index ba5112893..390f6b567 100644 --- a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml @@ -3,7 +3,7 @@ import QtQuick import Style 1.0 Text { - lineHeight: 16 + LanguageModel.getLineHeightAppend() + lineHeight: 16 + LanguageUiController.getLineHeightAppend() lineHeightMode: Text.FixedHeight color: AmneziaStyle.color.midnightBlack diff --git a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml index 40a0b35ee..12e134359 100644 --- a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml @@ -3,7 +3,7 @@ import QtQuick import Style 1.0 Text { - lineHeight: 38 + LanguageModel.getLineHeightAppend() + lineHeight: 38 + LanguageUiController.getLineHeightAppend() lineHeightMode: Text.FixedHeight color: AmneziaStyle.color.paleGray diff --git a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml index 74412cfd1..0fffcfc58 100644 --- a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml @@ -3,7 +3,7 @@ import QtQuick import Style 1.0 Text { - lineHeight: 30 + LanguageModel.getLineHeightAppend() + lineHeight: 30 + LanguageUiController.getLineHeightAppend() lineHeightMode: Text.FixedHeight color: AmneziaStyle.color.paleGray diff --git a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml index 9a9a1963a..04b5c3175 100644 --- a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml @@ -3,7 +3,7 @@ import QtQuick import Style 1.0 Text { - lineHeight: 16 + LanguageModel.getLineHeightAppend() + lineHeight: 16 + LanguageUiController.getLineHeightAppend() lineHeightMode: Text.FixedHeight color: AmneziaStyle.color.mutedGray diff --git a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml index 40cd7835f..71ea72df0 100644 --- a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml +++ b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml @@ -3,7 +3,7 @@ import QtQuick import Style 1.0 Text { - lineHeight: 21.6 + LanguageModel.getLineHeightAppend() + lineHeight: 21.6 + LanguageUiController.getLineHeightAppend() lineHeightMode: Text.FixedHeight color: AmneziaStyle.color.paleGray diff --git a/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml index 109b28760..bcda8905e 100644 --- a/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml @@ -2,7 +2,7 @@ import QtQuick import Style 1.0 Text { - lineHeight: 24 + LanguageModel.getLineHeightAppend() + lineHeight: 24 + LanguageUiController.getLineHeightAppend() lineHeightMode: Text.FixedHeight color: AmneziaStyle.color.paleGray diff --git a/client/ui/qml/Controls2/TextTypes/SmallTextType.qml b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml index 6c28607a9..4af7dad35 100644 --- a/client/ui/qml/Controls2/TextTypes/SmallTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml @@ -3,7 +3,7 @@ import QtQuick import Style 1.0 Text { - lineHeight: 20 + LanguageModel.getLineHeightAppend() + lineHeight: 20 + LanguageUiController.getLineHeightAppend() lineHeightMode: Text.FixedHeight color: AmneziaStyle.color.paleGray diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml index 3a652da6c..f48e931b7 100644 --- a/client/ui/qml/Controls2/TopCloseButtonType.qml +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -24,7 +24,7 @@ Popup { ImageButtonType { id: button - image: "qrc:/images/svg/close_black_24dp.svg" + image: "qrc:/images/controls/close.svg" imageColor: AmneziaStyle.color.paleGray implicitWidth: 40 diff --git a/client/ui/qml/Filters/ContainersModelFilters.qml b/client/ui/qml/Filters/ContainersModelFilters.qml index ed537fdf1..94d293fb5 100644 --- a/client/ui/qml/Filters/ContainersModelFilters.qml +++ b/client/ui/qml/Filters/ContainersModelFilters.qml @@ -4,19 +4,17 @@ import QtQuick 2.15 import SortFilterProxyModel 0.2 -import ProtocolEnum 1.0 - Item { ValueFilter { id: vpnTypeFilter - roleName: "serviceType" - value: ProtocolEnum.Vpn + roleName: "isVpnContainer" + value: true } ValueFilter { id: serviceTypeFilter - roleName: "serviceType" - value: ProtocolEnum.Other + roleName: "isServiceContainer" + value: true } ValueFilter { diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index 24f497bd3..b4af9482e 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -45,7 +45,7 @@ PageType { BaseHeaderType { Layout.fillWidth: true - Layout.topMargin: 20 + SettingsController.safeAreaTopMargin + Layout.topMargin: 20 + PageController.safeAreaTopMargin Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageDevMenu.qml b/client/ui/qml/Pages2/PageDevMenu.qml index c55c04b1e..b6b029939 100644 --- a/client/ui/qml/Pages2/PageDevMenu.qml +++ b/client/ui/qml/Pages2/PageDevMenu.qml @@ -20,7 +20,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin } ListViewType { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 764f3d13f..25794211c 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -6,7 +6,6 @@ import Qt5Compat.GraphicalEffects import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import ContainerProps 1.0 import ContainersModelFilters 1.0 import Style 1.0 @@ -20,6 +19,8 @@ import "../Components" PageType { id: root + property var containersDropDownRef: null + Connections { target: Qt.application @@ -42,8 +43,8 @@ PageType { function onRestorePageHomeState(isContainerInstalled) { drawer.openTriggered() - if (isContainerInstalled) { - containersDropDown.rootButtonClickedFunction() + if (isContainerInstalled && root.containersDropDownRef) { + root.containersDropDownRef.rootButtonClickedFunction() } } } @@ -59,7 +60,7 @@ PageType { objectName: "homeColumnLayout" anchors.fill: parent - anchors.topMargin: 12 + SettingsController.safeAreaTopMargin + anchors.topMargin: 12 + PageController.safeAreaTopMargin anchors.bottomMargin: 16 BasicButtonType { @@ -147,8 +148,8 @@ PageType { buttonTextLabel.font.pixelSize: 14 buttonTextLabel.font.weight: 500 - property bool isSplitTunnelingEnabled: SitesModel.isTunnelingEnabled || AppSplitTunnelingModel.isTunnelingEnabled || - ServersModel.isDefaultServerDefaultContainerHasSplitTunneling + property bool isSplitTunnelingEnabled: IpSplitTunnelingController.isSplitTunnelingEnabled || AppSplitTunnelingController.isSplitTunnelingEnabled || + ServersUiController.isDefaultServerDefaultContainerHasSplitTunneling text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled") @@ -268,7 +269,7 @@ PageType { maximumLineCount: 2 elide: Qt.ElideRight - text: ServersModel.defaultServerName + text: ServersUiController.defaultServerName horizontalAlignment: Qt.AlignHCenter Behavior on opacity { @@ -310,11 +311,11 @@ PageType { objectName: "rowLayoutLabel" Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.topMargin: 8 - Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16 + Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : ServersUiController.isDefaultServerFromApi ? 61 : 16 spacing: 0 BasicButtonType { - enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsedStateActive + enabled: (ServersUiController.defaultServerImagePathCollapsed !== "") && drawer.isCollapsedStateActive hoverEnabled: enabled implicitHeight: 36 @@ -332,8 +333,8 @@ PageType { buttonTextLabel.font.pixelSize: 13 buttonTextLabel.font.weight: 400 - text: drawer.isCollapsedStateActive ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded - leftImageSource: ServersModel.defaultServerImagePathCollapsed + text: drawer.isCollapsedStateActive ? ServersUiController.defaultServerDescriptionCollapsed : ServersUiController.defaultServerDescriptionExpanded + leftImageSource: ServersUiController.defaultServerImagePathCollapsed leftImageColor: "" changeLeftImageSize: false @@ -343,14 +344,14 @@ PageType { Keys.onReturnPressed: this.clicked() onClicked: { - ServersModel.processedIndex = ServersModel.defaultIndex + ServersUiController.processedIndex = ServersUiController.defaultIndex if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) { PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries) } else { PageController.showBusyIndicator(true) - let result = ApiSettingsController.getAccountInfo(false) + let result = SubscriptionUiController.getAccountInfo(ServersUiController.getProcessedServerIndex(), false) PageController.showBusyIndicator(false) if (!result) { return @@ -378,12 +379,14 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 8 - visible: !ServersModel.isDefaultServerFromApi + visible: !ServersUiController.isDefaultServerFromApi DropDownType { id: containersDropDown objectName: "containersDropDown" + Component.onCompleted: root.containersDropDownRef = containersDropDown + rootButtonImageColor: AmneziaStyle.color.midnightBlack rootButtonBackgroundColor: AmneziaStyle.color.paleGray rootButtonBackgroundHoveredColor: AmneziaStyle.color.mistyGray @@ -395,7 +398,7 @@ PageType { enabled: drawer.isOpened - text: ServersModel.defaultServerDefaultContainerName + text: ServersUiController.defaultServerDefaultContainerName textColor: AmneziaStyle.color.midnightBlack headerText: qsTr("VPN protocol") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" @@ -415,7 +418,7 @@ PageType { Connections { objectName: "rowLayoutConnections" - target: ServersModel + target: ServersUiController function onDefaultServerIndexChanged() { updateContainersModelFilters() diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml index bd0e5a450..e5ae84aae 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 +import ProtocolEnum 1.0 import "./" import "../Controls2" @@ -22,7 +23,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { @@ -434,13 +435,13 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) { PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) return } PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(AwgConfigModel.getConfig()) + InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Awg) } var noButtonFunction = function() {} diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index f725b4b11..c8c8ab0ed 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -7,6 +7,7 @@ import QtCore import SortFilterProxyModel 0.2 import PageEnum 1.0 +import ProtocolEnum 1.0 import Style 1.0 import "./" @@ -25,7 +26,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { @@ -55,7 +56,7 @@ PageType { width: listView.width property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField - property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() + property bool isEnabled: ServersUiController.isProcessedServerHasWriteAccess() spacing: 0 @@ -554,13 +555,13 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) { PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) return } PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(AwgConfigModel.getConfig()) + InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Awg) } var noButtonFunction = function() {} diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml deleted file mode 100644 index 48b59c87d..000000000 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ /dev/null @@ -1,218 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import SortFilterProxyModel 0.2 - -import PageEnum 1.0 -import Style 1.0 - -import "./" -import "../Controls2" -import "../Controls2/TextTypes" -import "../Config" -import "../Components" - -PageType { - id: root - - BackButtonType { - id: backButton - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin - - onActiveFocusChanged: { - if(backButton.enabled && backButton.activeFocus) { - listView.positionViewAtBeginning() - } - } - } - - ListViewType { - id: listView - - anchors.top: backButton.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - - property int selectedIndex: 0 - - enabled: ServersModel.isProcessedServerHasWriteAccess() - - header: ColumnLayout { - width: listView.width - - BaseHeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: qsTr("Cloak settings") - } - } - - model: CloakConfigModel - - delegate: ColumnLayout { - width: listView.width - - property alias trafficFromField: trafficFromField - - spacing: 0 - - TextFieldWithHeaderType { - id: trafficFromField - - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: qsTr("Disguised as traffic from") - textField.text: site - - textField.onEditingFinished: { - if (textField.text !== site) { - var tmpText = textField.text - tmpText = tmpText.toLocaleLowerCase() - - var indexHttps = tmpText.indexOf("https://") - if (indexHttps === 0) { - tmpText = textField.text.substring(8) - } else { - site = textField.text - } - } - } - - checkEmptyText: true - } - - TextFieldWithHeaderType { - id: portTextField - - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: qsTr("Port") - textField.text: port - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } - - textField.onEditingFinished: { - if (textField.text !== port) { - port = textField.text - } - } - - checkEmptyText: true - } - - DropDownType { - id: cipherDropDown - - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - descriptionText: qsTr("Cipher") - headerText: qsTr("Cipher") - - drawerParent: root - - listView: ListViewWithRadioButtonType { - id: cipherListView - - rootWidth: root.width - - model: ListModel { - ListElement { name : "chacha20-ietf-poly1305" } - ListElement { name : "xchacha20-ietf-poly1305" } - ListElement { name : "aes-256-gcm" } - ListElement { name : "aes-192-gcm" } - ListElement { name : "aes-128-gcm" } - } - - function updateSelectedIndex() { - cipherDropDown.text = cipher - for (var i = 0; i < cipherListView.model.count; i++) { - if (cipherListView.model.get(i).name === cipher) { - selectedIndex = i - break - } - } - } - - clickedFunction: function() { - cipherDropDown.text = selectedText - cipher = cipherDropDown.text - cipherDropDown.closeTriggered() - } - - Component.onCompleted: { - updateSelectedIndex() - } - } - - Connections { - target: listView.model - function onDataChanged() { - cipherListView.updateSelectedIndex() - } - } - } - - BasicButtonType { - id: saveButton - - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - enabled: trafficFromField.errorText === "" && - portTextField.errorText === "" - - text: qsTr("Save") - - clickedFunc: function() { - forceActiveFocus() - - var headerText = qsTr("Save settings?") - var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } - - PageController.goToPage(PageEnum.PageSetupWizardInstalling) - InstallController.updateContainer(CloakConfigModel.getConfig()) - } - - var noButtonFunction = function() { - if (!GC.isMobile()) { - saveButton.forceActiveFocus() - } - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - - Keys.onEnterPressed: saveButton.clicked() - Keys.onReturnPressed: saveButton.clicked() - } - } - } -} diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index a11ed09ba..1e00ac8fb 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -5,7 +5,7 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ContainerEnum 1.0 +import ProtocolEnum 1.0 import Style 1.0 import "./" @@ -23,7 +23,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { @@ -40,7 +40,7 @@ PageType { anchors.right: parent.right anchors.left: parent.left - enabled: ServersModel.isProcessedServerHasWriteAccess() + enabled: ServersUiController.isProcessedServerHasWriteAccess() header: ColumnLayout { width: listView.width @@ -428,13 +428,13 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) { PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) return } PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(OpenVpnConfigModel.getConfig()) + InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn) } var noButtonFunction = function() { if (!GC.isMobile()) { diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 4714772ff..57dc7d9fc 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -5,8 +5,6 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 -import ContainerEnum 1.0 import ContainerProps 1.0 import Style 1.0 @@ -25,7 +23,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { @@ -173,7 +171,7 @@ PageType { width: parent.width - visible: ServersModel.isProcessedServerHasWriteAccess() + visible: ServersUiController.isProcessedServerHasWriteAccess() text: qsTr("Remove ") + ContainersModel.getProcessedContainerName() textColor: AmneziaStyle.color.vibrantRed @@ -186,7 +184,7 @@ PageType { var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeProcessedContainer() + InstallController.removeContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex) } var noButtonFunction = function() {} diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml deleted file mode 100644 index 7d20d044a..000000000 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ /dev/null @@ -1,184 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import SortFilterProxyModel 0.2 - -import PageEnum 1.0 -import Style 1.0 - -import "./" -import "../Controls2" -import "../Controls2/TextTypes" -import "../Config" -import "../Components" - -PageType { - id: root - - BackButtonType { - id: backButton - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin - - onFocusChanged: { - if (this.activeFocus) { - listView.positionViewAtBeginning() - } - } - } - - ListViewType { - id: listView - - anchors.top: backButton.bottom - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - - enabled: ServersModel.isProcessedServerHasWriteAccess() - - model: ShadowSocksConfigModel - - delegate: ColumnLayout { - width: listView.width - - spacing: 0 - - BaseHeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: qsTr("Shadowsocks settings") - } - - TextFieldWithHeaderType { - id: portTextField - - Layout.fillWidth: true - Layout.topMargin: 40 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - enabled: listView.enabled - - headerText: qsTr("Port") - textField.text: port - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } - - textField.onEditingFinished: { - if (textField.text !== port) { - port = textField.text - } - } - - checkEmptyText: true - } - - DropDownType { - id: cipherDropDown - - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - enabled: listView.enabled - - descriptionText: qsTr("Cipher") - headerText: qsTr("Cipher") - - drawerParent: root - - listView: ListViewWithRadioButtonType { - - id: cipherListView - - rootWidth: root.width - - model: ListModel { - ListElement { name : "chacha20-ietf-poly1305" } - ListElement { name : "xchacha20-ietf-poly1305" } - ListElement { name : "aes-256-gcm" } - ListElement { name : "aes-192-gcm" } - ListElement { name : "aes-128-gcm" } - } - - function updateSelectedIndex() { - cipherDropDown.text = cipher - for (var i = 0; i < cipherListView.model.count; i++) { - if (cipherListView.model.get(i).name === cipher) { - selectedIndex = i - break - } - } - } - - clickedFunction: function() { - cipherDropDown.text = selectedText - cipher = cipherDropDown.text - cipherDropDown.closeTriggered() - } - - Component.onCompleted: { - updateSelectedIndex() - } - } - - Connections { - target: listView.model - function onDataChanged() { - cipherListView.updateSelectedIndex() - } - } - } - - BasicButtonType { - id: saveButton - - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - enabled: portTextField.errorText === "" - - text: qsTr("Save") - - clickedFunc: function() { - forceActiveFocus() - - var headerText = qsTr("Save settings?") - var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } - - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - saveButton.forceActiveFocus() - } - } - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - - Keys.onEnterPressed: saveButton.clicked() - Keys.onReturnPressed: saveButton.clicked() - } - } - } -} diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml index a704bdcc9..f572d9c5c 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 +import ProtocolEnum 1.0 import "./" import "../Controls2" @@ -22,7 +23,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { @@ -122,13 +123,13 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) { PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) return } PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(WireGuardConfigModel.getConfig()) + InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard) } var noButtonFunction = function() {} showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml index ac21c229e..899def535 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 +import ProtocolEnum 1.0 import Style 1.0 import "./" @@ -22,7 +23,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { @@ -39,14 +40,14 @@ PageType { anchors.right: parent.right anchors.left: parent.left - enabled: ServersModel.isProcessedServerHasWriteAccess() + enabled: ServersUiController.isProcessedServerHasWriteAccess() model: WireGuardConfigModel delegate: ColumnLayout { width: listView.width - property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() + property bool isEnabled: ServersUiController.isProcessedServerHasWriteAccess() spacing: 0 @@ -122,13 +123,13 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) { PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) return } PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(WireGuardConfigModel.getConfig()) + InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard) } var noButtonFunction = function() { if (!GC.isMobile()) { diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index d316db6d2..552ec3794 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -5,7 +5,7 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ContainerEnum 1.0 +import ProtocolEnum 1.0 import Style 1.0 import "./" @@ -23,7 +23,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { @@ -40,7 +40,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - enabled: ServersModel.isProcessedServerHasWriteAccess() + enabled: ServersUiController.isProcessedServerHasWriteAccess() model: XrayConfigModel delegate: ColumnLayout { @@ -133,13 +133,13 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) { PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) return } PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(XrayConfigModel.getConfig()) + InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray) } var noButtonFunction = function() { if (!GC.isMobile()) { diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index afef9053c..6a2f6a07d 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -22,7 +22,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { @@ -60,6 +60,7 @@ PageType { width: listView.width LabelWithButtonType { + id: removeButton Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 @@ -73,12 +74,12 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected + if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected && SettingsController.isAmneziaDnsEnabled()) { PageController.showNotificationMessage(qsTr("Cannot remove AmneziaDNS from running server")) } else { PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeProcessedContainer() + InstallController.removeContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex) } } var noButtonFunction = function() {} diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 67e384aa1..c47f84672 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -30,7 +30,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { @@ -47,7 +47,7 @@ PageType { anchors.right: parent.right anchors.left: parent.left - enabled: ServersModel.isProcessedServerHasWriteAccess() + enabled: ServersUiController.isProcessedServerHasWriteAccess() model: SftpConfigModel @@ -173,7 +173,7 @@ PageType { clickedFunc: function() { PageController.showBusyIndicator(true) - InstallController.mountSftpDrive(port, password, username) + InstallController.mountSftpDrive(ServersUiController.processedIndex, port, password, username) PageController.showBusyIndicator(false) } } diff --git a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml index 0a5c8ab29..b52d0b69e 100644 --- a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml +++ b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 +import ProtocolEnum 1.0 import ContainerProps 1.0 import Style 1.0 @@ -31,7 +32,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { @@ -284,7 +285,7 @@ PageType { } PageController.goToPage(PageEnum.PageSetupWizardInstalling) - InstallController.updateContainer(Socks5ProxyConfigModel.getConfig()) + InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy) tempPort = portTextField.textField.text tempUsername = usernameTextField.textField.text tempPassword = passwordTextField.textField.text diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 5e6cae933..02c290b6a 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -31,7 +31,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { @@ -60,7 +60,7 @@ PageType { } } - model: 1 // fake model to force the ListView to be created without a model + model: TorConfigModel delegate: ColumnLayout { // TODO(CyAn84): add DelegateChooser after migrate to 6.9 width: listView.width @@ -73,11 +73,7 @@ PageType { Layout.bottomMargin: 24 text: qsTr("Website address") - descriptionText: { - var containerIndex = ContainersModel.getProcessedContainerIndex() - var config = ContainersModel.getContainerConfig(containerIndex) - return config[ContainerProps.containerTypeToString(containerIndex)]["site"] - } + descriptionText: site || "" descriptionOnTop: true textColor: AmneziaStyle.color.goldenApricot diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 456b622ae..c2fa14282 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -40,7 +40,7 @@ PageType { BaseHeaderType { id: header Layout.fillWidth: true - Layout.topMargin: 24 + SettingsController.safeAreaTopMargin + Layout.topMargin: 24 + PageController.safeAreaTopMargin Layout.bottomMargin: 16 Layout.rightMargin: 16 Layout.leftMargin: 16 @@ -149,9 +149,9 @@ PageType { property string title: qsTr("News & Notifications") readonly property string leftImagePath: NewsModel.hasUnread && SettingsController.isNewsNotificationsEnabled() ? "qrc:/images/controls/news-unread.svg" : "qrc:/images/controls/news.svg" - property bool isVisible: ServersModel.hasServersFromGatewayApi + property bool isVisible: ServersUiController.hasServersFromGatewayApi readonly property var clickedHandler: function() { - if (!ServersModel.hasServersFromGatewayApi) { + if (!ServersUiController.hasServersFromGatewayApi) { return; } PageController.showBusyIndicator(true) diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 0f0374e66..0054d63bf 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -20,7 +20,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { @@ -170,7 +170,7 @@ PageType { text: qsTr("Privacy Policy") clickedFunc: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl("policy")) + Qt.openUrlExternally(LanguageUiController.getCurrentSiteUrl("policy")) } } } @@ -223,7 +223,7 @@ PageType { readonly property string description: qsTr("Visit official website") readonly property string imageSource: "qrc:/images/controls/amnezia.svg" readonly property var handler: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) + Qt.openUrlExternally(LanguageUiController.getCurrentSiteUrl()) } } } diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index ca0976188..e54508096 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -90,7 +90,7 @@ PageType { id: backButton objectName: "backButton" - Layout.topMargin: 20 + SettingsController.safeAreaTopMargin + Layout.topMargin: 20 + PageController.safeAreaTopMargin } HeaderTypeWithButton { @@ -108,7 +108,7 @@ PageType { actionButtonFunction: function() { PageController.showBusyIndicator(true) - let result = ApiSettingsController.getAccountInfo(false) + let result = SubscriptionUiController.getAccountInfo(ServersUiController.getProcessedServerIndex(), false) PageController.showBusyIndicator(false) if (!result) { return @@ -148,7 +148,7 @@ PageType { text: qsTr("Renew subscription") clickedFunc: function() { - ApiSettingsController.getRenewalLink() + SubscriptionUiController.getRenewalLink(ServersUiController.getProcessedServerIndex()) } } @@ -200,7 +200,7 @@ PageType { PageController.showBusyIndicator(true) var prevIndex = ApiCountryModel.currentIndex ApiCountryModel.currentIndex = index - if (!ApiConfigsController.updateServiceFromGateway(ServersModel.defaultIndex, countryCode, countryName)) { + if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.getProcessedServerIndex(), countryCode, countryName)) { ApiCountryModel.currentIndex = prevIndex } PageController.showBusyIndicator(false) diff --git a/client/ui/qml/Pages2/PageSettingsApiDevices.qml b/client/ui/qml/Pages2/PageSettingsApiDevices.qml index cb68ad89f..6dd7b59d7 100644 --- a/client/ui/qml/Pages2/PageSettingsApiDevices.qml +++ b/client/ui/qml/Pages2/PageSettingsApiDevices.qml @@ -23,7 +23,7 @@ PageType { id: listView anchors.fill: parent - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin anchors.bottomMargin: 24 model: ApiDevicesModel @@ -71,7 +71,7 @@ PageType { rightImageSource: "qrc:/images/controls/trash.svg" clickedFunction: function() { - if (isCurrentDevice && ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + if (isCurrentDevice && ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection")) return } @@ -82,7 +82,8 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - Qt.callLater(deactivateExternalDevice, supportTag, countryCode) + var serverIndex = ServersUiController.getProcessedServerIndex() + Qt.callLater(deactivateExternalDevice, serverIndex, supportTag, countryCode) } var noButtonFunction = function() { } @@ -95,10 +96,10 @@ PageType { } } - function deactivateExternalDevice(supportTag, countryCode) { + function deactivateExternalDevice(serverIndex, supportTag, countryCode) { PageController.showBusyIndicator(true) - if (ApiConfigsController.deactivateExternalDevice(supportTag, countryCode)) { - ApiSettingsController.getAccountInfo(true) + if (SubscriptionUiController.deactivateExternalDevice(serverIndex, supportTag, countryCode)) { + SubscriptionUiController.getAccountInfo(serverIndex, true) } PageController.showBusyIndicator(false) } diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml index f474e7c0b..611b6898e 100644 --- a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -79,7 +79,7 @@ PageType { id: listView anchors.fill: parent - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin anchors.bottomMargin: 24 model: instructionsModel @@ -114,7 +114,7 @@ PageType { rightImageSource: "qrc:/images/controls/external-link.svg" clickedFunction: function() { - Qt.openUrlExternally(LanguageModel.getCurrentDocsUrl(link)) + Qt.openUrlExternally(LanguageUiController.getCurrentDocsUrl(link)) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml index 95aa969e3..b55e9f46c 100644 --- a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -28,7 +28,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { @@ -191,9 +191,9 @@ PageType { } if (fileName !== "") { PageController.showBusyIndicator(true) - let result = ApiConfigsController.exportNativeConfig(countryCode, fileName) + let result = SubscriptionUiController.exportNativeConfig(ServersUiController.getProcessedServerIndex(), countryCode, fileName) if (result) { - ApiSettingsController.getAccountInfo(true) + SubscriptionUiController.getAccountInfo(ServersUiController.getProcessedServerIndex(), true) } PageController.showBusyIndicator(false) @@ -205,9 +205,9 @@ PageType { function revokeConfig(countryCode) { PageController.showBusyIndicator(true) - let result = ApiConfigsController.revokeNativeConfig(countryCode) + let result = SubscriptionUiController.revokeNativeConfig(ServersUiController.getProcessedServerIndex(), countryCode) if (result) { - ApiSettingsController.getAccountInfo(true) + SubscriptionUiController.getAccountInfo(ServersUiController.getProcessedServerIndex(), true) } PageController.showBusyIndicator(false) diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index a8a70493d..143352fb3 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -117,7 +117,7 @@ PageType { id: backButton objectName: "backButton" - Layout.topMargin: 20 + SettingsController.safeAreaTopMargin + Layout.topMargin: 20 + PageController.safeAreaTopMargin } HeaderTypeWithButton { @@ -186,7 +186,7 @@ PageType { textColor: AmneziaStyle.color.midnightBlack clickedFunc: function() { - ApiSettingsController.getRenewalLink() + SubscriptionUiController.getRenewalLink(ServersUiController.getProcessedServerIndex()) } } } @@ -246,7 +246,7 @@ PageType { text: qsTr("Renew subscription") clickedFunc: function() { - ApiSettingsController.getRenewalLink() + SubscriptionUiController.getRenewalLink(ServersUiController.getProcessedServerIndex()) } } @@ -258,7 +258,7 @@ PageType { SwitcherType { id: switcher - readonly property bool isVlessProtocol: ApiConfigsController.isVlessProtocol() + readonly property bool isVlessProtocol: SubscriptionUiController.isVlessProtocol(ServersUiController.getProcessedServerIndex()) readonly property bool isProtocolSwitchBlocked: ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected Layout.fillWidth: true @@ -272,12 +272,12 @@ PageType { text: qsTr("Use VLESS protocol") checked: switcher.isVlessProtocol onToggled: function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection")) } else { PageController.showBusyIndicator(true) - ApiConfigsController.setCurrentProtocol(switcher.isVlessProtocol ? "awg" : "vless") - ApiConfigsController.updateServiceFromGateway(ServersModel.processedIndex, "", "", true) + SubscriptionUiController.setCurrentProtocol(ServersUiController.getProcessedServerIndex(), switcher.isVlessProtocol ? "awg" : "vless") + SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedIndex, "", "", true) PageController.showBusyIndicator(false) } } @@ -325,7 +325,7 @@ PageType { PageController.goToPage(PageEnum.PageSettingsApiSubscriptionKey) PageController.showBusyIndicator(true) - ApiConfigsController.prepareVpnKeyExport() + SubscriptionUiController.prepareVpnKeyExport(ServersUiController.getProcessedServerIndex()) PageController.showBusyIndicator(false) } @@ -346,7 +346,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - ApiSettingsController.updateApiCountryModel() + SubscriptionUiController.updateApiCountryModel() PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs) } } @@ -366,7 +366,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - ApiSettingsController.updateApiDevicesModel() + SubscriptionUiController.updateApiDevicesModel() PageController.goToPage(PageEnum.PageSettingsApiDevices) } } @@ -427,11 +427,11 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { PageController.showNotificationMessage(qsTr("Cannot reload API config during active connection")) } else { PageController.showBusyIndicator(true) - ApiConfigsController.updateServiceFromGateway(ServersModel.processedIndex, "", "", true) + SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedIndex, "", "", true) PageController.showBusyIndicator(false) } } @@ -465,12 +465,12 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection")) } else { PageController.showBusyIndicator(true) - if (ApiConfigsController.deactivateDevice(false)) { - ApiSettingsController.getAccountInfo(true) + if (SubscriptionUiController.deactivateDevice(ServersUiController.getProcessedServerIndex(), false)) { + SubscriptionUiController.getAccountInfo(ServersUiController.getProcessedServerIndex(), true) } PageController.showBusyIndicator(false) } @@ -502,11 +502,11 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { PageController.showNotificationMessage(qsTr("Cannot remove server during active connection")) } else { PageController.showBusyIndicator(true) - InstallController.removeProcessedServer() + InstallController.removeServer(ServersUiController.getProcessedServerIndex()) PageController.showBusyIndicator(false) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml b/client/ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml index 0a5ea6e34..ac0c9f71e 100644 --- a/client/ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml +++ b/client/ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml @@ -48,7 +48,7 @@ PageType { Component.onCompleted: { PageController.showBusyIndicator(true) - ApiConfigsController.prepareVpnKeyExport() + SubscriptionUiController.prepareVpnKeyExport(ServersUiController.getProcessedServerIndex()) PageController.showBusyIndicator(false) } @@ -61,7 +61,7 @@ PageType { width: root.width BackButtonType { - Layout.topMargin: 20 + SettingsController.safeAreaTopMargin + Layout.topMargin: 20 + PageController.safeAreaTopMargin } Label { @@ -86,7 +86,7 @@ PageType { leftImageSource: "qrc:/images/controls/copy.svg" clickedFunc: function() { - ApiConfigsController.copyVpnKeyToClipboard() + SubscriptionUiController.copyVpnKeyToClipboard() PageController.showNotificationMessage(qsTr("Copied")) } } @@ -119,7 +119,7 @@ PageType { if (fileName !== "") { PageController.showBusyIndicator(true) - ApiConfigsController.exportVpnKey(fileName) + SubscriptionUiController.exportVpnKey(ServersUiController.getProcessedServerIndex(), fileName) PageController.showBusyIndicator(false) } } @@ -141,7 +141,7 @@ PageType { clickedFunc: function() { PageController.showBusyIndicator(true) - ApiConfigsController.prepareVpnKeyExport() + SubscriptionUiController.prepareVpnKeyExport(ServersUiController.getProcessedServerIndex()) PageController.showBusyIndicator(false) vpnKeyDrawer.openTriggered() } @@ -155,7 +155,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - visible: ApiConfigsController.qrCodesCount > 0 + visible: SubscriptionUiController.qrCodesCount > 0 color: "white" radius: 12 @@ -165,7 +165,7 @@ PageType { fillMode: Image.PreserveAspectFit sourceSize.width: parent.width sourceSize.height: parent.height - source: ApiConfigsController.qrCodesCount > 0 && ApiConfigsController.qrCodes[0] ? ApiConfigsController.qrCodes[0] : "" + source: SubscriptionUiController.qrCodesCount > 0 && SubscriptionUiController.qrCodes[0] ? SubscriptionUiController.qrCodes[0] : "" } } @@ -175,7 +175,7 @@ PageType { Layout.bottomMargin: 16 Layout.leftMargin: 16 Layout.rightMargin: 16 - visible: ApiConfigsController.qrCodesCount > 0 + visible: SubscriptionUiController.qrCodesCount > 0 horizontalAlignment: Text.AlignHCenter text: qsTr("To read the QR code in the Amnezia app, tap + in the main menu → 'QR code'") } @@ -219,7 +219,7 @@ PageType { font.pixelSize: 16 font.weight: Font.Medium font.family: "PT Root UI VF" - text: ApiConfigsController.vpnKey + text: SubscriptionUiController.vpnKey wrapMode: Text.Wrap background: Rectangle { color: AmneziaStyle.color.transparent } } diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml index 395cd1ff8..980b48674 100644 --- a/client/ui/qml/Pages2/PageSettingsApiSupport.qml +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -59,7 +59,7 @@ PageType { id: listView anchors.fill: parent - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin anchors.bottomMargin: 24 model: supportModel diff --git a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml index f3c63eb59..29a1e5b10 100644 --- a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -8,7 +8,6 @@ import QtCore import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import ContainerProps 1.0 import Style 1.0 @@ -59,7 +58,7 @@ PageType { } function getRouteModesModelIndex() { - var currentRouteMode = AppSplitTunnelingModel.routeMode + var currentRouteMode = AppSplitTunnelingController.routeMode if ((routeMode.onlyForwardApps === currentRouteMode) || (routeMode.allApps === currentRouteMode)) { return 0 } else if (routeMode.allExceptApps === currentRouteMode) { @@ -74,7 +73,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin BackButtonType { id: backButton @@ -90,11 +89,11 @@ PageType { enabled: root.pageEnabled showSwitcher: true switcher { - checked: AppSplitTunnelingModel.isTunnelingEnabled + checked: AppSplitTunnelingController.isSplitTunnelingEnabled enabled: root.pageEnabled } switcherFunction: function(checked) { - AppSplitTunnelingModel.toggleSplitTunneling(checked) + AppSplitTunnelingController.toggleSplitTunneling(checked) selector.text = root.routeModesModel[getRouteModesModelIndex()].name } } @@ -124,13 +123,13 @@ PageType { clickedFunction: function() { selector.text = selectedText selector.closeTriggered() - if (AppSplitTunnelingModel.routeMode !== root.routeModesModel[selectedIndex].type) { - AppSplitTunnelingModel.routeMode = root.routeModesModel[selectedIndex].type + if (AppSplitTunnelingController.routeMode !== root.routeModesModel[selectedIndex].type) { + AppSplitTunnelingController.routeMode = root.routeModesModel[selectedIndex].type } } Component.onCompleted: { - if (root.routeModesModel[selectedIndex].type === AppSplitTunnelingModel.routeMode) { + if (root.routeModesModel[selectedIndex].type === AppSplitTunnelingController.routeMode) { selector.text = selectedText } else { selector.text = root.routeModesModel[0].name @@ -138,7 +137,7 @@ PageType { } Connections { - target: AppSplitTunnelingModel + target: AppSplitTunnelingController function onRouteModeChanged() { selectedIndex = getRouteModesModelIndex() } @@ -166,7 +165,7 @@ PageType { anchors.top: header.bottom anchors.bottom: parent.bottom - anchors.bottomMargin: addAppButton.implicitHeight + 48 + SettingsController.safeAreaBottomMargin + (searchField.textField.activeFocus ? 0 : SettingsController.imeHeight) + anchors.bottomMargin: addAppButton.implicitHeight + 48 + PageController.safeAreaBottomMargin + (searchField.textField.activeFocus ? 0 : PageController.imeHeight) anchors.left: parent.left anchors.right: parent.right clip: true @@ -221,7 +220,7 @@ PageType { anchors.right: parent.right anchors.bottom: parent.bottom - height: addAppButton.implicitHeight + 48 + SettingsController.safeAreaBottomMargin + height: addAppButton.implicitHeight + 48 + PageController.safeAreaBottomMargin color: AmneziaStyle.color.midnightBlack @@ -236,7 +235,7 @@ PageType { anchors.topMargin: 24 anchors.rightMargin: 16 anchors.leftMargin: 16 - anchors.bottomMargin: 24 + SettingsController.safeAreaBottomMargin + anchors.bottomMargin: 24 + PageController.safeAreaBottomMargin TextFieldWithHeaderType { id: searchField diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 0ba483652..29e42af3f 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -20,7 +20,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { @@ -172,7 +172,7 @@ PageType { SwitcherType { id: switcherNewsNotificationEnabled - visible: ServersModel.hasServersFromGatewayApi + visible: ServersUiController.hasServersFromGatewayApi Layout.fillWidth: true Layout.margins: 16 @@ -203,7 +203,7 @@ PageType { Layout.fillWidth: true text: qsTr("Language") - descriptionText: LanguageModel.currentLanguageName + descriptionText: LanguageUiController.currentLanguageName rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -245,7 +245,7 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { PageController.showNotificationMessage(qsTr("Cannot reset settings during active connection")) } else { diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 20056dce5..ec79ab8e3 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -20,10 +20,6 @@ PageType { Connections { target: SettingsController - function onChangeSettingsErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - function onRestoreBackupFinished() { PageController.showNotificationMessage(qsTr("Settings restored from backup file")) PageController.goToPageHome() @@ -40,7 +36,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 039b8cc22..bc7e6735b 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -20,7 +20,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 528945277..a96d45a1b 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -1,163 +1,170 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import PageEnum 1.0 -import Style 1.0 - -import "./" -import "../Controls2" -import "../Config" -import "../Controls2/TextTypes" -import "../Components" - -PageType { - id: root - - BackButtonType { - id: backButton - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin - - onFocusChanged: { - if (this.activeFocus) { - listView.positionViewAtBeginning() - } - } - } - - ListViewType { - id: listView - - anchors.top: backButton.bottom - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - - property var isServerFromApi: ServersModel.isServerFromApi(ServersModel.defaultIndex) - - enabled: !isServerFromApi - - Component.onCompleted: { - if (isServerFromApi) { - PageController.showNotificationMessage(qsTr("Default server does not support custom DNS")) - } - } - - header: ColumnLayout { - width: listView.width - spacing: 16 - - BaseHeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: qsTr("DNS servers") - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: qsTr("If AmneziaDNS is not used or installed") - } - } - - model: 1 // fake model to force the ListView to be created without a model - - delegate: ColumnLayout { - width: listView.width - spacing: 16 - - TextFieldWithHeaderType { - id: primaryDns - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: qsTr("Primary DNS") - - textField.text: SettingsController.primaryDns - textField.validator: RegularExpressionValidator { - regularExpression: InstallController.ipAddressRegExp() - } - } - - TextFieldWithHeaderType { - id: secondaryDns - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: qsTr("Secondary DNS") - - textField.text: SettingsController.secondaryDns - textField.validator: RegularExpressionValidator { - regularExpression: InstallController.ipAddressRegExp() - } - } - - BasicButtonType { - id: restoreDefaultButton - - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - defaultColor: AmneziaStyle.color.transparent - hoveredColor: AmneziaStyle.color.translucentWhite - pressedColor: AmneziaStyle.color.sheerWhite - disabledColor: AmneziaStyle.color.mutedGray - textColor: AmneziaStyle.color.paleGray - borderWidth: 1 - - text: qsTr("Restore default") - - clickedFunc: function() { - var headerText = qsTr("Restore default DNS settings?") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - SettingsController.primaryDns = "1.1.1.1" - primaryDns.textField.text = SettingsController.primaryDns - SettingsController.secondaryDns = "1.0.0.1" - secondaryDns.textField.text = SettingsController.secondaryDns - PageController.showNotificationMessage(qsTr("Settings have been reset")) - } - var noButtonFunction = function() { - } - - showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - - BasicButtonType { - id: saveButton - - Layout.fillWidth: true - Layout.margins: 16 - - text: qsTr("Save") - - clickedFunc: function() { - if (primaryDns.textField.text !== SettingsController.primaryDns) { - SettingsController.primaryDns = primaryDns.textField.text - } - if (secondaryDns.textField.text !== SettingsController.secondaryDns) { - SettingsController.secondaryDns = secondaryDns.textField.text - } - PageController.showNotificationMessage(qsTr("Settings saved")) - } - } - } - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + PageController.safeAreaTopMargin + + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } + } + } + + ListViewType { + id: listView + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + property var isServerFromApi: ServersModel.isServerFromApi(ServersUiController.defaultIndex) + + enabled: !isServerFromApi + + Component.onCompleted: { + if (isServerFromApi) { + PageController.showNotificationMessage(qsTr("Default server does not support custom DNS")) + } + } + + header: ColumnLayout { + width: listView.width + spacing: 16 + + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("DNS servers") + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("If AmneziaDNS is not used or installed") + } + } + + model: 1 // fake model to force the ListView to be created without a model + + delegate: ColumnLayout { + width: listView.width + spacing: 16 + + TextFieldWithHeaderType { + id: primaryDns + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Primary DNS") + + textField.text: SettingsController.primaryDns + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressRegExp() + } + } + + TextFieldWithHeaderType { + id: secondaryDns + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Secondary DNS") + + textField.text: SettingsController.secondaryDns + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressRegExp() + } + } + + BasicButtonType { + id: restoreDefaultButton + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: AmneziaStyle.color.transparent + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + disabledColor: AmneziaStyle.color.mutedGray + textColor: AmneziaStyle.color.paleGray + borderWidth: 1 + + text: qsTr("Restore default") + + clickedFunc: function() { + var headerText = qsTr("Restore default DNS settings?") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + SettingsController.primaryDns = "1.1.1.1" + primaryDns.textField.text = SettingsController.primaryDns + SettingsController.secondaryDns = "1.0.0.1" + secondaryDns.textField.text = SettingsController.secondaryDns + PageController.showNotificationMessage(qsTr("Settings have been reset")) + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + BasicButtonType { + id: saveButton + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Save") + + clickedFunc: function() { + if (primaryDns.textField.text === "") { + primaryDns.errorText = qsTr("Primary DNS cannot be empty") + return + } + primaryDns.errorText = "" + secondaryDns.errorText = "" + + if (primaryDns.textField.text !== SettingsController.primaryDns) { + SettingsController.primaryDns = primaryDns.textField.text + } + if (secondaryDns.textField.text !== SettingsController.secondaryDns) { + SettingsController.secondaryDns = secondaryDns.textField.text + } + PageController.showNotificationMessage(qsTr("Settings saved")) + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml index 1877f0dc7..4ccbd4a89 100644 --- a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml +++ b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml @@ -17,7 +17,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin } FlickableType { diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml b/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml index 8daa4a34b..44eabec34 100644 --- a/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml +++ b/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml @@ -8,7 +8,6 @@ import QtCore import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import ContainerProps 1.0 import Style 1.0 @@ -30,7 +29,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin BackButtonType { id: backButton diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index a49ab3098..d8a689f57 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -22,7 +22,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { diff --git a/client/ui/qml/Pages2/PageSettingsNewsDetail.qml b/client/ui/qml/Pages2/PageSettingsNewsDetail.qml index 74fbb9218..c67b4303f 100644 --- a/client/ui/qml/Pages2/PageSettingsNewsDetail.qml +++ b/client/ui/qml/Pages2/PageSettingsNewsDetail.qml @@ -34,7 +34,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin } FlickableType { diff --git a/client/ui/qml/Pages2/PageSettingsNewsNotifications.qml b/client/ui/qml/Pages2/PageSettingsNewsNotifications.qml index 661d9c6c1..2aa4ba008 100644 --- a/client/ui/qml/Pages2/PageSettingsNewsNotifications.qml +++ b/client/ui/qml/Pages2/PageSettingsNewsNotifications.qml @@ -19,7 +19,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin BackButtonType { id: backButton diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 71e4bf4e0..8ae736ae0 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -5,7 +5,6 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import Style 1.0 import "../Controls2" @@ -18,7 +17,7 @@ PageType { signal lastItemTabClickedSignal() - property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess() + property bool isServerWithWriteAccess: ServersUiController.isProcessedServerHasWriteAccess() Connections { target: InstallController @@ -34,7 +33,7 @@ PageType { PageController.showErrorMessage(message) } - function onRebootProcessedServerFinished(finishedMessage) { + function onRebootServerFinished(finishedMessage) { PageController.showNotificationMessage(finishedMessage) } @@ -43,7 +42,7 @@ PageType { PageController.showNotificationMessage(finishedMessage) } - function onRemoveProcessedContainerFinished(finishedMessage) { + function onRemoveContainerFinished(finishedMessage) { PageController.closePage() // close deInstalling page PageController.closePage() // close page with remove button PageController.showNotificationMessage(finishedMessage) @@ -58,10 +57,10 @@ PageType { } Connections { - target: ServersModel + target: ServersUiController function onProcessedServerIndexChanged() { - root.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess() + root.isServerWithWriteAccess = ServersUiController.isProcessedServerHasWriteAccess() } } @@ -112,7 +111,7 @@ PageType { readonly property var tColor: AmneziaStyle.color.paleGray readonly property var clickedHandler: function() { PageController.showBusyIndicator(true) - InstallController.scanServerForInstalledContainers() + InstallController.scanServerForInstalledContainers(ServersUiController.processedIndex) PageController.showBusyIndicator(false) } } @@ -131,11 +130,11 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { PageController.showNotificationMessage(qsTr("Cannot reboot server during active connection")) } else { PageController.showBusyIndicator(true) - InstallController.rebootProcessedServer() + InstallController.rebootServer(ServersUiController.processedIndex) PageController.showBusyIndicator(false) } } @@ -161,11 +160,11 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { PageController.showNotificationMessage(qsTr("Cannot remove server during active connection")) } else { PageController.showBusyIndicator(true) - InstallController.removeProcessedServer() + InstallController.removeServer(ServersUiController.processedIndex) PageController.showBusyIndicator(false) } } @@ -191,11 +190,11 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { PageController.showNotificationMessage(qsTr("Cannot clear server from Amnezia software during active connection")) } else { PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeAllContainers() + InstallController.removeAllContainers(ServersUiController.processedIndex) } } var noButtonFunction = function() { @@ -220,11 +219,11 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { PageController.showNotificationMessage(qsTr("Cannot reset API config during active connection")) } else { PageController.showBusyIndicator(true) - InstallController.removeApiConfig(ServersModel.processedIndex) + SubscriptionUiController.removeApiConfig(ServersUiController.processedIndex) PageController.showBusyIndicator(false) } } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index e4508008f..8611d10f0 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -5,9 +5,7 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import ContainerProps 1.0 -import ProtocolProps 1.0 import Style 1.0 import "./" @@ -62,7 +60,7 @@ PageType { objectName: "mainLayout" anchors.fill: parent - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin spacing: 4 diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index a70348ef7..13d77aa5b 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -5,8 +5,6 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 -import ContainerEnum 1.0 import ContainerProps 1.0 import Style 1.0 @@ -19,7 +17,7 @@ import "../Components" PageType { id: root - property bool isClearCacheVisible: ServersModel.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ContainersModel.getProcessedContainerIndex()) + property bool isClearCacheVisible: ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex) BackButtonType { id: backButton @@ -27,7 +25,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { @@ -64,8 +62,8 @@ PageType { width: listView.width - property bool isClientSettingsVisible: (protocolIndex === ProtocolEnum.WireGuard) || (protocolIndex === ProtocolEnum.Awg) - property bool isServerSettingsVisible: ServersModel.isProcessedServerHasWriteAccess() + property bool isClientSettingsVisible: isWireGuard || isAwg + property bool isServerSettingsVisible: ServersUiController.isProcessedServerHasWriteAccess() LabelWithButtonType { id: clientSettings @@ -78,10 +76,7 @@ PageType { clickedFunction: function() { if (isClientProtocolExists) { - switch (protocolIndex) { - case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Awg: AwgConfigModel.updateModel(ProtocolsModel.getConfig()); break; - } + InstallController.openClientSettings(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, protocolIndex) PageController.goToPage(clientProtocolPage); } else { PageController.showNotificationMessage(qsTr("Click the \"connect\" button to create a connection configuration")) @@ -109,17 +104,7 @@ PageType { visible: delegateContent.isServerSettingsVisible clickedFunction: function() { - switch (protocolIndex) { - case ProtocolEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Awg: AwgConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Xray: XrayConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Sftp: SftpConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Socks5Proxy: Socks5ProxyConfigModel.updateModel(ProtocolsModel.getConfig()); break; - } + InstallController.openServerSettings(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, protocolIndex) PageController.goToPage(serverProtocolPage); } @@ -155,14 +140,14 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) { var message = qsTr("Unable to clear %1 profile while there is an active connection").arg(ContainersModel.getProcessedContainerName()) PageController.showNotificationMessage(message) return } PageController.showBusyIndicator(true) - InstallController.clearCachedProfile() + InstallController.clearCachedProfile(ServersUiController.processedIndex, ServersUiController.processedContainerIndex) PageController.showBusyIndicator(false) } @@ -188,7 +173,7 @@ PageType { Layout.fillWidth: true - visible: ServersModel.isProcessedServerHasWriteAccess() + visible: ServersUiController.isProcessedServerHasWriteAccess() text: qsTr("Remove ") textColor: AmneziaStyle.color.vibrantRed @@ -200,13 +185,13 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected - && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected + && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) { PageController.showNotificationMessage(qsTr("Cannot remove active container")) } else { PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeProcessedContainer() + InstallController.removeContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex) } } var noButtonFunction = function() { @@ -224,7 +209,7 @@ PageType { } DividerType { - visible: ServersModel.isProcessedServerHasWriteAccess() + visible: ServersUiController.isProcessedServerHasWriteAccess() } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml index ba72957ef..762dcb1d0 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -5,7 +5,6 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import ContainerProps 1.0 import ContainersModelFilters 1.0 import Style 1.0 @@ -31,7 +30,7 @@ PageType { anchors.fill: parent Connections { - target: ServersModel + target: ServersUiController function onProcessedServerIndexChanged() { settingsContainersListView.updateContainersModelFilters() @@ -39,7 +38,7 @@ PageType { } function updateContainersModelFilters() { - if (ServersModel.isProcessedServerHasWriteAccess()) { + if (ServersUiController.isProcessedServerHasWriteAccess()) { proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() } else { proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml index a46d4051e..6f8fda2c8 100644 --- a/client/ui/qml/Pages2/PageSettingsServerServices.qml +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -5,7 +5,6 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import ContainerProps 1.0 import ContainersModelFilters 1.0 import Style 1.0 @@ -27,7 +26,7 @@ PageType { anchors.fill: parent Connections { - target: ServersModel + target: ServersUiController function onProcessedServerIndexChanged() { settingsContainersListView.updateContainersModelFilters() @@ -35,7 +34,7 @@ PageType { } function updateContainersModelFilters() { - if (ServersModel.isProcessedServerHasWriteAccess()) { + if (ServersUiController.isProcessedServerHasWriteAccess()) { proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters() } else { proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters() diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 0a62fa736..c163b1eb9 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -5,7 +5,6 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import ContainerProps 1.0 import Style 1.0 @@ -25,7 +24,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin BackButtonType { id: backButton @@ -73,7 +72,7 @@ PageType { descriptionText: { var servicesNameString = "" - var servicesName = ServersModel.getAllInstalledServicesName(index) + var servicesName = ServersUiController.getAllInstalledServicesName(index) for (var i = 0; i < servicesName.length; i++) { servicesNameString += servicesName[i] + " · " } @@ -87,11 +86,11 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - ServersModel.processedIndex = index + ServersUiController.processedIndex = index if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) { PageController.showBusyIndicator(true) - let result = ApiSettingsController.getAccountInfo(false) + let result = SubscriptionUiController.getAccountInfo(ServersUiController.getProcessedServerIndex(), false) PageController.showBusyIndicator(false) if (!result) { return diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index ed0923367..d3ab32659 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -8,7 +8,6 @@ import QtCore import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import ContainerProps 1.0 import Style 1.0 @@ -29,7 +28,7 @@ PageType { if (ConnectionController.isConnected) { PageController.showNotificationMessage(qsTr("Cannot change split tunneling settings during active connection")) root.pageEnabled = false - } else if (ServersModel.isDefaultServerDefaultContainerHasSplitTunneling) { + } else if (ServersUiController.isDefaultServerDefaultContainerHasSplitTunneling) { PageController.showNotificationMessage(qsTr("Default server does not support split tunneling function")) root.pageEnabled = false } else { @@ -38,7 +37,7 @@ PageType { } Connections { - target: SitesController + target: IpSplitTunnelingController function onFinished(message) { PageController.showNotificationMessage(message) @@ -73,7 +72,7 @@ PageType { } function getRouteModesModelIndex() { - var currentRouteMode = SitesModel.routeMode + var currentRouteMode = IpSplitTunnelingController.routeMode if ((routeMode.onlyForwardSites === currentRouteMode) || (routeMode.allSites === currentRouteMode)) { return 0 } else if (routeMode.allExceptSites === currentRouteMode) { @@ -88,7 +87,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin BackButtonType { id: backButton @@ -104,11 +103,11 @@ PageType { enabled: root.pageEnabled showSwitcher: true switcher { - checked: SitesModel.isTunnelingEnabled + checked: IpSplitTunnelingController.isSplitTunnelingEnabled enabled: root.pageEnabled } switcherFunction: function(checked) { - SitesModel.toggleSplitTunneling(checked) + IpSplitTunnelingController.toggleSplitTunneling(checked) selector.text = root.routeModesModel[getRouteModesModelIndex()].name } } @@ -138,13 +137,13 @@ PageType { clickedFunction: function() { selector.text = selectedText selector.closeTriggered() - if (SitesModel.routeMode !== root.routeModesModel[selectedIndex].type) { - SitesModel.routeMode = root.routeModesModel[selectedIndex].type + if (IpSplitTunnelingController.routeMode !== root.routeModesModel[selectedIndex].type) { + IpSplitTunnelingController.routeMode = root.routeModesModel[selectedIndex].type } } Component.onCompleted: { - if (root.routeModesModel[selectedIndex].type === SitesModel.routeMode) { + if (root.routeModesModel[selectedIndex].type === IpSplitTunnelingController.routeMode) { selector.text = selectedText } else { selector.text = root.routeModesModel[0].name @@ -152,7 +151,7 @@ PageType { } Connections { - target: SitesModel + target: IpSplitTunnelingController function onRouteModeChanged() { selectedIndex = getRouteModesModelIndex() } @@ -169,7 +168,7 @@ PageType { anchors.top: header.bottom anchors.topMargin: 16 anchors.bottom: parent.bottom - anchors.bottomMargin: addSiteButton.implicitHeight + 48 + (searchField.textField.activeFocus ? 0 : SettingsController.imeHeight) + anchors.bottomMargin: addSiteButton.implicitHeight + 48 + (searchField.textField.activeFocus ? 0 : PageController.imeHeight) width: parent.width @@ -177,8 +176,8 @@ PageType { clip: true model: SortFilterProxyModel { - id: proxySitesModel - sourceModel: SitesModel + id: proxyIpSplitTunnelingModel + sourceModel: IpSplitTunnelingModel filters: [ AnyOf { RegExpFilter { @@ -213,7 +212,7 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - SitesController.removeSite(proxySitesModel.mapToSource(index)) + IpSplitTunnelingController.removeSite(proxyIpSplitTunnelingModel.mapToSource(index)) if (!GC.isMobile()) { site.rightButton.forceActiveFocus() } @@ -265,7 +264,7 @@ PageType { clickedFunc: function() { PageController.showBusyIndicator(true) - SitesController.addSite(textField.text) + IpSplitTunnelingController.addSite(textField.text) textField.text = "" PageController.showBusyIndicator(false) } @@ -341,7 +340,7 @@ PageType { } if (fileName !== "") { PageController.showBusyIndicator(true) - SitesController.exportSites(fileName) + IpSplitTunnelingController.exportSites(fileName) moreActionsDrawer.closeTriggered() PageController.showBusyIndicator(false) } @@ -364,7 +363,7 @@ PageType { var yesButtonFunction = function() { PageController.showBusyIndicator(true) - SitesController.removeSites() + IpSplitTunnelingController.removeSites() PageController.showBusyIndicator(false) } var noButtonFunction = function() { @@ -482,7 +481,7 @@ PageType { function importSites(fileName, replaceExistingSites) { PageController.showBusyIndicator(true) - SitesController.importSites(fileName, replaceExistingSites) + IpSplitTunnelingController.importSites(fileName, replaceExistingSites) PageController.showBusyIndicator(false) importSitesDrawer.closeTriggered() moreActionsDrawer.closeTriggered() diff --git a/client/ui/qml/Pages2/PageSetupWizardApiFreeInfo.qml b/client/ui/qml/Pages2/PageSetupWizardApiFreeInfo.qml index 507e0d621..90135962e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiFreeInfo.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiFreeInfo.qml @@ -29,7 +29,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (activeFocus) { @@ -46,7 +46,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - contentHeight: scrollColumn.implicitHeight + 24 + contentHeight: scrollColumn.childrenRect.height + 24 ColumnLayout { id: scrollColumn @@ -106,7 +106,7 @@ PageType { visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) termsUrl: "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/" - privacyUrl: LanguageModel.getCurrentSiteUrl("policy") + privacyUrl: LanguageUiController.getCurrentSiteUrl("policy") } } } @@ -120,13 +120,13 @@ PageType { anchors.bottom: parent.bottom anchors.leftMargin: 16 anchors.rightMargin: 16 - anchors.bottomMargin: 16 + SettingsController.safeAreaBottomMargin + anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin text: qsTr("Continue") clickedFunc: function() { PageController.showBusyIndicator(true) - var result = ApiConfigsController.importService() + var result = SubscriptionUiController.importFreeFromGateway() PageController.showBusyIndicator(false) if (!result) { diff --git a/client/ui/qml/Pages2/PageSetupWizardApiPremiumInfo.qml b/client/ui/qml/Pages2/PageSetupWizardApiPremiumInfo.qml index b2fcce852..3a64b8ba4 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiPremiumInfo.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiPremiumInfo.qml @@ -35,7 +35,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (activeFocus) { @@ -52,7 +52,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - contentHeight: scrollColumn.implicitHeight + 24 + contentHeight: scrollColumn.childrenRect.height + 24 ColumnLayout { id: scrollColumn @@ -134,7 +134,7 @@ PageType { TermsAndPrivacyText { termsUrl: "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/" - privacyUrl: LanguageModel.getCurrentSiteUrl("policy") + privacyUrl: LanguageUiController.getCurrentSiteUrl("policy") } } @@ -161,7 +161,7 @@ PageType { anchors.bottom: parent.bottom anchors.leftMargin: 16 anchors.rightMargin: 16 - anchors.bottomMargin: 16 + SettingsController.safeAreaBottomMargin + anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin text: { var plan = root.currentPlan @@ -183,7 +183,7 @@ PageType { if (Qt.platform.os === "ios" || IsMacOsNeBuild) { PageController.showBusyIndicator(true) var storeId = plan.storeProductId !== undefined ? String(plan.storeProductId) : "" - ApiConfigsController.importPremiumFromAppStore(storeId) + SubscriptionUiController.importPremiumFromAppStore(storeId) PageController.showBusyIndicator(false) return } diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml index 6d8be466a..d335ecc66 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml @@ -22,7 +22,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { diff --git a/client/ui/qml/Pages2/PageSetupWizardApiTrialEmail.qml b/client/ui/qml/Pages2/PageSetupWizardApiTrialEmail.qml index a37d0c37e..3dcbeb3a3 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiTrialEmail.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiTrialEmail.qml @@ -16,7 +16,7 @@ PageType { property string trialEmailErrorMessage: "" Connections { - target: ApiConfigsController + target: SubscriptionUiController function onTrialEmailError(message) { root.trialEmailErrorMessage = message @@ -30,7 +30,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (activeFocus) { @@ -113,7 +113,7 @@ PageType { anchors.bottom: parent.bottom anchors.leftMargin: 16 anchors.rightMargin: 16 - anchors.bottomMargin: 16 + SettingsController.safeAreaBottomMargin + anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin text: qsTr("Continue") @@ -127,7 +127,7 @@ PageType { return } PageController.showBusyIndicator(true) - var ok = ApiConfigsController.importTrialFromGateway(raw) + var ok = SubscriptionUiController.importTrialFromGateway(raw) PageController.showBusyIndicator(false) if (ok) { PageController.closePage() diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 970587846..1f3318933 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -41,7 +41,7 @@ PageType { property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible() Layout.fillWidth: true - Layout.topMargin: 24 + SettingsController.safeAreaTopMargin + Layout.topMargin: 24 + PageController.safeAreaTopMargin Layout.rightMargin: 16 Layout.leftMargin: 16 @@ -258,7 +258,7 @@ PageType { rightImageSource: "qrc:/images/controls/external-link.svg" clickedFunc: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) + Qt.openUrlExternally(LanguageUiController.getCurrentSiteUrl()) } } } @@ -284,7 +284,7 @@ PageType { property bool isVisible: true property var handler: function() { PageController.showBusyIndicator(true) - var result = ApiConfigsController.fillAvailableServices() + var result = SubscriptionUiController.fillAvailableServices() PageController.showBusyIndicator(false) if (result) { PageController.goToPage(PageEnum.PageSetupWizardApiServicesList) @@ -333,8 +333,7 @@ PageType { property string imageSource: "qrc:/images/controls/folder-search-2.svg" property bool isVisible: true property var handler: function() { - var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" : - "Config files (*.vpn *.ovpn *.conf *.json)" + var nameFilter = "Config files (*.vpn *.ovpn *.conf *.json)" var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter) if (fileName !== "") { if (ImportController.extractConfigFromFile(fileName)) { @@ -370,7 +369,7 @@ PageType { property bool isVisible: Qt.platform.os === "ios" || IsMacOsNeBuild property var handler: function() { PageController.showBusyIndicator(true) - ApiConfigsController.restoreServiceFromAppStore() + SubscriptionUiController.restoreServiceFromAppStore() PageController.showBusyIndicator(false) } } @@ -384,7 +383,7 @@ PageType { property string imageSource: "qrc:/images/controls/help-circle.svg" property bool isVisible: PageController.isStartPageVisible() && Qt.platform.os !== "ios" && !IsMacOsNeBuild property var handler: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) + Qt.openUrlExternally(LanguageUiController.getCurrentSiteUrl()) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index f98afe28f..216d8aa32 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -19,7 +19,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { @@ -116,12 +116,12 @@ PageType { return } - InstallController.setShouldCreateServer(true) var _hostname = listView.itemAtIndex(vars.hostnameIndex).children[0].textField.text var _username = listView.itemAtIndex(vars.usernameIndex).children[0].textField.text var _secretData = listView.itemAtIndex(vars.secretDataIndex).children[0].textField.text InstallController.setProcessedServerCredentials(_hostname, _username, _secretData) + ServersUiController.processedIndex = -1 PageController.showBusyIndicator(true) var isConnectionOpened = InstallController.checkSshConnection() @@ -159,7 +159,7 @@ PageType { leftImageSource: "qrc:/images/controls/help-circle.svg" onClicked: { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl("starter-guide")) + Qt.openUrlExternally(LanguageUiController.getCurrentSiteUrl("starter-guide")) } Keys.onEnterPressed: this.clicked() diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index f4d5293c8..27d955812 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -40,7 +40,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { @@ -111,8 +111,8 @@ PageType { var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) listView.dockerContainer = dockerContainer - listView.containerDefaultPort = ProtocolProps.getPortForInstall(defaultContainerProto) - listView.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto) + listView.containerDefaultPort = InstallController.getPortForInstall(defaultContainerProto) + listView.containerDefaultTransportProto = InstallController.defaultTransportProto(defaultContainerProto) } Keys.onReturnPressed: this.clicked() @@ -160,11 +160,12 @@ PageType { clickedFunc: function() { if (root.isEasySetup) { - ContainersModel.setProcessedContainerIndex(listView.dockerContainer) + ServersUiController.processedContainerIndex = listView.dockerContainer PageController.goToPage(PageEnum.PageSetupWizardInstalling) InstallController.install(listView.dockerContainer, listView.containerDefaultPort, - listView.containerDefaultTransportProto) + listView.containerDefaultTransportProto, + ServersUiController.processedIndex) } else { PageController.goToPage(PageEnum.PageSetupWizardProtocols) } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index d7880dc56..4759eb3d7 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -26,9 +26,9 @@ PageType { target: InstallController function onInstallContainerFinished(finishedMessage, isServiceInstall) { - var containerIndex = ContainersModel.getProcessedContainerIndex() + var containerIndex = ServersUiController.processedContainerIndex if (!ConnectionController.isConnected && !ContainersModel.isServiceContainer(containerIndex)) { - ServersModel.setDefaultContainer(ServersModel.processedIndex, containerIndex) + ServersUiController.setDefaultContainer(ServersUiController.processedIndex, containerIndex) } PageController.closePage() // close installing page @@ -47,8 +47,8 @@ PageType { function onInstallServerFinished(finishedMessage) { if (!ConnectionController.isConnected) { - ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); - ServersModel.processedIndex = ServersModel.defaultIndex + ServersUiController.setDefaultServerIndex(ServersModel.getServersCount() - 1); + ServersUiController.processedIndex = ServersUiController.defaultIndex } PageController.goToPageHome() @@ -57,7 +57,7 @@ PageType { function onServerAlreadyExists(serverIndex) { PageController.goToStartPage() - ServersModel.processedIndex = serverIndex + ServersUiController.processedIndex = serverIndex PageController.goToPage(PageEnum.PageSettingsServerInfo, false) PageController.showErrorMessage(qsTr("The server has already been added to the application")) @@ -103,7 +103,7 @@ PageType { BaseHeaderType { Layout.fillWidth: true - Layout.topMargin: 20 + SettingsController.safeAreaTopMargin + Layout.topMargin: 20 + PageController.safeAreaTopMargin Layout.leftMargin: 16 Layout.rightMargin: 16 @@ -147,7 +147,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 24 - Layout.bottomMargin: 24 + SettingsController.safeAreaBottomMargin + Layout.bottomMargin: 24 + PageController.safeAreaBottomMargin Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 2cf60047f..7d7526592 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -35,7 +35,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { @@ -243,22 +243,22 @@ PageType { } PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.install(dockerContainer, port.textField.text, transportProtoSelector.currentIndex) + InstallController.install(dockerContainer, port.textField.text, transportProtoSelector.currentIndex, ServersUiController.processedIndex) } } Component.onCompleted: { var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) - if (ProtocolProps.defaultPort(defaultContainerProto) < 0) { + if (InstallController.defaultPort(defaultContainerProto) < 0) { port.visible = false } else { - port.textField.text = ProtocolProps.getPortForInstall(defaultContainerProto) + port.textField.text = InstallController.getPortForInstall(defaultContainerProto) } - transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) + transportProtoSelector.currentIndex = InstallController.defaultTransportProto(defaultContainerProto) - port.enabled = ProtocolProps.defaultPortChangeable(defaultContainerProto) - var protocolSelectorVisible = ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) + port.enabled = InstallController.defaultPortChangeable(defaultContainerProto) + var protocolSelectorVisible = InstallController.defaultTransportProtoChangeable(defaultContainerProto) transportProtoSelector.visible = protocolSelectorVisible transportProtoHeader.visible = protocolSelectorVisible } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 380bc5176..1fa9f6d7f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -5,7 +5,6 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 -import ProtocolEnum 1.0 import Style 1.0 import "./" @@ -20,8 +19,8 @@ PageType { sourceModel: ContainersModel filters: [ ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Vpn + roleName: "isVpnContainer" + value: true }, ValueFilter { roleName: "isSupported" @@ -45,7 +44,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { @@ -93,7 +92,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function () { - ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(index)); + ServersUiController.processedContainerIndex = proxyContainersModel.mapToSource(index) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings); } } diff --git a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml index 320e0e206..03053459b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml +++ b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml @@ -20,7 +20,7 @@ PageType { anchors.left: parent.left anchors.top: parent.top - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin } ParagraphTextType { diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index adacd6c08..8545d93d6 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -26,7 +26,7 @@ PageType { source: "qrc:/images/amneziaBigLogo.png" Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - Layout.topMargin: 32 + SettingsController.safeAreaTopMargin + Layout.topMargin: 32 + PageController.safeAreaTopMargin Layout.preferredWidth: 360 Layout.preferredHeight: 287 } @@ -34,7 +34,7 @@ PageType { BasicButtonType { id: startButton Layout.fillWidth: true - Layout.bottomMargin: 48 + SettingsController.safeAreaBottomMargin + Layout.bottomMargin: 48 + PageController.safeAreaBottomMargin Layout.leftMargin: 16 Layout.rightMargin: 16 Layout.alignment: Qt.AlignBottom diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index b3dc15a98..e8d661a61 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -19,7 +19,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onFocusChanged: { if (this.activeFocus) { diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index a9a30ef4b..778d5bfa0 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -22,7 +22,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin onActiveFocusChanged: { if(backButton.enabled && backButton.activeFocus) { @@ -53,11 +53,6 @@ PageType { function onImportFinished() { PageController.showBusyIndicator(false) - if (!ConnectionController.isConnected) { - ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); - ServersModel.processedIndex = ServersModel.defaultIndex - } - PageController.goToPageHome() } } @@ -128,7 +123,7 @@ PageType { id: cloakingCheckBox objectName: "cloakingCheckBox" - visible: ImportController.isNativeWireGuardConfig() + visible: ImportController.isNativeWireGuardConfig Layout.fillWidth: true Layout.leftMargin: 16 @@ -149,7 +144,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - textString: ImportController.getMaliciousWarningText() + textString: ImportController.maliciousWarningText textFormat: Qt.RichText visible: textString !== "" diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 3de4bd86e..6c2c81e4e 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -24,15 +24,13 @@ PageType { OpenVpn, WireGuard, Awg, - ShadowSocks, - Cloak, Xray } Connections { target: ExportController - function onRevokeConfigCompleted() { + function onRevokeConfigFinished() { PageController.showBusyIndicator(false) PageController.showNotificationMessage(qsTr("Config revoked")) } @@ -44,51 +42,40 @@ PageType { var configExtension var configFileName + var serverIndex = ServersUiController.processedIndex + var containerIndex = ServersUiController.processedContainerIndex + switch (type) { case PageShare.ConfigType.AmneziaConnection: { - ExportController.generateConnectionConfig(clientNameTextField.textField.text); + ExportController.generateConnectionConfig(serverIndex, containerIndex, clientNameTextField.textField.text); configCaption = qsTr("Save AmneziaVPN config") configExtension = ".vpn" configFileName = "amnezia_config" break; } case PageShare.ConfigType.OpenVpn: { - ExportController.generateOpenVpnConfig(clientNameTextField.textField.text) + ExportController.generateOpenVpnConfig(serverIndex, clientNameTextField.textField.text) configCaption = qsTr("Save OpenVPN config") configExtension = ".ovpn" configFileName = "amnezia_for_openvpn" break } case PageShare.ConfigType.WireGuard: { - ExportController.generateWireGuardConfig(clientNameTextField.textField.text) + ExportController.generateWireGuardConfig(serverIndex, clientNameTextField.textField.text) configCaption = qsTr("Save WireGuard config") configExtension = ".conf" configFileName = "amnezia_for_wireguard" break } case PageShare.ConfigType.Awg: { - ExportController.generateAwgConfig(clientNameTextField.textField.text) + ExportController.generateAwgConfig(serverIndex, containerIndex, clientNameTextField.textField.text) configCaption = qsTr("Save AmneziaWG config") configExtension = ".conf" configFileName = "amnezia_for_awg" break } - case PageShare.ConfigType.ShadowSocks: { - ExportController.generateShadowSocksConfig() - configCaption = qsTr("Save Shadowsocks config") - configExtension = ".json" - configFileName = "amnezia_for_shadowsocks" - break - } - case PageShare.ConfigType.Cloak: { - ExportController.generateCloakConfig() - configCaption = qsTr("Save Cloak config") - configExtension = ".json" - configFileName = "amnezia_for_cloak" - break - } case PageShare.ConfigType.Xray: { - ExportController.generateXrayConfig(clientNameTextField.textField.text) + ExportController.generateXrayConfig(serverIndex, clientNameTextField.textField.text) configCaption = qsTr("Save XRay config") configExtension = ".json" configFileName = "amnezia_for_xray" @@ -135,16 +122,6 @@ PageType { readonly property string name: qsTr("AmneziaWG native format") readonly property int type: PageShare.ConfigType.Awg } - QtObject { - id: shadowSocksConnectionFormat - readonly property string name: qsTr("Shadowsocks native format") - readonly property int type: PageShare.ConfigType.ShadowSocks - } - QtObject { - id: cloakConnectionFormat - readonly property string name: qsTr("Cloak native format") - readonly property int type: PageShare.ConfigType.Cloak - } QtObject { id: xrayConnectionFormat readonly property string name: qsTr("XRay native format") @@ -173,7 +150,7 @@ PageType { HeaderTypeWithButton { id: header Layout.fillWidth: true - Layout.topMargin: 24 + SettingsController.safeAreaTopMargin + Layout.topMargin: 24 + PageController.safeAreaTopMargin headerText: qsTr("Share VPN Access") @@ -272,8 +249,8 @@ PageType { onClicked: { accessTypeSelector.currentIndex = 1 PageController.showBusyIndicator(true) - ExportController.updateClientManagementModel(ContainersModel.getProcessedContainerIndex(), - ServersModel.getProcessedServerCredentials()) + ExportController.updateClientManagementModel(ServersUiController.processedIndex, + ServersUiController.processedContainerIndex) PageController.showBusyIndicator(false) } @@ -356,7 +333,7 @@ PageType { Component.onCompleted: { if (ServersModel.isDefaultServerHasWriteAccess() && ServersModel.getDefaultServerData("hasInstalledContainers")) { - serverSelectorListView.selectedIndex = proxyServersModel.mapFromSource(ServersModel.defaultIndex) + serverSelectorListView.selectedIndex = proxyServersModel.mapFromSource(ServersUiController.defaultIndex) } else { serverSelectorListView.selectedIndex = 0 } @@ -367,15 +344,15 @@ PageType { function handler() { serverSelector.text = selectedText - ServersModel.processedIndex = proxyServersModel.mapToSource(selectedIndex) + ServersUiController.processedIndex = proxyServersModel.mapToSource(selectedIndex) } } } DropDownType { - id: protocolSelector + id: containerSelector - signal protocolSelectorTextChanged + signal containerSelectorTextChanged Layout.fillWidth: true Layout.topMargin: 16 @@ -387,7 +364,7 @@ PageType { headerText: qsTr("Protocol") listView: ListViewWithRadioButtonType { - id: protocolSelectorListView + id: containerSelectorListView rootWidth: root.width imageSource: "qrc:/images/controls/check.svg" @@ -410,7 +387,7 @@ PageType { clickedFunction: function() { handler() - protocolSelector.closeTriggered() + containerSelector.closeTriggered() } Connections { @@ -418,9 +395,9 @@ PageType { function onServerSelectorIndexChanged() { var defaultContainer = proxyContainersModel.mapFromSource(ServersModel.getProcessedServerData("defaultContainer")) - protocolSelectorListView.selectedIndex = defaultContainer - protocolSelectorListView.positionViewAtIndex(selectedIndex, ListView.Beginning) - protocolSelectorListView.triggerCurrentItem() + containerSelectorListView.selectedIndex = defaultContainer + containerSelectorListView.positionViewAtIndex(selectedIndex, ListView.Beginning) + containerSelectorListView.triggerCurrentItem() } } @@ -432,20 +409,20 @@ PageType { root.shareButtonEnabled = true } - protocolSelector.text = selectedText + containerSelector.text = selectedText - ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(selectedIndex)) + ServersUiController.processedContainerIndex = proxyContainersModel.mapToSource(selectedIndex) fillConnectionTypeModel() if (accessTypeSelector.currentIndex === 1) { PageController.showBusyIndicator(true) - ExportController.updateClientManagementModel(ContainersModel.getProcessedContainerIndex(), - ServersModel.getProcessedServerCredentials()) + ExportController.updateClientManagementModel(ServersUiController.processedIndex, + ServersUiController.processedContainerIndex) PageController.showBusyIndicator(false) } - protocolSelector.protocolSelectorTextChanged() + containerSelector.containerSelectorTextChanged() } function fillConnectionTypeModel() { @@ -461,13 +438,6 @@ PageType { root.connectionTypesModel.push(awgConnectionFormat) } else if (index === ContainerProps.containerFromString("amnezia-awg2")) { root.connectionTypesModel.push(awgConnectionFormat) - } else if (index === ContainerProps.containerFromString("amnezia-shadowsocks")) { - root.connectionTypesModel.push(openVpnConnectionFormat) - root.connectionTypesModel.push(shadowSocksConnectionFormat) - } else if (index === ContainerProps.containerFromString("amnezia-openvpn-cloak")) { - root.connectionTypesModel.push(openVpnConnectionFormat) - root.connectionTypesModel.push(shadowSocksConnectionFormat) - root.connectionTypesModel.push(cloakConnectionFormat) } else if (index === ContainerProps.containerFromString("amnezia-xray")) { root.connectionTypesModel.push(xrayConnectionFormat) } @@ -522,9 +492,9 @@ PageType { currentIndex: 0 Connections { - target: protocolSelector + target: containerSelector - function onProtocolSelectorTextChanged() { + function onContainerSelectorTextChanged() { if (exportTypeSelector.currentIndex >= root.connectionTypesModel.length) { exportTypeSelectorListView.selectedIndex = 0 exportTypeSelector.currentIndex = 0 @@ -823,8 +793,8 @@ PageType { PageController.showBusyIndicator(true) ExportController.renameClient(proxyClientManagementModel.mapToSource(index), clientNameEditor.textField.text, - ContainersModel.getProcessedContainerIndex(), - ServersModel.getProcessedServerCredentials()) + ServersUiController.processedIndex, + ServersUiController.processedContainerIndex) PageController.showBusyIndicator(false) Qt.callLater(function(){ clientsListView.freezeFilter = false }) clientNameEditDrawer.closeTriggered() @@ -859,8 +829,8 @@ PageType { clientInfoDrawer.closeTriggered() PageController.showBusyIndicator(true) ExportController.revokeConfig(proxyClientManagementModel.mapToSource(index), - ContainersModel.getProcessedContainerIndex(), - ServersModel.getProcessedServerCredentials()) + ServersUiController.processedIndex, + ServersUiController.processedContainerIndex) } var noButtonFunction = function() { } diff --git a/client/ui/qml/Pages2/PageShareConnection.qml b/client/ui/qml/Pages2/PageShareConnection.qml index 7b55d1b9e..20716e83b 100644 --- a/client/ui/qml/Pages2/PageShareConnection.qml +++ b/client/ui/qml/Pages2/PageShareConnection.qml @@ -47,7 +47,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin } Text { @@ -55,7 +55,7 @@ PageType { anchors.top: backButton.bottom anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin anchors.leftMargin: 16 anchors.rightMargin: 16 @@ -175,7 +175,7 @@ PageType { id: configContentDrawer parent: pageShareConnection.parent anchors.fill: parent - expandedHeight: parent ? parent.height * 0.9 : 0 + expandedHeight: (parent ? parent.height : pageShareConnection.height) * 0.9 expandedStateContent: Item { id: configContentContainer implicitHeight: configContentDrawer.expandedHeight @@ -197,7 +197,7 @@ PageType { configText.copy() configText.select(0, 0) PageController.showNotificationMessage(qsTr("Copied")) - header.forceActiveFocus() + shareHeader.forceActiveFocus() } } @@ -265,7 +265,7 @@ PageType { delegate: ColumnLayout { width: listView.width - property bool isQrCodeVisible: pageShareConnection.isSelfHostedConfig ? ExportController.qrCodesCount > 0 : ApiConfigsController.qrCodesCount > 0 + property bool isQrCodeVisible: pageShareConnection.isSelfHostedConfig ? ExportController.qrCodesCount > 0 : SubscriptionUiController.qrCodesCount > 0 Rectangle { id: qrCodeContainer @@ -284,7 +284,7 @@ PageType { fillMode: Image.PreserveAspectFit sourceSize.width: parent.width sourceSize.height: parent.height - source: pageShareConnection.isSelfHostedConfig ? (isQrCodeVisible ? ExportController.qrCodes[0] : "") : (isQrCodeVisible ? ApiConfigsController.qrCodes[0] : "") + source: pageShareConnection.isSelfHostedConfig ? (isQrCodeVisible ? ExportController.qrCodes[0] : "") : (isQrCodeVisible ? SubscriptionUiController.qrCodes[0] : "") property bool isFocusable: true Keys.onTabPressed: FocusController.nextKeyTabItem() Keys.onBacktabPressed: FocusController.previousKeyTabItem() @@ -301,9 +301,9 @@ PageType { onTriggered: { if (isQrCodeVisible) { index++ - let qrCodesCount = pageShareConnection.isSelfHostedConfig ? ExportController.qrCodesCount : ApiConfigsController.qrCodesCount + let qrCodesCount = pageShareConnection.isSelfHostedConfig ? ExportController.qrCodesCount : SubscriptionUiController.qrCodesCount if (index >= qrCodesCount) index = 0 - parent.source = pageShareConnection.isSelfHostedConfig ? ExportController.qrCodes[index] : ApiConfigsController.qrCodes[index] + parent.source = pageShareConnection.isSelfHostedConfig ? ExportController.qrCodes[index] : SubscriptionUiController.qrCodes[index] } } } diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index 9cbbc7a8d..8b162899c 100644 --- a/client/ui/qml/Pages2/PageShareFullAccess.qml +++ b/client/ui/qml/Pages2/PageShareFullAccess.qml @@ -1,168 +1,168 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Dialogs - -import SortFilterProxyModel 0.2 - -import PageEnum 1.0 -import ContainerProps 1.0 -import Style 1.0 - -import "./" -import "../Controls2" -import "../Controls2/TextTypes" -import "../Components" -import "../Config" - - -PageType { - id: root - - BackButtonType { - id: backButton - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin - - onFocusChanged: { - if (this.activeFocus) { - listView.positionViewAtBeginning() - } - } - } - - ListViewType { - id: listView - - property string headerText: "" - property string configContentHeaderText: "" - - anchors.top: backButton.bottom - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - - header: ColumnLayout { - width: listView.width - - BaseHeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.topMargin: 24 - - headerText: qsTr("Full access to the server and VPN") - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.topMargin: 24 - Layout.bottomMargin: 24 - - text: qsTr("We recommend that you use full access to the server only for your own additional devices.\n") + - qsTr("If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. ") - color: AmneziaStyle.color.mutedGray - } - - DropDownType { - id: serverSelector - objectName: "serverSelector" - - signal serverSelectorIndexChanged - property int currentIndex: 0 - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.topMargin: 16 - - drawerHeight: 0.4375 - drawerParent: root - - descriptionText: qsTr("Server") - headerText: qsTr("Server") - - listView: ListViewWithRadioButtonType { - id: serverSelectorListView - - rootWidth: root.width - imageSource: "qrc:/images/controls/check.svg" - - model: SortFilterProxyModel { - id: proxyServersModel - sourceModel: ServersModel - filters: [ - ValueFilter { - roleName: "hasWriteAccess" - value: true - } - ] - } - - clickedFunction: function() { - handler() - - if (serverSelector.currentIndex !== serverSelectorListView.selectedIndex) { - serverSelector.currentIndex = serverSelectorListView.selectedIndex - serverSelector.serverSelectorIndexChanged() - } - - listView.headerText = qsTr("Accessing ") + serverSelector.text - listView.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text - serverSelector.closeTriggered() - } - - Component.onCompleted: { - serverSelectorListView.currentIndex = ServersModel.isDefaultServerHasWriteAccess() ? - proxyServersModel.mapFromSource(ServersModel.defaultIndex) : 0 - serverSelectorListView.triggerCurrentItem() - } - - function handler() { - serverSelector.text = selectedText - ServersModel.processedIndex = proxyServersModel.mapToSource(selectedIndex) - } - } - } - } - - model: 1 // fake model to force the ListView to be created without a model - spacing: 0 - - delegate: ColumnLayout { - width: listView.width - - BasicButtonType { - id: shareButton - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: qsTr("Share") - leftImageSource: "qrc:/images/controls/share-2.svg" - - clickedFunc: function() { - PageController.showBusyIndicator(true) - - if (Qt.platform.os === "android" && !SystemController.isAuthenticated()) { - PageController.showBusyIndicator(false) - ExportController.exportErrorOccurred(qsTr("Access error!")) - return - } else { - ExportController.generateFullAccessConfig() - } - - PageController.showBusyIndicator(false) - - PageController.goToShareConnectionPage(listView.headerText, listView.configContentHeaderText, "", ".vpn", "amnezia_config") - } - } - } - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerProps 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Components" +import "../Config" + + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + PageController.safeAreaTopMargin + + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } + } + } + + ListViewType { + id: listView + + property string headerText: "" + property string configContentHeaderText: "" + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + header: ColumnLayout { + width: listView.width + + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.topMargin: 24 + + headerText: qsTr("Full access to the server and VPN") + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("We recommend that you use full access to the server only for your own additional devices.\n") + + qsTr("If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. ") + color: AmneziaStyle.color.mutedGray + } + + DropDownType { + id: serverSelector + objectName: "serverSelector" + + signal severSelectorIndexChanged + property int currentIndex: 0 + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.topMargin: 16 + + drawerHeight: 0.4375 + drawerParent: root + + descriptionText: qsTr("Server") + headerText: qsTr("Server") + + listView: ListViewWithRadioButtonType { + id: serverSelectorListView + + rootWidth: root.width + imageSource: "qrc:/images/controls/check.svg" + + model: SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "hasWriteAccess" + value: true + } + ] + } + + clickedFunction: function() { + handler() + + if (serverSelector.currentIndex !== serverSelectorListView.selectedIndex) { + serverSelector.currentIndex = serverSelectorListView.selectedIndex + serverSelector.severSelectorIndexChanged() + } + + listView.headerText = qsTr("Accessing ") + serverSelector.text + listView.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text + serverSelector.closeTriggered() + } + + Component.onCompleted: { + serverSelectorListView.currentIndex = ServersModel.isDefaultServerHasWriteAccess() ? + proxyServersModel.mapFromSource(ServersUiController.defaultIndex) : 0 + serverSelectorListView.triggerCurrentItem() + } + + function handler() { + serverSelector.text = selectedText + ServersUiController.processedIndex = proxyServersModel.mapToSource(selectedIndex) + } + } + } + } + + model: 1 // fake model to force the ListView to be created without a model + spacing: 0 + + delegate: ColumnLayout { + width: listView.width + + BasicButtonType { + id: shareButton + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Share") + leftImageSource: "qrc:/images/controls/share-2.svg" + + clickedFunc: function() { + PageController.showBusyIndicator(true) + + if (Qt.platform.os === "android" && !SystemController.isAuthenticated()) { + PageController.showBusyIndicator(false) + ExportController.exportErrorOccurred(qsTr("Access error!")) + return + } else { + ExportController.generateFullAccessConfig(ServersUiController.processedIndex) + } + + PageController.showBusyIndicator(false) + + PageController.goToShareConnectionPage(listView.headerText, listView.configContentHeaderText, "", ".vpn", "amnezia_config") + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 51608e0be..d39baa9f8 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -141,11 +141,7 @@ PageType { PageController.showNotificationMessage(message) } - function onApiConfigRemoved(message) { - PageController.showNotificationMessage(message) - } - - function onRemoveProcessedServerFinished(finishedMessage) { + function onRemoveServerFinished(finishedMessage) { if (!ServersModel.getServersCount()) { PageController.goToPageHome() } else { @@ -158,8 +154,7 @@ PageType { function onNoInstalledContainers() { PageController.setTriggeredByConnectButton(true) - ServersModel.processedIndex = ServersModel.getDefaultServerIndex() - InstallController.setShouldCreateServer(false) + ServersUiController.processedIndex = ServersUiController.defaultIndex PageController.goToPage(PageEnum.PageSetupWizardEasy) } } @@ -215,7 +210,7 @@ PageType { } Connections { - target: ApiSettingsController + target: SubscriptionUiController function onErrorOccurred(error) { PageController.showErrorMessage(error) @@ -223,16 +218,20 @@ PageType { } Connections { - target: ApiConfigsController + target: SubscriptionUiController + + function onApiConfigRemoved(message) { + PageController.showNotificationMessage(message) + } function onInstallServerFromApiFinished(message, preferredDefaultIndex) { if (!ConnectionController.isConnected) { if (preferredDefaultIndex !== undefined && preferredDefaultIndex >= 0) { - ServersModel.setDefaultServerIndex(preferredDefaultIndex) + ServersUiController.setDefaultServerIndex(preferredDefaultIndex) } else { - ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1) + ServersUiController.setDefaultServerIndex(ServersModel.getServersCount() - 1); } - ServersModel.processedIndex = ServersModel.defaultIndex + ServersUiController.processedIndex = ServersUiController.defaultIndex } PageController.goToPageHome() @@ -275,7 +274,7 @@ PageType { } else { tabBar.visible = true pagePath = PageController.getPagePath(PageEnum.PageHome) - ServersModel.processedIndex = ServersModel.defaultIndex + ServersUiController.processedIndex = ServersUiController.defaultIndex } tabBarStackView.push(pagePath, { "objectName" : pagePath }) @@ -309,10 +308,10 @@ PageType { anchors.bottom: parent.bottom // Also adjust TabBar position when keyboard appears (Android 14+ workaround) - anchors.bottomMargin: SettingsController.imeHeight + anchors.bottomMargin: PageController.imeHeight topPadding: 8 - bottomPadding: 8 + SettingsController.safeAreaBottomMargin + bottomPadding: 8 + PageController.safeAreaBottomMargin leftPadding: 96 rightPadding: 96 @@ -349,7 +348,7 @@ PageType { image: "qrc:/images/controls/home.svg" clickedFunc: function () { tabBarStackView.goToTabBarPage(PageEnum.PageHome) - ServersModel.processedIndex = ServersModel.defaultIndex + ServersUiController.processedIndex = ServersUiController.defaultIndex tabBar.currentIndex = 0 } } @@ -386,12 +385,12 @@ PageType { objectName: "settingsTabButton" isSelected: tabBar.currentIndex === 2 - image: (ServersModel.hasServersFromGatewayApi && NewsModel.hasUnread && SettingsController.isNewsNotificationsEnabled()) ? "qrc:/images/controls/settings-news.svg" : "qrc:/images/controls/settings.svg" + image: (ServersUiController.hasServersFromGatewayApi && NewsModel.hasUnread && SettingsController.isNewsNotificationsEnabled()) ? "qrc:/images/controls/settings-news.svg" : "qrc:/images/controls/settings.svg" Binding { target: settingsTabButton property: "defaultColor" value: "transparent" - when: (ServersModel.hasServersFromGatewayApi && NewsModel.hasUnread) + when: (ServersUiController.hasServersFromGatewayApi && NewsModel.hasUnread) } clickedFunc: function () { tabBarStackView.goToTabBarPage(PageEnum.PageSettings) diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 147f90b8b..aa88b1b08 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -47,7 +47,7 @@ Window { interval: 150 repeat: false onTriggered: { - if (Qt.platform.os === "android" && SettingsController.isEdgeToEdgeEnabled()) { + if (Qt.platform.os === "android" && PageController.isEdgeToEdgeEnabled()) { console.log("QML: Application resumed with edge-to-edge") } } @@ -210,7 +210,7 @@ Window { id: privateKeyPassphraseDrawer anchors.fill: parent - expandedHeight: root.height * 0.35 + SettingsController.safeAreaBottomMargin + SettingsController.imeHeight + expandedHeight: root.height * 0.35 + PageController.safeAreaBottomMargin + PageController.imeHeight expandedStateContent: ColumnLayout { anchors.top: parent.top @@ -301,7 +301,7 @@ Window { } Connections { - target: ApiConfigsController + target: SubscriptionUiController function onSubscriptionExpiredOnServer() { subscriptionExpiredDrawer.openTriggered() @@ -309,7 +309,7 @@ Window { } Connections { - target: ApiSettingsController + target: SubscriptionUiController function onRenewalLinkReceived(url) { Qt.openUrlExternally(url) diff --git a/client/ui/qml/qml.qrc b/client/ui/qml/qml.qrc new file mode 100644 index 000000000..9f2a8ccf6 --- /dev/null +++ b/client/ui/qml/qml.qrc @@ -0,0 +1,135 @@ + + + Components/AdLabel.qml + Components/ConnectButton.qml + Components/ConnectionTypeSelectionDrawer.qml + Components/GamepadLoader.qml + Components/HomeContainersListView.qml + Components/HomeSplitTunnelingDrawer.qml + Components/InstalledAppsDrawer.qml + Components/QuestionDrawer.qml + Components/SelectLanguageDrawer.qml + Components/ServersListView.qml + Components/SettingsContainersListView.qml + Components/BenefitRow.qml + Components/BenefitsPanel.qml + Components/SubscriptionExpiredDrawer.qml + Components/SubscriptionPlanCard.qml + Components/TermsAndPrivacyText.qml + Components/TransportProtoSelector.qml + Components/AddSitePanel.qml + Config/GlobalConfig.qml + Config/qmldir + Controls2/BackButtonType.qml + Controls2/BasicButtonType.qml + Controls2/BusyIndicatorType.qml + Controls2/CardType.qml + Controls2/CardWithIconsType.qml + Controls2/CheckBoxType.qml + Controls2/ContextMenuType.qml + Controls2/DividerType.qml + Controls2/DrawerType2.qml + Controls2/DropDownType.qml + Controls2/FlickableType.qml + Controls2/Header2Type.qml + Controls2/BaseHeaderType.qml + Controls2/HeaderTypeWithButton.qml + Controls2/HeaderTypeWithSwitcher.qml + Controls2/HorizontalRadioButton.qml + Controls2/ImageButtonType.qml + Controls2/LabelWithButtonType.qml + Controls2/LabelWithImageType.qml + Controls2/ListViewWithLabelsType.qml + Controls2/ListViewWithRadioButtonType.qml + Controls2/PageType.qml + Controls2/PopupType.qml + Controls2/ProgressBarType.qml + Controls2/ScrollBarType.qml + Controls2/StackViewType.qml + Controls2/SwitcherType.qml + Controls2/TabButtonType.qml + Controls2/TabImageButtonType.qml + Controls2/TextAreaType.qml + Controls2/TextAreaWithFooterType.qml + Controls2/TextFieldWithHeaderType.qml + Controls2/TextTypes/ButtonTextType.qml + Controls2/TextTypes/CaptionTextType.qml + Controls2/TextTypes/Header1TextType.qml + Controls2/TextTypes/Header2TextType.qml + Controls2/TextTypes/LabelTextType.qml + Controls2/TextTypes/ListItemTitleType.qml + Controls2/TextTypes/ParagraphTextType.qml + Controls2/TextTypes/BadgeTextType.qml + Controls2/TextTypes/SmallTextType.qml + Controls2/TopCloseButtonType.qml + Controls2/VerticalRadioButton.qml + Controls2/WarningType.qml + Filters/ContainersModelFilters.qml + main2.qml + Modules/Style/AmneziaStyle.qml + Modules/Style/qmldir + Pages2/PageDeinstalling.qml + Pages2/PageDevMenu.qml + Pages2/PageHome.qml + Pages2/PageProtocolAwgSettings.qml + Pages2/PageProtocolOpenVpnSettings.qml + Pages2/PageProtocolRaw.qml + Pages2/PageProtocolWireGuardSettings.qml + Pages2/PageProtocolXraySettings.qml + Pages2/PageServiceDnsSettings.qml + Pages2/PageServiceSftpSettings.qml + Pages2/PageServiceSocksProxySettings.qml + Pages2/PageServiceTorWebsiteSettings.qml + Pages2/PageSettings.qml + Pages2/PageSettingsAbout.qml + Pages2/PageSettingsApiAvailableCountries.qml + Pages2/PageSettingsApiServerInfo.qml + Pages2/PageSettingsApplication.qml + Pages2/PageSettingsAppSplitTunneling.qml + Pages2/PageSettingsBackup.qml + Pages2/PageSettingsConnection.qml + Pages2/PageSettingsDns.qml + Pages2/PageSettingsKillSwitch.qml + Pages2/PageSettingsKillSwitchExceptions.qml + Pages2/PageSettingsLogging.qml + Pages2/PageSettingsServerData.qml + Pages2/PageSettingsServerInfo.qml + Pages2/PageSettingsServerProtocol.qml + Pages2/PageSettingsServerProtocols.qml + Pages2/PageSettingsServerServices.qml + Pages2/PageSettingsServersList.qml + Pages2/PageSettingsSplitTunneling.qml + Pages2/PageSettingsNewsNotifications.qml + Pages2/PageSettingsNewsDetail.qml + Pages2/PageProtocolAwgClientSettings.qml + Pages2/PageProtocolWireGuardClientSettings.qml + Pages2/PageSetupWizardApiFreeInfo.qml + Pages2/PageSetupWizardApiPremiumInfo.qml + Pages2/PageSetupWizardApiTrialEmail.qml + Pages2/PageSetupWizardApiServicesList.qml + Pages2/PageSetupWizardConfigSource.qml + Pages2/PageSetupWizardCredentials.qml + Pages2/PageSetupWizardEasy.qml + Pages2/PageSetupWizardInstalling.qml + Pages2/PageSetupWizardProtocols.qml + Pages2/PageSetupWizardProtocolSettings.qml + Pages2/PageSetupWizardQrReader.qml + Pages2/PageSetupWizardStart.qml + Pages2/PageSetupWizardTextKey.qml + Pages2/PageSetupWizardViewConfig.qml + Pages2/PageShare.qml + Pages2/PageShareFullAccess.qml + Pages2/PageShareConnection.qml + Pages2/PageStart.qml + Components/RenameServerDrawer.qml + Controls2/ListViewType.qml + Pages2/PageSettingsApiSupport.qml + Pages2/PageSettingsApiInstructions.qml + Pages2/PageSettingsApiNativeConfigs.qml + Pages2/PageSettingsApiDevices.qml + Components/AwgTextField.qml + Pages2/PageSettingsApiSubscriptionKey.qml + Components/SmartScroll.qml + + + diff --git a/client/utils/converter.h b/client/ui/utils/converter.h similarity index 100% rename from client/utils/converter.h rename to client/ui/utils/converter.h diff --git a/client/ui/macos_util.h b/client/ui/utils/macosUtil.h similarity index 100% rename from client/ui/macos_util.h rename to client/ui/utils/macosUtil.h diff --git a/client/ui/macos_util.mm b/client/ui/utils/macosUtil.mm similarity index 99% rename from client/ui/macos_util.mm rename to client/ui/utils/macosUtil.mm index 3947b89bd..599ad7954 100644 --- a/client/ui/macos_util.mm +++ b/client/ui/utils/macosUtil.mm @@ -1,4 +1,4 @@ -#include "macos_util.h" +#include "macosUtil.h" #include #include diff --git a/client/ui/ne_notificationhandler.h b/client/ui/utils/neNotificationHandler.h similarity index 84% rename from client/ui/ne_notificationhandler.h rename to client/ui/utils/neNotificationHandler.h index e84d80685..b53eda986 100644 --- a/client/ui/ne_notificationhandler.h +++ b/client/ui/utils/neNotificationHandler.h @@ -1,7 +1,7 @@ -#ifndef NE_NOTIFICATION_HANDLER_H -#define NE_NOTIFICATION_HANDLER_H +#ifndef NENOTIFICATIONHANDLER_H +#define NENOTIFICATIONHANDLER_H -#include "notificationhandler.h" +#include "notificationHandler.h" #include #include @@ -33,4 +33,4 @@ private: QAction* m_actionQuit; }; -#endif // NE_NOTIFICATION_HANDLER_H +#endif // NENOTIFICATIONHANDLER_H diff --git a/client/ui/notificationhandler.cpp b/client/ui/utils/notificationHandler.cpp similarity index 97% rename from client/ui/notificationhandler.cpp rename to client/ui/utils/notificationHandler.cpp index 4ccdcdf16..46f3628ca 100644 --- a/client/ui/notificationhandler.cpp +++ b/client/ui/utils/notificationHandler.cpp @@ -3,12 +3,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include -#include "notificationhandler.h" +#include "notificationHandler.h" #if defined(Q_OS_IOS) # include "platforms/ios/iosnotificationhandler.h" #else -# include "systemtray_notificationhandler.h" +# include "systemTrayNotificationHandler.h" #endif diff --git a/client/ui/notificationhandler.h b/client/ui/utils/notificationHandler.h similarity index 97% rename from client/ui/notificationhandler.h rename to client/ui/utils/notificationHandler.h index abdce27b6..1e428b624 100644 --- a/client/ui/notificationhandler.h +++ b/client/ui/utils/notificationHandler.h @@ -6,7 +6,7 @@ #define NOTIFICATIONHANDLER_H #include -#include "protocols/vpnprotocol.h" +#include "core/protocols/vpnProtocol.h" class QMenu; diff --git a/client/ui/pages.h b/client/ui/utils/pages.h similarity index 100% rename from client/ui/pages.h rename to client/ui/utils/pages.h diff --git a/client/ui/qautostart.cpp b/client/ui/utils/qAutoStart.cpp similarity index 99% rename from client/ui/qautostart.cpp rename to client/ui/utils/qAutoStart.cpp index b0165dc4d..7ccdb4ec9 100644 --- a/client/ui/qautostart.cpp +++ b/client/ui/utils/qAutoStart.cpp @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include "qautostart.h" +#include "qAutoStart.h" #include #include diff --git a/client/ui/qautostart.h b/client/ui/utils/qAutoStart.h similarity index 100% rename from client/ui/qautostart.h rename to client/ui/utils/qAutoStart.h diff --git a/client/utils/qmlUtils.cpp b/client/ui/utils/qmlUtils.cpp similarity index 100% rename from client/utils/qmlUtils.cpp rename to client/ui/utils/qmlUtils.cpp diff --git a/client/utils/qmlUtils.h b/client/ui/utils/qmlUtils.h similarity index 100% rename from client/utils/qmlUtils.h rename to client/ui/utils/qmlUtils.h diff --git a/client/ui/systemtray_notificationhandler.cpp b/client/ui/utils/systemTrayNotificationHandler.cpp similarity index 99% rename from client/ui/systemtray_notificationhandler.cpp rename to client/ui/utils/systemTrayNotificationHandler.cpp index 11a1b6510..8cbcbe67e 100644 --- a/client/ui/systemtray_notificationhandler.cpp +++ b/client/ui/utils/systemTrayNotificationHandler.cpp @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include -#include "systemtray_notificationhandler.h" +#include "systemTrayNotificationHandler.h" #ifdef Q_OS_MAC diff --git a/client/ui/systemtray_notificationhandler.h b/client/ui/utils/systemTrayNotificationHandler.h similarity index 90% rename from client/ui/systemtray_notificationhandler.h rename to client/ui/utils/systemTrayNotificationHandler.h index d1458021b..309b1d64b 100644 --- a/client/ui/systemtray_notificationhandler.h +++ b/client/ui/utils/systemTrayNotificationHandler.h @@ -2,10 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef SYSTEMTRAY_NOTIFICATIONHANDLER_H -#define SYSTEMTRAY_NOTIFICATIONHANDLER_H +#ifndef SYSTEMTRAYNOTIFICATIONHANDLER_H +#define SYSTEMTRAYNOTIFICATIONHANDLER_H -#include "notificationhandler.h" +#include "notificationHandler.h" #include #include @@ -54,4 +54,4 @@ private: QString websiteUrl = "https://amnezia.org"; }; -#endif // SYSTEMTRAY_NOTIFICATIONHANDLER_H +#endif // SYSTEMTRAYNOTIFICATIONHANDLER_H diff --git a/client/vpnconnection.cpp b/client/vpnConnection.cpp similarity index 70% rename from client/vpnconnection.cpp rename to client/vpnConnection.cpp index 3997ed02c..9da0b254e 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnConnection.cpp @@ -1,4 +1,4 @@ -#include "vpnconnection.h" +#include "vpnConnection.h" #include #include @@ -11,14 +11,12 @@ #include #include -#include -#include -#include -#include +#include +#include #ifdef AMNEZIA_DESKTOP - #include "core/ipcclient.h" - #include + #include "core/utils/ipcClient.h" + #include #endif #ifdef Q_OS_ANDROID @@ -31,11 +29,13 @@ #include "platforms/ios/ios_controller.h" #endif -#include "core/networkUtilities.h" -#include "vpnconnection.h" +#include "core/utils/networkUtilities.h" +#include "vpnConnection.h" -VpnConnection::VpnConnection(std::shared_ptr settings, QObject *parent) - : QObject(parent), m_settings(settings), m_checkTimer(new QTimer(this)) +using namespace ProtocolUtils; + +VpnConnection::VpnConnection(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository, QObject *parent) + : QObject(parent), m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository), m_checkTimer(this) { #if defined(Q_OS_IOS) || defined(MACOS_NE) m_checkTimer.setInterval(1000); @@ -69,7 +69,13 @@ void VpnConnection::onKillSwitchModeChanged(bool enabled) void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) { #ifdef AMNEZIA_DESKTOP - auto container = m_settings->defaultContainer(m_settings->defaultServerIndex()); + if (!m_serversRepository || !m_appSettingsRepository) { + qCritical() << "VpnConnection::onConnectionStateChanged: repositories not initialized"; + return; + } + + ServerConfig defaultServer = m_serversRepository->server(m_serversRepository->defaultServerIndex()); + DockerContainer container = defaultServer.defaultContainer(); IpcClient::withInterface([&](QSharedPointer iface) { switch (state) { @@ -80,29 +86,26 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) if (flushDns.waitForFinished() && flushDns.returnValue()) qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS"; else - qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes"; + qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS"; + if (!ContainerUtils::isAwgContainer(container) && container != DockerContainer::WireGuard) { + QString dns1 = m_vpnConfiguration.value(configKey::dns1).toString(); + QString dns2 = m_vpnConfiguration.value(configKey::dns2).toString(); - if (!ContainerProps::isAwgContainer(container) && - container != DockerContainer::WireGuard) { - QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); - QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString(); - - // TODO: add error code handling for all routeAddList (or rework the code below) iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2); - if (m_settings->isSitesSplitTunnelingEnabled()) { + if (m_appSettingsRepository->isSitesSplitTunnelingEnabled()) { iface->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0"); - // qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); - if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { + RouteMode routeMode = m_appSettingsRepository->routeMode(); + if (routeMode == amnezia::RouteMode::VpnOnlyForwardSites) { QTimer::singleShot(1000, m_vpnProtocol.data(), - [this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); }); - } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + [this, routeMode]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), routeMode); }); + } else if (routeMode == amnezia::RouteMode::VpnAllExceptSites) { iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1"); iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1"); iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress()); - addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode()); + addSitesRoutes(m_vpnProtocol->routeGateway(), routeMode); } } } @@ -143,12 +146,23 @@ const QString &VpnConnection::remoteAddress() const return m_remoteAddress; } -void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode) +void VpnConnection::setRepositories(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository) +{ + m_serversRepository = serversRepository; + m_appSettingsRepository = appSettingsRepository; +} + +void VpnConnection::addSitesRoutes(const QString &gw, amnezia::RouteMode mode) { #ifdef AMNEZIA_DESKTOP + if (!m_appSettingsRepository) { + qCritical() << "VpnConnection::addSitesRoutes: repositories not initialized"; + return; + } + QStringList ips; QStringList sites; - const QVariantMap &m = m_settings->vpnSites(mode); + const QVariantMap &m = m_appSettingsRepository->vpnSites(mode); for (auto i = m.constBegin(); i != m.constEnd(); ++i) { if (NetworkUtilities::checkIpSubnetFormat(i.key())) { ips.append(i.key()); @@ -178,7 +192,7 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode) IpcClient::withInterface([&gw, &ip](QSharedPointer iface) { iface->routeAddList(gw, QStringList() << ip); }); - m_settings->addVpnSite(mode, site, ip); + m_appSettingsRepository->addVpnSite(mode, site, ip); } IpcClient::withInterface([](QSharedPointer iface) { auto reply = iface->flushDns(); @@ -219,15 +233,25 @@ ErrorCode VpnConnection::lastError() const return m_vpnProtocol.data()->lastError(); } -void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &vpnConfiguration) +Vpn::ConnectionState VpnConnection::connectionState() const { + return m_connectionState; +} + +void VpnConnection::connectToVpn(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration) +{ + if (!m_appSettingsRepository || !m_serversRepository) { + qCritical() << "VpnConnection::connectToVpn: repositories not initialized"; + setConnectionState(Vpn::ConnectionState::Error); + return; + } + qDebug() << QString("Trying to connect to VPN, server index is %1, container is %2, route mode is") .arg(serverIndex) - .arg(ContainerProps::containerToString(container)) - << m_settings->routeMode(); + .arg(ContainerUtils::containerToString(container)) + << m_appSettingsRepository->routeMode(); - m_remoteAddress = NetworkUtilities::getIPAddress(credentials.hostName); + m_remoteAddress = NetworkUtilities::getIPAddress(vpnConfiguration.value(configKey::hostName).toString()); setConnectionState(Vpn::ConnectionState::Connecting); m_vpnConfiguration = vpnConfiguration; @@ -256,7 +280,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede m_vpnProtocol.reset(androidVpnProtocol); #elif defined Q_OS_IOS || defined(MACOS_NE) - Proto proto = ContainerProps::defaultProtocol(container); + Proto proto = ContainerUtils::defaultProtocol(container); IosController::Instance()->connectVpn(proto, m_vpnConfiguration); connect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus); return; @@ -286,25 +310,35 @@ void VpnConnection::createProtocolConnections() void VpnConnection::appendKillSwitchConfig() { - m_vpnConfiguration.insert(config_key::killSwitchOption, QVariant(m_settings->isKillSwitchEnabled()).toString()); - m_vpnConfiguration.insert(config_key::allowedDnsServers, QVariant(m_settings->allowedDnsServers()).toJsonValue()); + if (!m_appSettingsRepository) { + qCritical() << "VpnConnection::appendKillSwitchConfig: repositories not initialized"; + return; + } + + m_vpnConfiguration.insert(configKey::killSwitchOption, QVariant(m_appSettingsRepository->isKillSwitchEnabled()).toString()); + m_vpnConfiguration.insert(configKey::allowedDnsServers, QVariant(m_appSettingsRepository->getAllowedDnsServers()).toJsonValue()); } void VpnConnection::appendSplitTunnelingConfig() { + if (!m_appSettingsRepository) { + qCritical() << "VpnConnection::appendSplitTunnelingConfig: repositories not initialized"; + return; + } + bool allowSiteBasedSplitTunneling = true; // this block is for old native configs and for old self-hosted configs - auto protocolName = m_vpnConfiguration.value(config_key::vpnproto).toString(); - if (protocolName == ProtocolProps::protoToString(Proto::Awg) || protocolName == ProtocolProps::protoToString(Proto::WireGuard)) { + auto protocolName = m_vpnConfiguration.value(configKey::vpnProto).toString(); + if (protocolName == ProtocolUtils::protoToString(Proto::Awg) || protocolName == ProtocolUtils::protoToString(Proto::WireGuard)) { allowSiteBasedSplitTunneling = false; auto configData = m_vpnConfiguration.value(protocolName + "_config_data").toObject(); - if (configData.value(config_key::allowed_ips).isString()) { - QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configData.value(config_key::allowed_ips).toString().split(", ")); - configData.insert(config_key::allowed_ips, allowedIpsJsonArray); + if (configData.value(configKey::allowedIps).isString()) { + QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configData.value(configKey::allowedIps).toString().split(", ")); + configData.insert(configKey::allowedIps, allowedIpsJsonArray); m_vpnConfiguration.insert(protocolName + "_config_data", configData); - } else if (configData.value(config_key::allowed_ips).isUndefined()) { - auto nativeConfig = configData.value(config_key::config).toString(); + } else if (configData.value(configKey::allowedIps).isUndefined()) { + auto nativeConfig = configData.value(configKey::config).toString(); auto nativeConfigLines = nativeConfig.split("\n"); for (auto &line : nativeConfigLines) { if (line.contains("AllowedIPs")) { @@ -313,15 +347,15 @@ void VpnConnection::appendSplitTunnelingConfig() break; } QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(allowedIpsString.at(1).split(", ")); - configData.insert(config_key::allowed_ips, allowedIpsJsonArray); + configData.insert(configKey::allowedIps, allowedIpsJsonArray); m_vpnConfiguration.insert(protocolName + "_config_data", configData); break; } } } - if (configData.value(config_key::persistent_keep_alive).isUndefined()) { - auto nativeConfig = configData.value(config_key::config).toString(); + if (configData.value(configKey::persistentKeepAlive).isUndefined()) { + auto nativeConfig = configData.value(configKey::config).toString(); auto nativeConfigLines = nativeConfig.split("\n"); for (auto &line : nativeConfigLines) { if (line.contains("PersistentKeepalive")) { @@ -329,66 +363,75 @@ void VpnConnection::appendSplitTunnelingConfig() if (persistentKeepaliveString.size() < 1) { break; } - configData.insert(config_key::persistent_keep_alive, persistentKeepaliveString.at(1)); + configData.insert(configKey::persistentKeepAlive, persistentKeepaliveString.at(1)); m_vpnConfiguration.insert(protocolName + "_config_data", configData); break; } } } - QJsonArray allowedIpsJsonArray = configData.value(config_key::allowed_ips).toArray(); + QJsonArray allowedIpsJsonArray = configData.value(configKey::allowedIps).toArray(); if (allowedIpsJsonArray.contains("0.0.0.0/0") && allowedIpsJsonArray.contains("::/0")) { allowSiteBasedSplitTunneling = true; } } - Settings::RouteMode routeMode = Settings::RouteMode::VpnAllSites; + amnezia::RouteMode routeMode = amnezia::RouteMode::VpnAllSites; QJsonArray sitesJsonArray; - if (m_settings->isSitesSplitTunnelingEnabled()) { - routeMode = m_settings->routeMode(); + if (m_appSettingsRepository->isSitesSplitTunnelingEnabled()) { + routeMode = m_appSettingsRepository->routeMode(); if (allowSiteBasedSplitTunneling) { - auto sites = m_settings->getVpnIps(routeMode); + QStringList sites; + const QVariantMap &m = m_appSettingsRepository->vpnSites(routeMode); + for (auto i = m.constBegin(); i != m.constEnd(); ++i) { + if (NetworkUtilities::checkIpSubnetFormat(i.key())) { + sites.append(i.key()); + } else if (NetworkUtilities::checkIpSubnetFormat(i.value().toString())) { + sites.append(i.value().toString()); + } + } + sites.removeDuplicates(); for (const auto &site : sites) { sitesJsonArray.append(site); } if (sitesJsonArray.isEmpty()) { - routeMode = Settings::RouteMode::VpnAllSites; - } else if (routeMode == Settings::VpnOnlyForwardSites) { + routeMode = amnezia::RouteMode::VpnAllSites; + } else if (routeMode == amnezia::RouteMode::VpnOnlyForwardSites) { // Allow traffic to Amnezia DNS - sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString()); - sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString()); + sitesJsonArray.append(m_vpnConfiguration.value(configKey::dns1).toString()); + sitesJsonArray.append(m_vpnConfiguration.value(configKey::dns2).toString()); } } } - m_vpnConfiguration.insert(config_key::splitTunnelType, routeMode); - m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray); + m_vpnConfiguration.insert(configKey::splitTunnelType, routeMode); + m_vpnConfiguration.insert(configKey::splitTunnelSites, sitesJsonArray); - Settings::AppsRouteMode appsRouteMode = Settings::AppsRouteMode::VpnAllApps; + amnezia::AppsRouteMode appsRouteMode = amnezia::AppsRouteMode::VpnAllApps; QJsonArray appsJsonArray; - if (m_settings->isAppsSplitTunnelingEnabled()) { - appsRouteMode = m_settings->getAppsRouteMode(); + if (m_appSettingsRepository->isAppsSplitTunnelingEnabled()) { + appsRouteMode = m_appSettingsRepository->appsRouteMode(); - auto apps = m_settings->getVpnApps(appsRouteMode); + auto apps = m_appSettingsRepository->vpnApps(appsRouteMode); for (const auto &app : apps) { appsJsonArray.append(app.appPath.isEmpty() ? app.packageName : app.appPath); } if (appsJsonArray.isEmpty()) { - appsRouteMode = Settings::AppsRouteMode::VpnAllApps; + appsRouteMode = amnezia::AppsRouteMode::VpnAllApps; } } - m_vpnConfiguration.insert(config_key::appSplitTunnelType, appsRouteMode); - m_vpnConfiguration.insert(config_key::splitTunnelApps, appsJsonArray); + m_vpnConfiguration.insert(configKey::appSplitTunnelType, appsRouteMode); + m_vpnConfiguration.insert(configKey::splitTunnelApps, appsJsonArray); qDebug() << QString("Site split tunneling is %1, route mode is %2") - .arg(m_settings->isSitesSplitTunnelingEnabled() ? "enabled" : "disabled") + .arg(m_appSettingsRepository->isSitesSplitTunnelingEnabled() ? "enabled" : "disabled") .arg(routeMode); qDebug() << QString("App split tunneling is %1, route mode is %2") - .arg(m_settings->isAppsSplitTunnelingEnabled() ? "enabled" : "disabled") + .arg(m_appSettingsRepository->isAppsSplitTunnelingEnabled() ? "enabled" : "disabled") .arg(appsRouteMode); } diff --git a/client/vpnconnection.h b/client/vpnConnection.h similarity index 65% rename from client/vpnconnection.h rename to client/vpnConnection.h index 777573f26..0a6857447 100644 --- a/client/vpnconnection.h +++ b/client/vpnConnection.h @@ -8,16 +8,19 @@ #include #include -#include "protocols/vpnprotocol.h" -#include "core/defs.h" -#include "settings.h" +#include "core/protocols/vpnProtocol.h" +#include "core/utils/errorCodes.h" +#include "core/utils/routeModes.h" +#include "core/utils/commonStructs.h" +#include "core/repositories/secureServersRepository.h" +#include "core/repositories/secureAppSettingsRepository.h" #ifdef AMNEZIA_DESKTOP -#include "core/ipcclient.h" +#include "core/utils/ipcClient.h" #endif #ifdef Q_OS_ANDROID -#include "protocols/android_vpnprotocol.h" +#include "core/protocols/androidVpnProtocol.h" #endif using namespace amnezia; @@ -27,30 +30,34 @@ class VpnConnection : public QObject Q_OBJECT public: - explicit VpnConnection(std::shared_ptr settings, QObject* parent = nullptr); + explicit VpnConnection(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository, QObject* parent = nullptr); ~VpnConnection() override; static QString bytesPerSecToText(quint64 bytes); ErrorCode lastError() const; + Vpn::ConnectionState connectionState() const; QSharedPointer vpnProtocol() const; const QString &remoteAddress() const; - void addSitesRoutes(const QString &gw, Settings::RouteMode mode); + void addSitesRoutes(const QString &gw, amnezia::RouteMode mode); #ifdef Q_OS_ANDROID void restoreConnection(); #endif public slots: - void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration); + void setRepositories(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository); + void connectToVpn(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration); void reconnectToVpn(); void disconnectFromVpn(); void onKillSwitchModeChanged(bool enabled); void disconnectSlots(); + void setConnectionState(Vpn::ConnectionState state); + signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void connectionStateChanged(Vpn::ConnectionState state); @@ -62,13 +69,13 @@ protected slots: void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); void onConnectionStateChanged(Vpn::ConnectionState state); - void setConnectionState(Vpn::ConnectionState state); - protected: QSharedPointer m_vpnProtocol; private: - std::shared_ptr m_settings; + SecureServersRepository* m_serversRepository; + SecureAppSettingsRepository* m_appSettingsRepository; + QJsonObject m_vpnConfiguration; QJsonObject m_routeMode; QString m_remoteAddress; diff --git a/common/logger/logger.cpp b/common/logger/logger.cpp index 95592264d..3ef0219c0 100644 --- a/common/logger/logger.cpp +++ b/common/logger/logger.cpp @@ -9,11 +9,11 @@ #include #include -#include "utilities.h" +#include "core/utils/utilities.h" #include "version.h" #ifdef AMNEZIA_DESKTOP - #include + #include #endif #ifdef Q_OS_IOS @@ -137,7 +137,9 @@ QString Logger::serviceLogsFilePath() QString Logger::getLogFile() { - m_file.flush(); + if (m_file.isOpen()) { + m_file.flush(); + } QFile file(userLogsFilePath()); file.open(QIODevice::ReadOnly); @@ -152,7 +154,9 @@ QString Logger::getLogFile() QString Logger::getServiceLogFile() { - m_file.flush(); + if (m_file.isOpen()) { + m_file.flush(); + } QFile file(serviceLogsFilePath()); file.open(QIODevice::ReadOnly); diff --git a/ipc/ipc.h b/ipc/ipc.h index 8fca44369..9bda12ad6 100644 --- a/ipc/ipc.h +++ b/ipc/ipc.h @@ -4,7 +4,7 @@ #include #include -#include "../client/utilities.h" +#include "../client/core/utils/utilities.h" #define IPC_SERVICE_URL "local:AmneziaVpnIpcInterface" diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 63a3ec137..0febd3948 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -69,9 +69,9 @@ include_directories( ) set(HEADERS - ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.h - ${CMAKE_CURRENT_LIST_DIR}/../../client/secure_qsettings.h - ${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/core/utils/utilities.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/secureQSettings.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/core/utils/networkUtilities.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.h @@ -91,9 +91,9 @@ set(HEADERS ) set(SOURCES - ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.cpp - ${CMAKE_CURRENT_LIST_DIR}/../../client/secure_qsettings.cpp - ${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/core/utils/utilities.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/secureQSettings.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/core/utils/networkUtilities.cpp ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.cpp ${CMAKE_CURRENT_LIST_DIR}/localserver.cpp diff --git a/service/server/killswitch.cpp b/service/server/killswitch.cpp index a0e5c7e8a..78b7654f3 100644 --- a/service/server/killswitch.cpp +++ b/service/server/killswitch.cpp @@ -4,7 +4,10 @@ #include #include -#include "../client/protocols/protocols_defs.h" +#include "../client/core/utils/protocolEnum.h" +#include "../client/core/protocols/protocolUtils.h" +#include "../client/core/utils/constants/configKeys.h" +#include "../client/core/utils/constants/protocolConstants.h" #include "qjsonarray.h" #include "version.h" @@ -195,11 +198,11 @@ bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) { #ifdef Q_OS_WIN InterfaceConfig config; - config.m_primaryDnsServer = configStr.value(amnezia::config_key::dns1).toString(); + config.m_primaryDnsServer = configStr.value(amnezia::configKey::dns1).toString(); // We don't use secondary DNS if primary DNS is AmneziaDNS if (!config.m_primaryDnsServer.contains(amnezia::protocols::dns::amneziaDnsIp)) { - config.m_secondaryDnsServer = configStr.value(amnezia::config_key::dns2).toString(); + config.m_secondaryDnsServer = configStr.value(amnezia::configKey::dns2).toString(); } config.m_serverPublicKey = "openvpn"; @@ -237,14 +240,14 @@ bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) { } } - for (const QJsonValue &i : configStr.value(amnezia::config_key::splitTunnelApps).toArray()) { + for (const QJsonValue &i : configStr.value(amnezia::configKey::splitTunnelApps).toArray()) { if (!i.isString()) { break; } config.m_vpnDisabledApps.append(i.toString()); } - for (auto dns : configStr.value(amnezia::config_key::allowedDnsServers).toArray()) { + for (auto dns : configStr.value(amnezia::configKey::allowedDnsServers).toArray()) { if (!dns.isString()) { break; } @@ -252,7 +255,7 @@ bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) { } // killSwitch toggle - if (QVariant(configStr.value(amnezia::config_key::killSwitchOption).toString()).toBool()) { + if (QVariant(configStr.value(amnezia::configKey::killSwitchOption).toString()).toBool()) { WindowsFirewall::create(this)->enablePeerTraffic(config); } @@ -317,17 +320,17 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true); QStringList dnsServers; - dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); + dnsServers.append(configStr.value(amnezia::configKey::dns1).toString()); // We don't use secondary DNS if primary DNS is AmneziaDNS - if (!configStr.value(amnezia::config_key::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) { - dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + if (!configStr.value(amnezia::configKey::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) { + dnsServers.append(configStr.value(amnezia::configKey::dns2).toString()); } dnsServers.append("127.0.0.1"); dnsServers.append("127.0.0.53"); - for (auto dns : configStr.value(amnezia::config_key::allowedDnsServers).toArray()) { + for (auto dns : configStr.value(amnezia::configKey::allowedDnsServers).toArray()) { if (!dns.isString()) { break; } @@ -359,14 +362,14 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); QStringList dnsServers; - dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); + dnsServers.append(configStr.value(amnezia::configKey::dns1).toString()); // We don't use secondary DNS if primary DNS is AmneziaDNS - if (!configStr.value(amnezia::config_key::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) { - dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + if (!configStr.value(amnezia::configKey::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) { + dnsServers.append(configStr.value(amnezia::configKey::dns2).toString()); } - for (auto dns : configStr.value(amnezia::config_key::allowedDnsServers).toArray()) { + for (auto dns : configStr.value(amnezia::configKey::allowedDnsServers).toArray()) { if (!dns.isString()) { break; } diff --git a/service/server/killswitch.h b/service/server/killswitch.h index 519e2ed2a..70ea3ced7 100644 --- a/service/server/killswitch.h +++ b/service/server/killswitch.h @@ -4,7 +4,7 @@ #include #include -#include "secure_qsettings.h" +#include "secureQSettings.h" class KillSwitch : public QObject { diff --git a/service/server/main.cpp b/service/server/main.cpp index cee33d727..f119c2fc5 100644 --- a/service/server/main.cpp +++ b/service/server/main.cpp @@ -4,7 +4,7 @@ #include "localserver.h" #include "logger.h" #include "systemservice.h" -#include "utilities.h" +#include "core/utils/utilities.h" #ifdef Q_OS_WIN #include "platforms/windows/daemon/windowsdaemontunnel.h" diff --git a/service/server/router_linux.cpp b/service/server/router_linux.cpp index 3f8a7f6e9..44f0e17d4 100644 --- a/service/server/router_linux.cpp +++ b/service/server/router_linux.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -20,7 +20,7 @@ #include #include -#include +#include RouterLinux &RouterLinux::Instance() { diff --git a/service/server/router_mac.cpp b/service/server/router_mac.cpp index f56e3f9f6..e0eb9fd0a 100644 --- a/service/server/router_mac.cpp +++ b/service/server/router_mac.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include RouterMac &RouterMac::Instance() { diff --git a/service/server/router_win.cpp b/service/server/router_win.cpp index 6cb2e38ee..5f8c4406b 100644 --- a/service/server/router_win.cpp +++ b/service/server/router_win.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include LONG (NTAPI * NtSuspendProcess)(HANDLE ProcessHandle) = NULL; LONG (NTAPI * NtResumeProcess)(HANDLE ProcessHandle) = NULL; diff --git a/service/server/xray.cpp b/service/server/xray.cpp index b0408d49a..5d324014e 100644 --- a/service/server/xray.cpp +++ b/service/server/xray.cpp @@ -1,5 +1,5 @@ #include "xray.h" -#include "core/networkUtilities.h" +#include "core/utils/networkUtilities.h" #include #include