From b906d12dc60230ccc0a2a0d3fe860be9ae7a6e87 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Thu, 21 Sep 2023 13:22:20 +0200 Subject: [PATCH] Add WireGuard NetP Error Pixels (#2023) Co-authored-by: Diego Rey Mendez Co-authored-by: Dax Mobile <44842493+daxmobile@users.noreply.github.com> Co-authored-by: Brad Slayter --- .github/workflows/alpha.yml | 113 ++++++++++++++++++ .github/workflows/nightly.yml | 14 ++- Core/Pixel.swift | 1 + Core/URLFileExtension.swift | 6 - Core/UserDefaultsPropertyWrapper.swift | 2 + DuckDuckGo.xcodeproj/project.pbxproj | 18 ++- .../xcshareddata/swiftpm/Package.resolved | 8 +- .../xcschemes/DuckDuckGo-Alpha.xcscheme | 77 ++++++++++++ .../EventMapping+NetworkProtectionError.swift | 11 +- .../NetworkProtectionDebugFeatures.swift | 26 ++++ ...NetworkProtectionDebugViewController.swift | 112 ++++++++++++----- .../NetworkProtectionTunnelController.swift | 35 +++++- DuckDuckGo/PrivacyInfo.xcprivacy | 69 +++++++++++ ...etworkProtectionPacketTunnelProvider.swift | 12 ++ alphaExportOptions.plist | 23 ++++ fastlane/Fastfile | 38 ++++-- fastlane/Matchfile | 4 +- fastlane/README.md | 18 +-- 18 files changed, 520 insertions(+), 67 deletions(-) create mode 100644 .github/workflows/alpha.yml create mode 100644 DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme create mode 100644 DuckDuckGo/NetworkProtectionDebugFeatures.swift create mode 100644 DuckDuckGo/PrivacyInfo.xcprivacy create mode 100644 alphaExportOptions.plist diff --git a/.github/workflows/alpha.yml b/.github/workflows/alpha.yml new file mode 100644 index 0000000000..4110318550 --- /dev/null +++ b/.github/workflows/alpha.yml @@ -0,0 +1,113 @@ +name: Make TestFlight Alpha Build + +on: + workflow_dispatch: + inputs: + destination: + description: "TestFlight Group" + required: true + default: "Latest Alpha Group" + type: string + workflow_call: + inputs: + destination: + description: "TestFlight Group" + required: true + default: "Latest Alpha Group" + type: string + secrets: + SSH_PRIVATE_KEY_FASTLANE_MATCH: + required: true + APPLE_API_KEY_BASE64: + required: true + APPLE_API_KEY_ID: + required: true + APPLE_API_KEY_ISSUER: + required: true + MATCH_PASSWORD: + required: true + ASANA_ACCESS_TOKEN: + required: true + +jobs: + make-alpha: + runs-on: macos-13 + name: Make TestFlight Alpha Build + + env: + destination: ${{ github.event.inputs.destination || inputs.destination }} + + steps: + + - name: Assert develop branch + run: | + case "${{ github.ref }}" in + *develop/*) ;; + *) echo "👎 Not develop branch"; exit 1 ;; + esac + + - name: Register SSH keys for access to certificates + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_FASTLANE_MATCH }} + + - name: Check out the code + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Set cache key hash + run: | + has_only_tags=$(jq '[ .object.pins[].state | has("version") ] | all' DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved) + if [[ "$has_only_tags" == "true" ]]; then + echo "cache_key_hash=${{ hashFiles('DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}" >> $GITHUB_ENV + else + echo "Package.resolved contains dependencies specified by branch or commit, skipping cache." + fi + + - name: Cache SPM + if: env.cache_key_hash + uses: actions/cache@v3 + with: + path: DerivedData/SourcePackages + key: ${{ runner.os }}-spm-${{ env.cache_key_hash }} + restore-keys: | + ${{ runner.os }}-spm- + + - name: Select Xcode + run: sudo xcode-select -s /Applications/Xcode_$(<.xcode-version).app/Contents/Developer + + - name: Prepare fastlane + run: bundle install + + - name: Archive and upload the app + env: + APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + run: | + app_version="$(cut -d ' ' -f 3 < Configuration/Version.xcconfig)" + bundle exec fastlane increment_build_number_for_version version:$app_version app_identifier:"com.duckduckgo.mobile.ios.alpha" + bundle exec fastlane release_alpha groups:"${{ env.destination }}" + build_version="$(xcodebuild -configuration Alpha -showBuildSettings | grep CURRENT_PROJECT_VERSION | tr -d 'CURRENT_PROJECT_VERSION =')" + echo "dsyms_path=${{ github.workspace }}/DuckDuckGo-Alpha.app.dSYM.zip" >> $GITHUB_ENV + echo "app_version=${app_version}" >> $GITHUB_ENV + echo "build_version=${build_version}" >> $GITHUB_ENV + + - name: Upload dSYMs artifact + uses: actions/upload-artifact@v3 + with: + name: DuckDuckGo-Alpha-dSYM-${{ env.app_version }} + path: ${{ env.dsyms_path }} + + - name: Upload debug symbols to Asana + env: + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + run: | + asana_dsyms_path="${{ github.workspace }}/DuckDuckGo-Alpha-${{ env.app_version }}(${{ env.build_version }})-dSYM.zip" + mv -f "${{ env.dsyms_path }}" "$asana_dsyms_path" + + curl -s "https://app.asana.com/api/1.0/tasks/1205344386326139/attachments" \ + -H "Authorization: Bearer ${{ secrets.ASANA_ACCESS_TOKEN }}" \ + --form "file=@${asana_dsyms_path};type=application/zip" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ad5c8bec9d..5a67fdb265 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,4 +1,4 @@ -name: Nightly Integration Tests +name: Nightly Test and Deploy on: schedule: @@ -94,3 +94,15 @@ jobs: with: report_paths: unittests.xml + deploy-alpha: + name: Deploy Nightly Alpha Build + uses: ./.github/workflows/alpha.yml + with: + destination: "Nightly Alpha Group" + secrets: + APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + SSH_PRIVATE_KEY_FASTLANE_MATCH: ${{ secrets.SSH_PRIVATE_KEY_FASTLANE_MATCH }} + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} diff --git a/Core/Pixel.swift b/Core/Pixel.swift index 12f002efd9..9751d958f1 100644 --- a/Core/Pixel.swift +++ b/Core/Pixel.swift @@ -113,6 +113,7 @@ public struct PixelParameters { // Network Protection public static let keychainFieldName = "fieldName" public static let keychainErrorCode = errorCode + public static let wireguardErrorCode = errorCode public static let function = "function" public static let line = "line" public static let reason = "reason" diff --git a/Core/URLFileExtension.swift b/Core/URLFileExtension.swift index 2d140b98a1..286a728c7b 100644 --- a/Core/URLFileExtension.swift +++ b/Core/URLFileExtension.swift @@ -27,12 +27,6 @@ extension URL { (try? resourceValues(forKeys: [.creationDateKey]))?.creationDate } - /// The time at which the resource was most recently modified. - /// This key corresponds to an Date value, or nil if the volume doesn't support modification dates. - public var contentModification: Date? { - (try? resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate - } - /// The time at which the resource was most recently accessed. /// This key corresponds to an Date value, or nil if the volume doesn't support access dates. /// When you set the contentAccessDateKey for a resource, also set contentModificationDateKey in the same call to the setResourceValues(_:) method. Otherwise, the file system may set the contentAccessDateKey value to the current contentModificationDateKey value. diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index e32e0513e5..63a0ede59d 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -97,6 +97,8 @@ public struct UserDefaultsWrapper { case defaultBrowserUsageLastSeen = "com.duckduckgo.ios.default-browser-usage-last-seen" case syncEnvironment = "com.duckduckgo.ios.sync-environment" + + case networkProtectionDebugOptionAlwaysOnEnabled = "com.duckduckgo.network-protection.always-on.enabled" } private let key: Key diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3999eba708..93c5fb59fa 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -100,6 +100,7 @@ 02CA904924F6BFE700D41DDF /* navigatorsharepatch.js in Resources */ = {isa = PBXBuildFile; fileRef = 02CA904824F6BFE700D41DDF /* navigatorsharepatch.js */; }; 02CA904B24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CA904A24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift */; }; 02EC02C429AFA33000557F1A /* AppTPBreakageFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EC02C329AFA33000557F1A /* AppTPBreakageFormView.swift */; }; + 02F880642AB206740020C2DF /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 02ECEC602A965074009F0654 /* PrivacyInfo.xcprivacy */; }; 0A6CC0EF23904D5400E4F627 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0A6CC0EE23904D5400E4F627 /* Settings.bundle */; }; 1CB7B82123CEA1F800AA24EA /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB7B82023CEA1F800AA24EA /* DateExtension.swift */; }; 1CB7B82323CEA28300AA24EA /* DateExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB7B82223CEA28300AA24EA /* DateExtensionTests.swift */; }; @@ -299,6 +300,7 @@ 56244C1D2A137B1900EDF259 /* WaitlistViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56244C1C2A137B1900EDF259 /* WaitlistViews.swift */; }; 6AC6DAB328804F97002723C0 /* BarsAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */; }; 6AC98419288055C1005FA9CA /* BarsAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */; }; + 7B5E1F9E2AB9E1E900DA1172 /* NetworkProtectionDebugFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5E1F9D2AB9E1E900DA1172 /* NetworkProtectionDebugFeatures.swift */; }; 83004E802193BB8200DA013C /* WKNavigationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */; }; 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */; }; 83004E882193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E872193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift */; }; @@ -1102,6 +1104,7 @@ 02CA904A24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSharePatchUserScript.swift; sourceTree = ""; }; 02CA904C24FD2DB000D41DDF /* ContentBlockingRulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockingRulesTests.swift; sourceTree = ""; }; 02EC02C329AFA33000557F1A /* AppTPBreakageFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTPBreakageFormView.swift; sourceTree = ""; }; + 02ECEC602A965074009F0654 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 0A6CC0EE23904D5400E4F627 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 1CB7B82023CEA1F800AA24EA /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = ""; }; 1CB7B82223CEA28300AA24EA /* DateExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtensionTests.swift; sourceTree = ""; }; @@ -1286,6 +1289,7 @@ 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimator.swift; sourceTree = ""; }; 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimatorTests.swift; sourceTree = ""; }; 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Configuration.xcconfig; path = Configuration/Configuration.xcconfig; sourceTree = ""; }; + 7B5E1F9D2AB9E1E900DA1172 /* NetworkProtectionDebugFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugFeatures.swift; sourceTree = ""; }; 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationExtension.swift; sourceTree = ""; }; 83004E832193E14C00DA013C /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIAlertControllerExtension.swift; path = ../Core/UIAlertControllerExtension.swift; sourceTree = ""; }; 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewControllerBrowsingMenuExtension.swift; sourceTree = ""; }; @@ -3349,6 +3353,14 @@ name = AppTrackingProtection; sourceTree = ""; }; + 7B5E1F9C2AB9E1D000DA1172 /* DebugFeatures */ = { + isa = PBXGroup; + children = ( + 7B5E1F9D2AB9E1E900DA1172 /* NetworkProtectionDebugFeatures.swift */, + ); + name = DebugFeatures; + sourceTree = ""; + }; 830FA79B1F8E81FB00FCE105 /* ContentBlocker */ = { isa = PBXGroup; children = ( @@ -3563,6 +3575,7 @@ F1C4A70C1E5771F800A6CA1B /* OmniBar */, F1AE54DB1F0425BB00D9A700 /* Privacy */, 1E87615728A1515400C7C5CE /* PrivacyDashboard */, + 02ECEC602A965074009F0654 /* PrivacyInfo.xcprivacy */, C1B7B51D28941F160098FD6A /* RemoteMessaging */, F1AB2B401E3F75A000868554 /* Settings */, 0A6CC0EE23904D5400E4F627 /* Settings.bundle */, @@ -4362,6 +4375,7 @@ EECD94B22A28B8580085C66E /* NetworkProtection */ = { isa = PBXGroup; children = ( + 7B5E1F9C2AB9E1D000DA1172 /* DebugFeatures */, EE0153E22A6FE031002A8B26 /* Root */, EE0153DF2A6EABAF002A8B26 /* Helpers */, EEFD562D2A65B68B00DAEC48 /* Invite */, @@ -5592,6 +5606,7 @@ AA4D6AA223DE4CC4007E8790 /* AppIconBlue76x76@2x.png in Resources */, AA4D6AB823DE4D15007E8790 /* AppIconYellow29x29@2x.png in Resources */, 984147C024F026A300362052 /* Tab.storyboard in Resources */, + 02F880642AB206740020C2DF /* PrivacyInfo.xcprivacy in Resources */, AA4D6AE123DE4D33007E8790 /* AppIconGreen76x76@2x.png in Resources */, AA4D6A9123DE49A5007E8790 /* AppIconBlack60x60@3x.png in Resources */, AA4D6A8E23DE49A5007E8790 /* AppIconBlack60x60@2x.png in Resources */, @@ -6126,6 +6141,7 @@ 98AA92B32456FBE100ED4B9E /* SearchFieldContainerView.swift in Sources */, 3157B43827F4C8490042D3D7 /* FaviconsHelper.swift in Sources */, 85F200042216F5D8006BB258 /* FindInPageView.swift in Sources */, + 7B5E1F9E2AB9E1E900DA1172 /* NetworkProtectionDebugFeatures.swift in Sources */, 8548D95E25262B1B005AAE49 /* ViewHighlighter.swift in Sources */, F4D7221026F29A70007D6193 /* BookmarkDetailsCell.swift in Sources */, F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */, @@ -8894,7 +8910,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 78.2.2; + version = 80.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index df774dd654..0730c213e1 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", "state": { "branch": null, - "revision": "781e68decc81dc33a6a7593ab88b1e074a8ee051", - "version": "78.2.2" + "revision": "ec5dc5931efc496aef2573b2f082607a3c82b655", + "version": "80.0.0" } }, { @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/duckduckgo/duckduckgo-autofill.git", "state": { "branch": null, - "revision": "21aa20e272b7de06e471c6e646d25e26a5887b60", - "version": "8.3.0" + "revision": "f3eccad8647fdba2b5d180a02a0513c61375b8fb", + "version": "8.4.0" } }, { diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme new file mode 100644 index 0000000000..ab41c0e9a0 --- /dev/null +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo-Alpha.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo/EventMapping+NetworkProtectionError.swift b/DuckDuckGo/EventMapping+NetworkProtectionError.swift index 7a89dabd1c..a131d64443 100644 --- a/DuckDuckGo/EventMapping+NetworkProtectionError.swift +++ b/DuckDuckGo/EventMapping+NetworkProtectionError.swift @@ -77,16 +77,19 @@ extension EventMapping where Event == NetworkProtectionError { .failedToDecodeServerList, .failedToWriteServerList, .couldNotCreateServerListDirectory, - .failedToReadServerList: + .failedToReadServerList, + .wireGuardCannotLocateTunnelFileDescriptor, + .wireGuardInvalidState, + .wireGuardDnsResolution, + .wireGuardSetNetworkSettings, + .startWireGuardBackend: pixelEvent = .networkProtectionUnhandledError params[PixelParameters.function] = #function params[PixelParameters.line] = String(#line) // Should never be sent from from the app case .unhandledError(function: let function, line: let line, error: let error): pixelEvent = .networkProtectionUnhandledError - params[PixelParameters.function] = function - params[PixelParameters.line] = String(line) - pixelError = error + } DailyPixel.fireDailyAndCount(pixel: pixelEvent, error: pixelError, withAdditionalParameters: params) diff --git a/DuckDuckGo/NetworkProtectionDebugFeatures.swift b/DuckDuckGo/NetworkProtectionDebugFeatures.swift new file mode 100644 index 0000000000..ee7c115efd --- /dev/null +++ b/DuckDuckGo/NetworkProtectionDebugFeatures.swift @@ -0,0 +1,26 @@ +// +// NetworkProtectionDebugFeatures.swift +// DuckDuckGo +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Core + +final class NetworkProtectionDebugFeatures { + @UserDefaultsWrapper(key: .networkProtectionDebugOptionAlwaysOnEnabled, defaultValue: false) + var alwaysOnEnabled +} diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index f9fb17b14e..6700bdc533 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -19,21 +19,27 @@ import UIKit -#if NETWORK_PROTECTION +#if !NETWORK_PROTECTION -import NetworkProtection +final class NetworkProtectionDebugViewController: UITableViewController { + // Just an empty VC +} -#endif +#else + +import NetworkProtection final class NetworkProtectionDebugViewController: UITableViewController { private let titles = [ Sections.keychain: "Keychain", + Sections.debugFeature: "Debug Features", Sections.simulateFailure: "Simulate Failure" ] enum Sections: Int, CaseIterable { case keychain + case debugFeature case simulateFailure } @@ -44,6 +50,10 @@ final class NetworkProtectionDebugViewController: UITableViewController { } + enum DebugFeatureRows: Int, CaseIterable { + case enableAlwaysOn + } + enum SimulateFailureRows: Int, CaseIterable { case tunnelFailure @@ -53,13 +63,14 @@ final class NetworkProtectionDebugViewController: UITableViewController { } -#if NETWORK_PROTECTION - + private let debugFeatures: NetworkProtectionDebugFeatures private let tokenStore: NetworkProtectionTokenStore init?(coder: NSCoder, - tokenStore: NetworkProtectionTokenStore) { + tokenStore: NetworkProtectionTokenStore, + debugFeatures: NetworkProtectionDebugFeatures = NetworkProtectionDebugFeatures()) { + self.debugFeatures = debugFeatures self.tokenStore = tokenStore super.init(coder: coder) @@ -69,8 +80,6 @@ final class NetworkProtectionDebugViewController: UITableViewController { self.init(coder: coder, tokenStore: NetworkProtectionKeychainTokenStore()) } -#endif - override func numberOfSections(in tableView: UITableView) -> Int { return Sections.allCases.count } @@ -95,19 +104,11 @@ final class NetworkProtectionDebugViewController: UITableViewController { break } + case .debugFeature: + configure(cell, forDebugFeatureAtRow: indexPath.row) + case .simulateFailure: - switch SimulateFailureRows(rawValue: indexPath.row) { - case .controllerFailure: - cell.textLabel?.text = "Enable NetP > Controller Failure" - case .tunnelFailure: - cell.textLabel?.text = "Enable NetP > Tunnel Failure" - case .crashFatalError: - cell.textLabel?.text = "Tunnel: Crash (Fatal Error)" - case .crashMemory: - cell.textLabel?.text = "Tunnel: Crash (CPU/Memory)" - case .none: - break - } + configure(cell, forSimulateFailureAtRow: indexPath.row) case.none: break } @@ -118,14 +119,13 @@ final class NetworkProtectionDebugViewController: UITableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch Sections(rawValue: section) { case .keychain: return KeychainRows.allCases.count + case .debugFeature: return DebugFeatureRows.allCases.count case .simulateFailure: return SimulateFailureRows.allCases.count case .none: return 0 } } - #if NETWORK_PROTECTION - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { switch Sections(rawValue: indexPath.section) { case .keychain: @@ -133,14 +133,10 @@ final class NetworkProtectionDebugViewController: UITableViewController { case .clearAuthToken: clearAuthToken() default: break } + case .debugFeature: + didSelectDebugFeature(at: indexPath) case .simulateFailure: - switch SimulateFailureRows(rawValue: indexPath.row) { - case .controllerFailure: simulateFailure(option: .controllerFailure) - case .tunnelFailure: simulateFailure(option: .tunnelFailure) - case .crashFatalError: simulateFailure(option: .crashFatalError) - case .crashMemory: simulateFailure(option: .crashMemory) - case .none: return - } + didSelectSimulateFailure(at: indexPath) case .none: break } @@ -148,6 +144,60 @@ final class NetworkProtectionDebugViewController: UITableViewController { tableView.deselectRow(at: indexPath, animated: true) } + // MARK: Simulate Failures + + private func configure(_ cell: UITableViewCell, forSimulateFailureAtRow row: Int) { + switch SimulateFailureRows(rawValue: row) { + case .controllerFailure: + cell.textLabel?.text = "Enable NetP > Controller Failure" + case .tunnelFailure: + cell.textLabel?.text = "Enable NetP > Tunnel Failure" + case .crashFatalError: + cell.textLabel?.text = "Tunnel: Crash (Fatal Error)" + case .crashMemory: + cell.textLabel?.text = "Tunnel: Crash (CPU/Memory)" + case .none: + break + } + } + + private func didSelectSimulateFailure(at indexPath: IndexPath) { + switch SimulateFailureRows(rawValue: indexPath.row) { + case .controllerFailure: simulateFailure(option: .controllerFailure) + case .tunnelFailure: simulateFailure(option: .tunnelFailure) + case .crashFatalError: simulateFailure(option: .crashFatalError) + case .crashMemory: simulateFailure(option: .crashMemory) + case .none: return + } + } + + // MARK: Debug Features + + private func configure(_ cell: UITableViewCell, forDebugFeatureAtRow row: Int) { + switch DebugFeatureRows(rawValue: row) { + case .enableAlwaysOn: + cell.textLabel?.text = "Enable Always On" + + if debugFeatures.alwaysOnEnabled { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + default: + break + } + } + + private func didSelectDebugFeature(at indexPath: IndexPath) { + switch DebugFeatureRows(rawValue: indexPath.row) { + case .enableAlwaysOn: + debugFeatures.alwaysOnEnabled.toggle() + tableView.reloadRows(at: [indexPath], with: .none) + default: + break + } + } + // MARK: Selection Actions private func clearAuthToken() { @@ -165,6 +215,6 @@ final class NetworkProtectionDebugViewController: UITableViewController { private func simulateFailure(option: NetworkProtectionSimulationOption) { NetworkProtectionTunnelController.enabledSimulationOption = .crashMemory } - - #endif } + +#endif diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index a6455a2977..9b16e4e333 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -29,6 +29,7 @@ final class NetworkProtectionTunnelController: TunnelController { static var simulationOptions = NetworkProtectionSimulationOptions() static var enabledSimulationOption: NetworkProtectionSimulationOption? + private let debugFeatures = NetworkProtectionDebugFeatures() private let tokenStore = NetworkProtectionKeychainTokenStore() private let errorStore = NetworkProtectionTunnelErrorStore() @@ -52,13 +53,19 @@ final class NetworkProtectionTunnelController: TunnelController { } func stop() async { + guard let tunnelManager = await loadTunnelManager() else { + return + } + do { - try await ConnectionSessionUtilities.activeSession()?.stopVPNTunnel() + try await disableOnDemand(tunnelManager: tunnelManager) } catch { #if DEBUG errorStore.lastErrorMessage = error.localizedDescription #endif } + + tunnelManager.connection.stopVPNTunnel() } private func startWithError() async throws { @@ -114,6 +121,12 @@ final class NetworkProtectionTunnelController: TunnelController { Pixel.fire(pixel: .networkProtectionActivationRequestFailed, error: error) throw error } + + if debugFeatures.alwaysOnEnabled { + Task { + try await enableOnDemand(tunnelManager: tunnelManager) + } + } } /// The actual storage for our tunnel manager. @@ -196,6 +209,26 @@ final class NetworkProtectionTunnelController: TunnelController { // reconnect on reboot tunnelManager.onDemandRules = [NEOnDemandRuleConnect()] } + + // MARK: - On Demand + + @MainActor + func enableOnDemand(tunnelManager: NETunnelProviderManager) async throws { + let rule = NEOnDemandRuleConnect() + rule.interfaceTypeMatch = .any + + tunnelManager.onDemandRules = [rule] + tunnelManager.isOnDemandEnabled = true + + try await tunnelManager.saveToPreferences() + } + + @MainActor + func disableOnDemand(tunnelManager: NETunnelProviderManager) async throws { + tunnelManager.isOnDemandEnabled = false + + try await tunnelManager.saveToPreferences() + } } private extension NetworkProtectionSimulationOption { diff --git a/DuckDuckGo/PrivacyInfo.xcprivacy b/DuckDuckGo/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..15d10330da --- /dev/null +++ b/DuckDuckGo/PrivacyInfo.xcprivacy @@ -0,0 +1,69 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + DDA9.1 + C617.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePerformanceData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCrashData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + + diff --git a/PacketTunnelProvider/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtectionPacketTunnelProvider.swift index b83a9ce8c1..8c29c47b2d 100644 --- a/PacketTunnelProvider/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtectionPacketTunnelProvider.swift @@ -99,6 +99,18 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { case .keychainDeleteError(let status): // TODO: Check whether field needed here pixelEvent = .networkProtectionKeychainDeleteError params[PixelParameters.keychainErrorCode] = String(status) + case .wireGuardCannotLocateTunnelFileDescriptor: + pixelEvent = .networkProtectionWireguardErrorCannotLocateTunnelFileDescriptor + case .wireGuardInvalidState: + pixelEvent = .networkProtectionWireguardErrorInvalidState + case .wireGuardDnsResolution: + pixelEvent = .networkProtectionWireguardErrorFailedDNSResolution + case .wireGuardSetNetworkSettings(let error): + pixelEvent = .networkProtectionWireguardErrorCannotSetNetworkSettings + pixelError = error + case .startWireGuardBackend(let code): + pixelEvent = .networkProtectionWireguardErrorCannotStartWireguardBackend + params[PixelParameters.wireguardErrorCode] = String(code) case .noAuthTokenFound: pixelEvent = .networkProtectionNoAuthTokenFoundError case .unhandledError(function: let function, line: let line, error: let error): diff --git a/alphaExportOptions.plist b/alphaExportOptions.plist new file mode 100644 index 0000000000..9f949f40ac --- /dev/null +++ b/alphaExportOptions.plist @@ -0,0 +1,23 @@ + + + + + teamID + HKE973VLUW + method + app-store + provisioningProfiles + + com.duckduckgo.mobile.ios.alpha + match AppStore com.duckduckgo.mobile.ios.alpha + com.duckduckgo.mobile.ios.alpha.ShareExtension + match AppStore com.duckduckgo.mobile.ios.alpha.ShareExtension + com.duckduckgo.mobile.ios.alpha.OpenAction2 + match AppStore com.duckduckgo.mobile.ios.alpha.OpenAction2 + com.duckduckgo.mobile.ios.alpha.Widgets + match AppStore com.duckduckgo.mobile.ios.alpha.Widgets + com.duckduckgo.mobile.ios.alpha.NetworkExtension + match AppStore com.duckduckgo.mobile.ios.alpha.NetworkExtension + + + diff --git a/fastlane/Fastfile b/fastlane/Fastfile index be82b0b85f..dcc1b3745d 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -18,6 +18,7 @@ lane :sync_signing_adhoc do |options| do_sync_signing(options) end +desc 'Fetches and updates certificates and provisioning profiles for Alpha distribution' lane :sync_signing_alpha do |options| do_sync_signing(options) end @@ -88,11 +89,6 @@ lane :adhoc do |options| end end -desc 'Makes Alpha release build and uploads it to App Store Connect (TestFlight)' -lane :alpha do |options| -# TODO -end - desc 'Makes App Store release build and uploads it to App Store Connect' lane :release_appstore do |options| build_release(options) @@ -132,13 +128,29 @@ lane :release_testflight do ) end +desc 'Makes Alpha release build and uploads it to TestFlight' +lane :release_alpha do |options| + build_alpha(options) + + upload_to_testflight( + api_key: get_api_key, + groups: options[:groups], + skip_waiting_for_build_processing: true + ) +end + desc 'Increment build number based on version in App Store Connect' lane :increment_build_number_for_version do |options| + app_identifier = "com.duckduckgo.mobile.ios" + if options[:app_identifier] + app_identifier = options[:app_identifier] + end increment_build_number({ build_number: latest_testflight_build_number( + api_key: get_api_key, version: options[:version], - app_identifier: "com.duckduckgo.mobile.ios", + app_identifier: app_identifier, initial_build_number: -1, username: get_username(options)) + 1, skip_info_plist: "true" @@ -172,6 +184,18 @@ private_lane :build_release do |options| ) end +private_lane :build_alpha do |options| + sync_signing_alpha(options) + + build_app( + export_method: "app-store", + configuration: "Alpha", + scheme: "DuckDuckGo-Alpha", + export_options: "alphaExportOptions.plist", + derived_data_path: "DerivedData" + ) +end + private_lane :get_api_key do has_api_key = [ "APPLE_API_KEY_ID", @@ -209,7 +233,7 @@ private_lane :do_sync_signing do |options| sync_code_signing( api_key: get_api_key, username: get_username(options), - readonly: is_ci && !is_adhoc + readonly: is_ci && !is_adhoc ) end diff --git a/fastlane/Matchfile b/fastlane/Matchfile index de59777a19..400b1ffebc 100644 --- a/fastlane/Matchfile +++ b/fastlane/Matchfile @@ -19,11 +19,9 @@ for_lane :adhoc do end for_lane :sync_signing_alpha do - type "appstore" app_identifier ["com.duckduckgo.mobile.ios.alpha", "com.duckduckgo.mobile.ios.alpha.ShareExtension", "com.duckduckgo.mobile.ios.alpha.OpenAction2", "com.duckduckgo.mobile.ios.alpha.Widgets", "com.duckduckgo.mobile.ios.alpha.NetworkExtension"] end -for_lane :alpha do - type "appstore" +for_lane :release_alpha do app_identifier ["com.duckduckgo.mobile.ios.alpha", "com.duckduckgo.mobile.ios.alpha.ShareExtension", "com.duckduckgo.mobile.ios.alpha.OpenAction2", "com.duckduckgo.mobile.ios.alpha.Widgets", "com.duckduckgo.mobile.ios.alpha.NetworkExtension"] end diff --git a/fastlane/README.md b/fastlane/README.md index b0375dd0f9..452c9fec13 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -35,7 +35,7 @@ Fetches and updates certificates and provisioning profiles for Ad-Hoc distributi [bundle exec] fastlane sync_signing_alpha ``` - +Fetches and updates certificates and provisioning profiles for Alpha distribution ### adhoc @@ -45,14 +45,6 @@ Fetches and updates certificates and provisioning profiles for Ad-Hoc distributi Makes Ad-Hoc build with a specified name in a given directory -### alpha - -```sh -[bundle exec] fastlane alpha -``` - -Makes Alpha release build and uploads it to App Store Connect (TestFlight) - ### release_appstore ```sh @@ -77,6 +69,14 @@ Updates App Store metadata Makes App Store release build and uploads it to TestFlight +### release_alpha + +```sh +[bundle exec] fastlane release_alpha +``` + +Makes Alpha release build and uploads it to TestFlight + ### increment_build_number_for_version ```sh