diff --git a/src/ProtonVPN.App/App.config b/src/ProtonVPN.App/App.config index 8a3ae4b87..5e9c93504 100644 --- a/src/ProtonVPN.App/App.config +++ b/src/ProtonVPN.App/App.config @@ -370,6 +370,12 @@ <setting name="ConnectOnAppStart" serializeAs="String"> <value>True</value> </setting> + <setting name="ConnectOnInsecureWifi" serializeAs="String"> + <value>True</value> + </setting> + <setting name="SecureDisconnect" serializeAs="String"> + <value>True</value> + </setting> <setting name="StartOnBoot" serializeAs="String"> <value>True</value> </setting> diff --git a/src/ProtonVPN.App/BugReporting/Diagnostic/UserSettingsLog.cs b/src/ProtonVPN.App/BugReporting/Diagnostic/UserSettingsLog.cs index 321f373bc..7e7adb956 100644 --- a/src/ProtonVPN.App/BugReporting/Diagnostic/UserSettingsLog.cs +++ b/src/ProtonVPN.App/BugReporting/Diagnostic/UserSettingsLog.cs @@ -71,6 +71,7 @@ private IEnumerable<KeyValuePair<string, dynamic>> GetProperties() yield return new(nameof(IAppSettings.AppFirstRun), _appSettings.AppFirstRun); yield return new(nameof(IAppSettings.ShowNotifications), _appSettings.ShowNotifications); yield return new(nameof(IAppSettings.ConnectOnAppStart), _appSettings.ConnectOnAppStart); + yield return new(nameof(IAppSettings.ConnectOnInsecureWifi), _appSettings.ConnectOnInsecureWifi); yield return new(nameof(IAppSettings.QuickConnect), _appSettings.QuickConnect); yield return new(nameof(IAppSettings.StartOnBoot), _appSettings.StartOnBoot); yield return new(nameof(IAppSettings.StartMinimized), _appSettings.StartMinimized); diff --git a/src/ProtonVPN.App/Core/AppSettings.cs b/src/ProtonVPN.App/Core/AppSettings.cs index 0ac2ead78..59e75b92f 100644 --- a/src/ProtonVPN.App/Core/AppSettings.cs +++ b/src/ProtonVPN.App/Core/AppSettings.cs @@ -498,6 +498,18 @@ public bool ConnectOnAppStart set => Set(value); } + public bool ConnectOnInsecureWifi + { + get => Get<bool>(); + set => Set(value); + } + + public bool SecureDisconnect + { + get => Get<bool>(); + set => Set(value); + } + [Obsolete( "Use this only for checking if the user enabled/disabled the feature." + "Use IsSmartReconnectEnabled() for checking if Smart Reconnect is/should be enabled.")] diff --git a/src/ProtonVPN.App/Core/AutoConnect.cs b/src/ProtonVPN.App/Core/AutoConnect.cs index 464f376c0..4a44812bd 100644 --- a/src/ProtonVPN.App/Core/AutoConnect.cs +++ b/src/ProtonVPN.App/Core/AutoConnect.cs @@ -26,24 +26,31 @@ using ProtonVPN.Core.Service.Vpn; using ProtonVPN.Core.Settings; using ProtonVPN.Core.Vpn; +using ProtonVPN.Core.Network; namespace ProtonVPN.Core { internal class AutoConnect : IVpnStateAware { private readonly IAppSettings _appSettings; + private readonly INetworkClient _networkClient; private readonly IVpnManager _vpnManager; private readonly ILogger _logger; private VpnStatus _vpnStatus; + private bool _connectedToInsecureWifi; public AutoConnect( IAppSettings appSettings, + INetworkClient networkClient, IVpnManager vpnManager, ILogger logger) { _appSettings = appSettings; + _networkClient = networkClient; _vpnManager = vpnManager; _logger = logger; + + _networkClient.WifiChangeDetected += OnWifiChangeDetected; } public async Task LoadAsync(bool autoLogin) @@ -75,5 +82,53 @@ public Task OnVpnStateChanged(VpnStateChangedEventArgs e) return Task.CompletedTask; } + + private bool InsecureWifiAutoConnectionRequired(bool isSecure) + { + return !isSecure && _vpnStatus.Equals(VpnStatus.Disconnected) && _appSettings.ConnectOnInsecureWifi; + } + private bool InsecureWifiSecureDisconnectRequired(bool isSecure) + { + return isSecure && _vpnStatus.Equals(VpnStatus.Connected) && _connectedToInsecureWifi && _appSettings.SecureDisconnect; + } + + private void OnWifiChangeDetected(object sender, WifiChangeEventArgs e) + { + if (_appSettings.ConnectOnInsecureWifi) + { + if (InsecureWifiSecureDisconnectRequired(e.Secure)) + { + Task.Factory.StartNew(async () => + { + try + { + _logger.Info<ConnectTriggerLog>("Automatically disconnecting on secure wifi"); + await _vpnManager.DisconnectAsync(); + } + catch (OperationCanceledException ex) + { + _logger.Error<AppLog>("An error occurred when disconnecting automatically on secure wifi.", ex); + } + }); + } + else if (InsecureWifiAutoConnectionRequired(e.Secure)) + { + Task.Factory.StartNew(async () => + { + try + { + _logger.Info<ConnectTriggerLog>("Automatically connecting on insecure wifi"); + await _vpnManager.QuickConnectAsync(); + } + catch (OperationCanceledException ex) + { + _logger.Error<AppLog>("An error occurred when connecting automatically on insecure wifi.", ex); + } + }); + } + } + + _connectedToInsecureWifi = !e.Secure; + } } } \ No newline at end of file diff --git a/src/ProtonVPN.App/Properties/Settings.Designer.cs b/src/ProtonVPN.App/Properties/Settings.Designer.cs index 28fb95f79..05e0f2152 100644 --- a/src/ProtonVPN.App/Properties/Settings.Designer.cs +++ b/src/ProtonVPN.App/Properties/Settings.Designer.cs @@ -1458,6 +1458,30 @@ public bool ConnectOnAppStart { } } + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ConnectOnInsecureWifi { + get { + return ((bool)(this["ConnectOnInsecureWifi"])); + } + set { + this["ConnectOnInsecureWifi"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SecureDisconnect { + get { + return ((bool)(this["SecureDisconnect"])); + } + set { + this["SecureDisconnect"] = value; + } + } + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("True")] diff --git a/src/ProtonVPN.App/Properties/Settings.settings b/src/ProtonVPN.App/Properties/Settings.settings index 276ed6166..d4532e1bb 100644 --- a/src/ProtonVPN.App/Properties/Settings.settings +++ b/src/ProtonVPN.App/Properties/Settings.settings @@ -362,6 +362,12 @@ <Setting Name="ConnectOnAppStart" Type="System.Boolean" Scope="User"> <Value Profile="(Default)">True</Value> </Setting> + <Setting Name="ConnectOnInsecureWifi" Type="System.Boolean" Scope="User"> + <Value Profile="(Default)">True</Value> + </Setting> + <Setting Name="SecureDisconnect" Type="System.Boolean" Scope="User"> + <Value Profile="(Default)">True</Value> + </Setting> <Setting Name="StartOnBoot" Type="System.Boolean" Scope="User"> <Value Profile="(Default)">True</Value> </Setting> diff --git a/src/ProtonVPN.App/Settings/Migrations/v1_27_1/AppSettingsMigration.cs b/src/ProtonVPN.App/Settings/Migrations/v1_27_1/AppSettingsMigration.cs index 72793e1d8..78c15790d 100644 --- a/src/ProtonVPN.App/Settings/Migrations/v1_27_1/AppSettingsMigration.cs +++ b/src/ProtonVPN.App/Settings/Migrations/v1_27_1/AppSettingsMigration.cs @@ -28,6 +28,8 @@ namespace ProtonVPN.Settings.Migrations.v1_27_1 internal class AppSettingsMigration : BaseAppSettingsMigration { private const string UserAutoConnectKey = "UserAutoConnect"; + private const string UserAutoConnectOnInsecureWifiKey = "ConnectOnInsecureWifi"; + private const string UserSecureDisconnectKey = "SecureDisconnect"; private const string StartOnStartupKey = "StartOnStartup"; private readonly InitialAppSettingsMigration _initialAppSettingsMigration; @@ -53,6 +55,18 @@ private void MigrateAutoConnect() bool autoConnect = autoConnectSettings.Any(setting => !setting.Value.IsNullOrEmpty()); Settings.Set(nameof(IAppSettings.ConnectOnAppStart), _initialAppSettingsMigration.IsCleanInstall || autoConnect); } + PerUser<string>[] autoConnectOnInsecureSettings = Settings.Get<PerUser<string>[]>(UserAutoConnectOnInsecureWifiKey); + if (autoConnectOnInsecureSettings != null) + { + bool autoConnect = autoConnectOnInsecureSettings.Any(setting => !setting.Value.IsNullOrEmpty()); + Settings.Set(nameof(IAppSettings.ConnectOnInsecureWifi), _initialAppSettingsMigration.IsCleanInstall || autoConnect); + } + PerUser<string>[] secureDisconnectSettings = Settings.Get<PerUser<string>[]>(UserSecureDisconnectKey); + if (secureDisconnectSettings != null) + { + bool secureDisconnect = secureDisconnectSettings.Any(setting => !setting.Value.IsNullOrEmpty()); + Settings.Set(nameof(IAppSettings.SecureDisconnect), _initialAppSettingsMigration.IsCleanInstall || secureDisconnect); + } } private void MigrateStartOnStartup() diff --git a/src/ProtonVPN.App/Settings/SettingsModalView.xaml b/src/ProtonVPN.App/Settings/SettingsModalView.xaml index e8147b6f4..1aea47632 100644 --- a/src/ProtonVPN.App/Settings/SettingsModalView.xaml +++ b/src/ProtonVPN.App/Settings/SettingsModalView.xaml @@ -210,6 +210,40 @@ along with ProtonVPN. If not, see <https://www.gnu.org/licenses/>. </DockPanel> </Border> + <Border Style="{StaticResource BorderlessSettingContainer}"> + <DockPanel> + <StackPanel Orientation="Horizontal"> + <Label Style="{StaticResource SettingName}" + Content="{translations:Loc Settings_General_lbl_ConnectOnInsecureWifi}" /> + <icons:InfoCircleFilled Style="{StaticResource InfoCircle}" + ToolTip="{translations:Loc Settings_General_lbl_ConnectOnInsecureWifi_Info}"/> + </StackPanel> + <CheckBox Margin="0,0,14,0" + HorizontalAlignment="Right" + Style="{StaticResource ToggleSwitch}" + IsChecked="{Binding ConnectOnInsecureWifi}" + AutomationProperties.AutomationId="{StaticResource ConnectOnInsecureWifiCheckbox}" /> + </DockPanel> + </Border> + + <Border Style="{StaticResource BorderSettingContainer}"> + <DockPanel> + <StackPanel Orientation="Horizontal"> + <TextBlock Style="{StaticResource SubSettingIcon}" /> + <Label Style="{StaticResource SettingName}" Margin="5,0,0,0" + Content="{translations:Loc Settings_General_lbl_SecureDisconnect}"/> + <icons:InfoCircleFilled Style="{StaticResource InfoCircle}" + ToolTip="{translations:Loc Settings_General_lbl_SecureDisconnect_Info}"/> + </StackPanel> + + <CheckBox Style="{StaticResource ToggleSwitch}" + Margin="0,0,14,0" + HorizontalAlignment="Right" + IsChecked="{Binding SecureDisconnect}" + AutomationProperties.AutomationId="{StaticResource SecureDisconnectCheckbox}"/> + </DockPanel> + </Border> + <Border Style="{StaticResource BorderSettingContainer}"> <DockPanel> <Label Style="{StaticResource SettingName}" Content="{translations:Loc Settings_General_lbl_ShowNotifications}"/> diff --git a/src/ProtonVPN.App/Settings/SettingsModalViewModel.cs b/src/ProtonVPN.App/Settings/SettingsModalViewModel.cs index 430e4a46d..e191505ba 100644 --- a/src/ProtonVPN.App/Settings/SettingsModalViewModel.cs +++ b/src/ProtonVPN.App/Settings/SettingsModalViewModel.cs @@ -439,6 +439,18 @@ public bool ConnectOnAppStart set => _appSettings.ConnectOnAppStart = value; } + public bool ConnectOnInsecureWifi + { + get => _appSettings.ConnectOnInsecureWifi; + set => _appSettings.ConnectOnInsecureWifi = value; + } + + public bool SecureDisconnect + { + get => _appSettings.SecureDisconnect; + set => _appSettings.SecureDisconnect = value; + } + public bool ShowNotifications { get => _appSettings.ShowNotifications; diff --git a/src/ProtonVPN.Core/ProtonVPN.Core.csproj b/src/ProtonVPN.Core/ProtonVPN.Core.csproj index 7e532cf41..6e03d26d9 100644 --- a/src/ProtonVPN.Core/ProtonVPN.Core.csproj +++ b/src/ProtonVPN.Core/ProtonVPN.Core.csproj @@ -32,6 +32,7 @@ <PackageReference Include="Newtonsoft.Json"> <Version>13.0.3</Version> </PackageReference> + <PackageReference Include="Srp.ProtonMail" Version="0.1.0" /> <PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" /> <PackageReference Include="System.Net.Http" Version="4.3.4" /> <PackageReference Include="Caliburn.Micro.Core" Version="4.0.212" /> diff --git a/src/ProtonVPN.Core/Settings/IAppSettings.cs b/src/ProtonVPN.Core/Settings/IAppSettings.cs index 8c3e9ee7d..c366c14eb 100644 --- a/src/ProtonVPN.Core/Settings/IAppSettings.cs +++ b/src/ProtonVPN.Core/Settings/IAppSettings.cs @@ -106,6 +106,8 @@ public interface IAppSettings bool FeaturePromoCodeEnabled { get; set; } bool FeatureFreeRescopeEnabled { get; set; } bool ConnectOnAppStart { get; set; } + bool ConnectOnInsecureWifi { get; set; } + bool SecureDisconnect { get; set; } bool FeatureSmartReconnectEnabled { get; set; } bool ShowNonStandardPortsToFreeUsers { get; set; } bool SmartReconnectEnabled { get; set; } diff --git a/src/ProtonVPN.Resources/Automation/Config.xaml b/src/ProtonVPN.Resources/Automation/Config.xaml index 7f12b5119..98e5e98d7 100644 --- a/src/ProtonVPN.Resources/Automation/Config.xaml +++ b/src/ProtonVPN.Resources/Automation/Config.xaml @@ -97,6 +97,8 @@ along with ProtonVPN. If not, see <https://www.gnu.org/licenses/>. <system:String x:Key="PortForwardingOnButton">PortForwardingOnButton</system:String> <system:String x:Key="PortForwardingOffButton">PortForwardingOffButton</system:String> <system:String x:Key="ConnectOnBootCheckbox">ConnectOnBootCheckbox</system:String> + <system:String x:Key="ConnectOnInsecureWifiCheckbox">ConnectOnInsecureWifi</system:String> + <system:String x:Key="SecureDisconnectCheckbox">SecureDisconnect</system:String> <system:String x:Key="CancelActionButton">CancelActionButton</system:String> <system:String x:Key="QuickLaunchFlag">QuickLaunchFlag</system:String> <system:String x:Key="UpsellModalTitle">UpsellModalTitle</system:String> diff --git a/src/ProtonVPN.Translations/Properties/Resources.Designer.cs b/src/ProtonVPN.Translations/Properties/Resources.Designer.cs index 9117df9cd..b37884876 100644 --- a/src/ProtonVPN.Translations/Properties/Resources.Designer.cs +++ b/src/ProtonVPN.Translations/Properties/Resources.Designer.cs @@ -6241,7 +6241,7 @@ public static string Settings_General_lbl_ConnectOnAppStart { } /// <summary> - /// Looks up a localized string similar to Automatically connect to the Quick Connect profile when Proton VPN starts.. + /// Looks up a localized string similar to Automatically connect to the Quick Connect profile when Proton VPN starts. /// </summary> public static string Settings_General_lbl_ConnectOnAppStart_Info { get { @@ -6249,6 +6249,42 @@ public static string Settings_General_lbl_ConnectOnAppStart_Info { } } + /// <summary> + /// Looks up a localized string similar to Connect on insecure wifi. + /// </summary> + public static string Settings_General_lbl_ConnectOnInsecureWifi { + get { + return ResourceManager.GetString("Settings_General_lbl_ConnectOnInsecureWifi", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Automatically connect to the Quick Connect profile when connecting to inseucre wifi. + /// </summary> + public static string Settings_General_lbl_ConnectOnInsecureWifi_Info { + get { + return ResourceManager.GetString("Settings_General_lbl_ConnectOnInsecureWifi_Info", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Disconnect on secured wifi. + /// </summary> + public static string Settings_General_lbl_SecureDisconnect { + get { + return ResourceManager.GetString("Settings_General_lbl_SecureDisconnect", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Disconnect when connecting to secure wifi. + /// </summary> + public static string Settings_General_lbl_SecureDisconnect_Info { + get { + return ResourceManager.GetString("Settings_General_lbl_SecureDisconnect_Info", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Early Access. /// </summary> diff --git a/src/ProtonVPN.Translations/Properties/Resources.en-US.resx b/src/ProtonVPN.Translations/Properties/Resources.en-US.resx index 9ae4b53ad..1e870d837 100644 --- a/src/ProtonVPN.Translations/Properties/Resources.en-US.resx +++ b/src/ProtonVPN.Translations/Properties/Resources.en-US.resx @@ -277,6 +277,14 @@ <value>Automatically connect to the Quick Connect profile when Proton VPN starts.</value> <comment>The description of Connect on boot setting displayed in the tooltip of ( i ) image next to the label for Connect on boot combo box in General tab of Settings window</comment> </data> + <data name="Settings_General_lbl_ConnectOnInsecureWifi_Info" xml:space="preserve"> + <value>Automatically connect to the Quick Connect profile when connecting to an insecure wifi.</value> + <comment>The description of Connect on insecure wifi setting displayed in the tooltip of ( i ) image next to the label for Connect on insecure wifi combo box in General tab of Settings window</comment> + </data> + <data name="Settings_General_lbl_SecureDisconnect_Info" xml:space="preserve"> + <value>Automatically disconnect once connected to a secure wifi.</value> + <comment>The description of Connect on insecure wifi setting displayed in the tooltip of ( i ) image next to the label for Disconnect on secure wifi combo box in General tab of Settings window</comment> + </data> <data name="StartMinimizedMode_val_Disabled" xml:space="preserve"> <value>Disabled</value> <comment>The name of start minimized mode displayed in Start Minimized combo box in General tab of Settings window</comment> @@ -709,6 +717,14 @@ This setting instructs the Proton VPN to start automatically when the user logs <value>Connect on app start</value> <comment>The label for Connect on boot combo box in General tab of Settings window</comment> </data> + <data name="Settings_General_lbl_ConnectOnInsecureWifi" xml:space="preserve"> + <value>Connect when connected to an insecure wifi</value> + <comment>The label for Connect on insecure wifi combo box in General tab of Settings window</comment> + </data> + <data name="Settings_General_lbl_SecureDisconnect" xml:space="preserve"> + <value>Disconnect on secured wifi</value> + <comment>The label for Disconnecting when connected to a secured wifi combo box in General tab of Settings window</comment> + </data> <data name="About_lbl_ChangelogVersion" xml:space="preserve"> <value>Changelog of v.{0}</value> <comment>The title of app version in change log in About window. The {0} is a placeholder for app version number.</comment> diff --git a/src/Tests/ProtonVPN.UI.Tests/Windows/SettingsWindow.cs b/src/Tests/ProtonVPN.UI.Tests/Windows/SettingsWindow.cs index 6ea8b9792..252352f61 100644 --- a/src/Tests/ProtonVPN.UI.Tests/Windows/SettingsWindow.cs +++ b/src/Tests/ProtonVPN.UI.Tests/Windows/SettingsWindow.cs @@ -29,6 +29,8 @@ public class SettingsWindow : UIActions private ListBoxItem OptionDisabled => StartMinimizedComboBox.FindFirstChild().AsListBoxItem(); private Button SettingsCloseButton => ElementByAutomationId("ModalCloseButton").AsButton(); private CheckBox ConnectOnBootCheckBox => ElementByAutomationId("ConnectOnBootCheckbox").AsCheckBox(); + private CheckBox ConnectOnInsecureWifiCheckBox => ElementByAutomationId("ConnectOnInsecureWifiCheckbox").AsCheckBox(); + private CheckBox SecureDisconnectCheckBox => ElementByAutomationId("SecureDisconnectCheckbox").AsCheckBox(); private AutomationElement ConnectionTab => ElementByName("Connection").FindFirstChild(); private AutomationElement AdvancedTab => ElementByName("Advanced").FindFirstChild(); private CheckBox CustomDnsCheckBox => ElementByAutomationId("CheckBoxCustomDnsServers").AsCheckBox(); @@ -55,6 +57,18 @@ public SettingsWindow ClickOnConnectOnBoot() return this; } + public SettingsWindow ClickOnConnectOnInsecureWifi() + { + ConnectOnInsecureWifiCheckBox.Click(); + return this; + } + + public SettingsWindow ClickOnSecureDisconnect() + { + SecureDisconnectCheckBox.Click(); + return this; + } + public HomeWindow CloseSettings() { SettingsCloseButton.Invoke();