From 6f380ecc6f20fd31495c464ad482f9ba33f950da Mon Sep 17 00:00:00 2001 From: Renish Nair Date: Fri, 8 Sep 2023 15:34:59 +0530 Subject: [PATCH] Added Split tunneling feature. Changes in UI as well as updated VPNSDK version to 2.2.30 --- Example/App.xaml | 2 + Example/Converters/BooleanInverseConverter.cs | 42 +++ Example/Example.csproj | 14 +- Example/ViewModel/MainViewModel.cs | 5 + Example/ViewModel/SplitTunnelingViewModel.cs | 248 ++++++++++++++++++ Example/Views/SplitTunnelingView.xaml | 182 +++++++++++++ Example/Views/SplitTunnelingView.xaml.cs | 24 ++ 7 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 Example/Converters/BooleanInverseConverter.cs create mode 100644 Example/ViewModel/SplitTunnelingViewModel.cs create mode 100644 Example/Views/SplitTunnelingView.xaml create mode 100644 Example/Views/SplitTunnelingView.xaml.cs diff --git a/Example/App.xaml b/Example/App.xaml index 54f1c63..3db0e1c 100644 --- a/Example/App.xaml +++ b/Example/App.xaml @@ -23,6 +23,8 @@ x:Key="BoolToColorConverter" FalseBrush="Gray" TrueBrush="White" /> + diff --git a/Example/Converters/BooleanInverseConverter.cs b/Example/Converters/BooleanInverseConverter.cs new file mode 100644 index 0000000..19e260b --- /dev/null +++ b/Example/Converters/BooleanInverseConverter.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) NetProtect, LLC. All Rights Reserved. +// + +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Example.Converters +{ + /// + /// Convert a boolean to boolean inverted. + /// + public class BooleanInverseConverter : IValueConverter + { + /// + /// Convetrs boolean to inverse. + /// + /// bool value input from the binding. + /// the return type. + /// any additional info needed. + /// users culture. + /// the converted value. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return !(bool)value; + } + + /// + /// converts the value back to a orignal bool alue. + /// + /// value from the binding. + /// type to convert to. + /// any additional info needed. + /// Users Culture. + /// the converted value. + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Example/Example.csproj b/Example/Example.csproj index c80a463..9a990ef 100644 --- a/Example/Example.csproj +++ b/Example/Example.csproj @@ -72,6 +72,7 @@ MSBuild:Compile Designer + @@ -93,6 +94,7 @@ + LoginDialogView.xaml @@ -112,6 +114,9 @@ DestinationsView.xaml + + SplitTunnelingView.xaml + Designer MSBuild:Compile @@ -156,6 +161,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + @@ -201,6 +210,9 @@ runtime; build; native; contentfiles; analyzers all + + 1.1.39 + 2.5.1 @@ -231,7 +243,7 @@ 1.3.7 - 2.2.29 + 2.2.30 diff --git a/Example/ViewModel/MainViewModel.cs b/Example/ViewModel/MainViewModel.cs index c857a17..b6e8986 100644 --- a/Example/ViewModel/MainViewModel.cs +++ b/Example/ViewModel/MainViewModel.cs @@ -130,6 +130,11 @@ private void InitViews() View = new ConnectionSettingsView() }); AvailableViews.Add(new ViewDefinition + { + DisplayName = "Split Tunneling", + View = new SplitTunnelingView() + }); + AvailableViews.Add(new ViewDefinition { DisplayName = "Information", View = new InformationView() diff --git a/Example/ViewModel/SplitTunnelingViewModel.cs b/Example/ViewModel/SplitTunnelingViewModel.cs new file mode 100644 index 0000000..f1fc1b7 --- /dev/null +++ b/Example/ViewModel/SplitTunnelingViewModel.cs @@ -0,0 +1,248 @@ +// +// Copyright (c) NetProtect, LLC. All Rights Reserved. +// + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Forms; +using System.Windows.Input; +using Example.Helpers; +using VpnSDK.Common.Enums; +using VpnSDK.Common.Settings; +using VpnSDK.Interfaces; + +namespace Example.ViewModel +{ + /// + /// SplitTunnelingViewModel + /// + public class SplitTunnelingViewModel : BindableBase + { + private const string SupportedExtension = ".exe"; + private const string UnsupportedApp = "Example"; + + private ICommand _addDomainCommand; + private ICommand _pickManuallyCommand; + private ICommand _deleteDomainCommand; + private ICommand _deleteApplicationCommand; + private ICommand _includeSubDomainsCommand; + private ICommand _excludeSubDomainsCommand; + private string _domainName; + private string _validationError; + + /// + /// Initializes a new instance of the class. + /// + public SplitTunnelingViewModel() + { + SDK.SplitTunnelMode = SplitTunnelMode.RouteSelectedTrafficOutsideVpn; + SDK.SplitTunnelAllowedApps = new List(); + SDK.SplitTunnelAllowedDomains = new List(); + Applications = new ObservableCollection(SDK.SplitTunnelAllowedApps); + Domains = new ObservableCollection(SDK.SplitTunnelAllowedDomains); + } + + /// + /// Gets or sets a value indicating whether split tunneling feature is on or off. + /// + public bool IsSplitTunnelingOn + { + get => SDK.IsSplitTunnelEnabled; + set + { + SDK.IsSplitTunnelEnabled = value; + } + } + + /// + /// Gets SDK Instance. + /// + public ISDK SDK => SDKFactory.GetInstance(); + + /// + /// Gets or sets Domain name. + /// + public string DomainName { get => _domainName; set => SetProperty(ref _domainName, value); } + + /// + /// Gets or sets error message while adding new domain. + /// + public string ValidationError { get => _validationError; set => SetProperty(ref _validationError, value); } + + /// + /// Gets or sets domain list. + /// + public ObservableCollection Domains { get; set; } + + /// + /// Gets or sets application list. + /// + public ObservableCollection Applications { get; set; } + + /// + /// Gets a value that represents the command to execute when the user add domains to split tunneling. + /// + public ICommand AddDomainCommand => _addDomainCommand ?? (_addDomainCommand = new RelayCommand( + execute: (p) => AddDomain())); + + /// + /// Gets a value that represents the command to execute when the user add domains to split tunneling. + /// + public ICommand PickManuallyCommand => _pickManuallyCommand ?? (_pickManuallyCommand = new RelayCommand( + execute: (p) => PickManually())); + + /// + /// Gets a value that represents the command to execute when the user delete domain from split tunneling. + /// + public ICommand DeleteDomainCommand => _deleteDomainCommand ?? (_deleteDomainCommand = new RelayCommand( + execute: (p) => DeleteDomain(p as SplitTunnelDomain))); + + /// + /// Gets a value that represents the command to execute when the user delete domain from split tunneling. + /// + public ICommand DeleteApplicationCommand => _deleteApplicationCommand ?? (_deleteApplicationCommand = new RelayCommand( + execute: (p) => DeleteApplication(p as SplitTunnelApp))); + + /// + /// Gets a value that represents the command to execute when the user include all subdomains for given domain in ST filter. + /// + public ICommand IncludeSubDomainsCommand => _includeSubDomainsCommand ?? (_includeSubDomainsCommand = new RelayCommand( + execute: (p) => UpdateIncludeSubDomains(true, p as SplitTunnelDomain))); + + /// + /// Gets a value that represents the command to execute when the user exclude all subdomains for given domain in ST filter. + /// + public ICommand ExcludeSubDomainsCommand => _excludeSubDomainsCommand ?? (_excludeSubDomainsCommand = new RelayCommand( + execute: (p) => UpdateIncludeSubDomains(false, p as SplitTunnelDomain))); + + /// + /// Deletes application from split tunneling filter + /// + /// Application that need to be removed from split tunneling. + public void DeleteApplication(SplitTunnelApp application) + { + try + { + Applications.Remove(application); + SDK.SplitTunnelAllowedApps.Remove(application); + } + catch (Exception) + { + // ignore. + } + } + + /// + /// Deletes domain from split tunneling filter + /// + /// Domain that need to be removed from split tunneling. + public void DeleteDomain(SplitTunnelDomain domain) + { + try + { + Domains.Remove(domain); + SDK.SplitTunnelAllowedDomains.Remove(domain); + } + catch (Exception) + { + // ignore. + } + } + + /// + /// Updates include subdomain property for domain added to split tunneling. + /// + /// bool value determining whether to include subdomains in split tunneling filter or not. + /// domain that needs to update includesubdomain property. + public void UpdateIncludeSubDomains(bool includeAllSubDomains, SplitTunnelDomain domain) + { + domain.IncludeAllSubdomains = includeAllSubDomains; + } + + /// + /// Adds new domain to split tunneling filter. + /// + public void AddDomain() + { + try + { + ValidationError = string.Empty; + var domain = new SplitTunnelDomain(DomainName); + if (Domains.Any(d => d.DomainName.Equals(domain.DomainName, StringComparison.OrdinalIgnoreCase))) + { + ValidationError = "This domain already exists, please enter a different domain."; + return; + } + + Domains.Add(domain); + SDK.SplitTunnelAllowedDomains.Add(domain); + DomainName = string.Empty; + } + catch (Exception ex) + { + ValidationError = ex.Message; + } + } + + /// + /// Opens up file picker dialog from which user can select application. + /// + public void PickManually() + { + var filter = string.Join("|", SupportedExtension + .Select(_ => $"Executable (*{SupportedExtension})|*{SupportedExtension}")); + + // Create OpenFileDialog + OpenFileDialog openFileDlg = new OpenFileDialog() + { + InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), + DefaultExt = SupportedExtension, + Filter = filter, + CheckFileExists = true, + Title = "Add Applications", + CheckPathExists = true, + Multiselect = true, + }; + openFileDlg.FileOk += OpenFileDlg_FileOk; + + openFileDlg.ShowDialog(); + } + + private void OpenFileDlg_FileOk(object sender, CancelEventArgs e) + { + if (sender is OpenFileDialog openFileDlg) + { + foreach (var filePath in openFileDlg.FileNames) + { + var extension = Path.GetExtension(filePath); + var fileName = Path.GetFileNameWithoutExtension(filePath); + if (!SupportedExtension.Equals(extension, System.StringComparison.OrdinalIgnoreCase) || + UnsupportedApp.Equals(fileName, System.StringComparison.OrdinalIgnoreCase)) + { + var message = string.Format("Unsupported file chosen: {0}", fileName); + System.Windows.MessageBox.Show(message, "Invalid Application", MessageBoxButton.OK, MessageBoxImage.Error); + e.Cancel = true; + continue; + } + + if (Applications.Any(app => app.Path == filePath)) + { + var message = string.Format("Application already added: {0}", fileName); + System.Windows.MessageBox.Show(message, "Duplicate Application", MessageBoxButton.OK, MessageBoxImage.Error); + e.Cancel = true; + continue; + } + + SplitTunnelApp splitTunnelApp = new SplitTunnelApp(fileName, filePath); + Applications.Add(splitTunnelApp); + SDK.SplitTunnelAllowedApps.Add(splitTunnelApp); + } + } + } + } +} diff --git a/Example/Views/SplitTunnelingView.xaml b/Example/Views/SplitTunnelingView.xaml new file mode 100644 index 0000000..27464f7 --- /dev/null +++ b/Example/Views/SplitTunnelingView.xaml @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +