From 8f68e3df7b8495f77c75c04f94cb2721b2ad2669 Mon Sep 17 00:00:00 2001 From: Beatriz Rizental Date: Mon, 18 Mar 2024 15:29:38 +0100 Subject: [PATCH] VPN-5466 - Bring back LAN Bypass setting on iOS (#9070) * Bring back LAN Bypass setting on iOS * Fix stuff * Remove unnecessary binding * Fix stuf * Fix build * Use toggle, address comments * Get stuff to work * typographic quotes --- src/platforms/ios/ioscontroller.mm | 1 + src/platforms/ios/ioscontroller.swift | 16 ++--- src/settingslist.h | 13 ++++- src/translations/strings.yaml | 24 ++++---- src/ui/screens/settings/ViewPreferences.qml | 65 +++++++++++++++++---- 5 files changed, 88 insertions(+), 31 deletions(-) diff --git a/src/platforms/ios/ioscontroller.mm b/src/platforms/ios/ioscontroller.mm index d4af1e5307..ebcbb694d4 100644 --- a/src/platforms/ios/ioscontroller.mm +++ b/src/platforms/ios/ioscontroller.mm @@ -147,6 +147,7 @@ serverPublicKey:config.m_serverPublicKey.toNSString() serverIpv4AddrIn:config.m_serverIpv4AddrIn.toNSString() serverPort:config.m_serverPort + excludeLocalNetworks:settingsHolder->localNetworkAccess() allowedIPAddressRanges:allowedIPAddressRangesNS reason:reason gleanDebugTag:settingsHolder->gleanDebugTagActive() diff --git a/src/platforms/ios/ioscontroller.swift b/src/platforms/ios/ioscontroller.swift index 24c3d3a3d4..c5e9541720 100644 --- a/src/platforms/ios/ioscontroller.swift +++ b/src/platforms/ios/ioscontroller.swift @@ -116,7 +116,7 @@ public class IOSControllerImpl : NSObject { } } - @objc func connect(dnsServer: String, serverIpv6Gateway: String, serverPublicKey: String, serverIpv4AddrIn: String, serverPort: Int, allowedIPAddressRanges: Array, reason: Int, gleanDebugTag: String, isSuperDooperFeatureActive: Bool, installationId: String, disconnectOnErrorCallback: @escaping () -> Void, onboardingCompletedCallback: @escaping () -> Void, vpnConfigPermissionResponseCallback: @escaping (Bool) -> Void) { + @objc func connect(dnsServer: String, serverIpv6Gateway: String, serverPublicKey: String, serverIpv4AddrIn: String, serverPort: Int, excludeLocalNetworks: Bool, allowedIPAddressRanges: Array, reason: Int, gleanDebugTag: String, isSuperDooperFeatureActive: Bool, installationId: String, disconnectOnErrorCallback: @escaping () -> Void, onboardingCompletedCallback: @escaping () -> Void, vpnConfigPermissionResponseCallback: @escaping (Bool) -> Void) { IOSControllerImpl.logger.debug(message: "Connecting") TunnelManager.withTunnel { tunnel in @@ -142,7 +142,7 @@ public class IOSControllerImpl : NSObject { var peerConfigurations: [PeerConfiguration] = [] peerConfigurations.append(peerConfiguration) - var interface = InterfaceConfiguration(privateKey: privateKey!) + var interface = InterfaceConfiguration(privateKey: self.privateKey!) if let ipv4Address = IPAddressRange(from: deviceIpv4Address!), let ipv6Address = IPAddressRange(from: deviceIpv6Address!) { @@ -152,11 +152,11 @@ public class IOSControllerImpl : NSObject { let config = TunnelConfiguration(name: VPN_NAME, interface: interface, peers: peerConfigurations) - return self.configureTunnel(config: config, reason: reason, serverName: serverIpv4AddrIn + ":\(serverPort )", gleanDebugTag: gleanDebugTag, isSuperDooperFeatureActive: isSuperDooperFeatureActive, installationId: installationId, disconnectOnErrorCallback: disconnectOnErrorCallback, onboardingCompletedCallback: onboardingCompletedCallback, vpnConfigPermissionResponseCallback: vpnConfigPermissionResponseCallback) + return self.configureTunnel(config: config, reason: reason, serverName: serverIpv4AddrIn + ":\(serverPort )", excludeLocalNetworks: excludeLocalNetworks, gleanDebugTag: gleanDebugTag, isSuperDooperFeatureActive: isSuperDooperFeatureActive, installationId: installationId, disconnectOnErrorCallback: disconnectOnErrorCallback, onboardingCompletedCallback: onboardingCompletedCallback, vpnConfigPermissionResponseCallback: vpnConfigPermissionResponseCallback) } } - func configureTunnel(config: TunnelConfiguration, reason: Int, serverName: String, gleanDebugTag: String, isSuperDooperFeatureActive: Bool, installationId: String, disconnectOnErrorCallback: @escaping () -> Void, onboardingCompletedCallback: @escaping () -> Void, vpnConfigPermissionResponseCallback: @escaping (Bool) -> Void) { + func configureTunnel(config: TunnelConfiguration, reason: Int, serverName: String, excludeLocalNetworks: Bool, gleanDebugTag: String, isSuperDooperFeatureActive: Bool, installationId: String, disconnectOnErrorCallback: @escaping () -> Void, onboardingCompletedCallback: @escaping () -> Void, vpnConfigPermissionResponseCallback: @escaping (Bool) -> Void) { TunnelManager.withTunnel { tunnel in let proto = NETunnelProviderProtocol(tunnelConfiguration: config) proto!.providerBundleIdentifier = TunnelManager.vpnBundleId @@ -166,7 +166,7 @@ public class IOSControllerImpl : NSObject { if #available(iOS 15.1, *) { IOSControllerImpl.logger.debug(message: "Activating includeAllNetworks") proto!.includeAllNetworks = true - proto!.excludeLocalNetworks = true + proto!.excludeLocalNetworks = excludeLocalNetworks if #available(iOS 16.4, *) { // By default, APNs is excluded from the VPN tunnel on 16.4 and later. We want to include it. @@ -189,7 +189,7 @@ public class IOSControllerImpl : NSObject { // the vpn configuration to be created, so it is safe to run activation retries via Controller::startHandshakeTimer() // without the possibility or re-prompting (flickering) the modal while it is currently being displayed vpnConfigPermissionResponseCallback(saveError == nil) - + if let error = saveError { IOSControllerImpl.logger.error(message: "Connect Tunnel Save Error: \(error)") disconnectOnErrorCallback() @@ -246,7 +246,7 @@ public class IOSControllerImpl : NSObject { @objc func checkStatus(callback: @escaping (String, String, String) -> Void) { IOSControllerImpl.logger.info(message: "Check status") - + TunnelManager.withTunnel { tunnel in let proto = tunnel.protocolConfiguration as? NETunnelProviderProtocol if proto == nil { @@ -296,7 +296,7 @@ public class IOSControllerImpl : NSObject { IOSControllerImpl.logger.error(message: "Failed to retrieve data from session. \(error)") callback("", "", "") } - + return } } diff --git a/src/settingslist.h b/src/settingslist.h index e6f5b3ab5c..fd91016087 100644 --- a/src/settingslist.h +++ b/src/settingslist.h @@ -705,10 +705,21 @@ SETTING_STRINGLIST(subscriptionTransactions, // getter removeSubscriptionTransactions, // remover hasSubscriptionTransactions, // has "subscriptionTransactions", // key - QStringList(), // efault value + QStringList(), // default value false, // remove when reset true // sensitive (do not log) ) + +SETTING_BOOL(localNetworkAccess, // getter + setLocalNetworkAccess, // setter + removeLocalNetworkAccess, // remover + hasLocalNetworkAccess, // has + "localNetworkAccess", // key + false, // default value + true, // remove when reset + false // sensitive (do not log) +) + #endif SETTING_BOOL(gleanDebugTagActive, // getter diff --git a/src/translations/strings.yaml b/src/translations/strings.yaml index 065fdf205d..412c493b42 100644 --- a/src/translations/strings.yaml +++ b/src/translations/strings.yaml @@ -360,6 +360,11 @@ settings: value: Clear all comment: Button label to clear all apps from the exclusions list +localNetworkAccess: + label: Local network access + subLabel: Access Apple CarPlay, AirDrop, AirPlay and local network devices like printers. + vpnMustBeOff: VPN must be off to edit “Local network access” + dnsOverwriteDialog: titleDNS: Override DNS settings? titlePrivacy: Override privacy features? @@ -724,7 +729,6 @@ inAppAuth: value: Continue to sign in comment: Button a user clicks after updating their Mozilla account settings so that they can re-attempt sign-in - postAuthentication: headline: Quick access subtitle: You can quickly access Mozilla VPN from your status bar. @@ -953,11 +957,11 @@ devices: value: My devices comment: Title for the menu bar when viewing the devices or device limit pages -helpSheets: +helpSheets: dnsTitle: value: Custom DNS settings comment: Title label for the custom dns help sheet - dnsHeader: + dnsHeader: value: What is a custom DNS? comment: Header label for the custom dns help sheet dnsBody1: @@ -982,7 +986,7 @@ helpSheets: value: Excluded apps comment: Title label for the excluded apps help sheet excludedAppsHeader: - value: What are excluded apps? + value: What are excluded apps? comment: Header label for the excluded apps help sheet excludedAppsBody1: value: Excluded apps let you turn off VPN protection for specific apps, without turning off VPN protection for your entire device. @@ -1002,7 +1006,7 @@ helpSheets: devicesHeader: value: Adding and removing devices comment: Header label for the devices help sheet - devicesBody1: + devicesBody1: value: "Adding a new device to your subscription is easy: just download and log in to Mozilla VPN on that device." comment: Body label for the devices help sheet devicesBody2: @@ -1021,7 +1025,7 @@ helpSheets: value: If you want to add more privacy, switch to “Multi-hop” and add an additional server location. Multi-hop VPN routes your traffic through two server locations instead of one for extra security and privacy. This may also slow down your connection a bit. comment: Body label for the location selection help sheet -resetSettings: +resetSettings: resetLabel: value: Reset VPN comment: Label for button that takes the user to the factory reset flow @@ -1030,16 +1034,16 @@ resetSettings: comment: Title of the screen where users go to reset the VPN to factory settings body1: value: "This will reset all VPN settings, including:" - comment: Body content of the screen where users go to reset the VPN to factory settings + comment: Body content of the screen where users go to reset the VPN to factory settings listItemPreferences: value: Preferences comment: Item in a bulleted list denoting what will be deleted if the user opts to factory reset the client body2: value: Resetting the VPN will sign you out, but your account will not be deleted. - comment: Body content of the screen where users go to reset the VPN to factory settings + comment: Body content of the screen where users go to reset the VPN to factory settings resetButtonLabel: value: Reset VPN - comment: Label for button that initiates a restore to factory settings + comment: Label for button that initiates a restore to factory settings confirmResetModalTitle: value: Confirm reset comment: Title of the modal where users can confirm they want to restore back to factory settings @@ -1048,7 +1052,7 @@ resetSettings: comment: Body of the modal where users can confirm they want to restore back to factory settings confirmResetModalResetButtonLabel: value: Reset and quit - comment: Label for button that finalizes a restore to factory settings + comment: Label for button that finalizes a restore to factory settings global: expand: diff --git a/src/ui/screens/settings/ViewPreferences.qml b/src/ui/screens/settings/ViewPreferences.qml index e2b0c84316..eb92b7288e 100644 --- a/src/ui/screens/settings/ViewPreferences.qml +++ b/src/ui/screens/settings/ViewPreferences.qml @@ -9,6 +9,7 @@ import QtQuick.Layouts 1.14 import Mozilla.Shared 1.0 import Mozilla.VPN 1.0 import components 0.1 +import components.forms 0.1 MZViewBase { id: vpnFlickable @@ -17,22 +18,25 @@ MZViewBase { property string _languageTitle: "" readonly property string telemetryScreenId : "app_preferences" + property bool vpnIsOff: VPNController.state === VPNController.StateOff + property bool isIOS: Qt.platform.os === "ios" + objectName: "settingsPreferencesView" Component.onCompleted: Glean.impression.appPreferencesScreen.record({screen:telemetryScreenId}) - _viewContentData: Column { - spacing: MZTheme.theme.windowMargin - Layout.fillWidth: true + _viewContentData: ColumnLayout { + Layout.preferredWidth: parent.width Layout.alignment: Qt.AlignHCenter + spacing: MZTheme.theme.windowMargin + MZToggleRow { objectName: "startAtBootToogle" - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: MZTheme.theme.windowMargin - anchors.rightMargin: MZTheme.theme.windowMargin + Layout.fillWidth: true + Layout.rightMargin: MZTheme.theme.windowMargin + Layout.leftMargin: MZTheme.theme.windowMargin labelText: _startAtBootTitle subLabelText: MZI18n.SettingsStartAtBootSubtitle @@ -55,10 +59,10 @@ MZViewBase { MZToggleRow { objectName: "dataCollectionToggle" - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: MZTheme.theme.windowMargin - anchors.rightMargin: MZTheme.theme.windowMargin + Layout.fillWidth: true + Layout.rightMargin: MZTheme.theme.windowMargin + Layout.leftMargin: MZTheme.theme.windowMargin + showDivider: isIOS labelText: MZI18n.TelemetryPolicyViewDataCollectionAndUse subLabelText: MZI18n.SettingsDataCollectionDescription @@ -67,7 +71,44 @@ MZViewBase { onClicked: MZSettings.gleanEnabled = !MZSettings.gleanEnabled } - Column { + MZToggleRow { + id: localNetwork + objectName: "settingLocalNetworkAccess" + visible: isIOS + + Layout.fillWidth: true + Layout.rightMargin: MZTheme.theme.windowMargin + Layout.leftMargin: MZTheme.theme.windowMargin + showDivider: false + + labelText: MZI18n.LocalNetworkAccessLabel + subLabelText: MZI18n.LocalNetworkAccessSubLabel + checked: MZSettings.localNetworkAccess + enabled: isIOS && vpnIsOff + dividerTopMargin: MZTheme.theme.toggleRowDividerSpacing + onClicked: { + if (VPNController.StateOff) { + MZSettings.localNetworkAccess = !MZSettings.localNetworkAccess + } + } + } + + MZContextualAlerts { + Layout.topMargin: MZTheme.theme.listSpacing + Layout.rightMargin: MZTheme.theme.windowMargin + Layout.leftMargin: MZTheme.theme.windowMargin + + visible: isIOS && !vpnIsOff + + messages: [ + { + type: "warning", + message: MZI18n.LocalNetworkAccessVpnMustBeOff, + } + ] + } + + ColumnLayout { spacing: MZTheme.theme.windowMargin / 2 width: parent.width MZSettingsItem {