From 96ceae1aa1c7df120d161afb990f4003fc12f122 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Mon, 25 Mar 2024 00:57:37 -0400 Subject: [PATCH] Collect extra metadata (#2622) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Feedback/VPNMetadataCollector.swift | 71 ++++++++++++++----- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 21bcdd74e8..b76bb7a78c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.4; + version = 129.1.6; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0d68ef32b2..e0a30aaaa8 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "91b012d9450af211f7c47b9fde811e3a8292b16b", - "version" : "129.1.4" + "branch" : "anh/pp/add-metadata", + "revision" : "0cba47cf651175806bc151366605f8b19802d3ee" } }, { diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index a4103cdd00..b858cf4672 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -24,6 +24,7 @@ import Common import NetworkProtection import NetworkExtension import Network +import Subscription struct VPNMetadata: Encodable { @@ -61,11 +62,27 @@ struct VPNMetadata: Encodable { let selectedServer: String } + struct PrivacyProInfo: Encodable { + // swiftlint:disable nesting + enum Source: String, Encodable { + case `internal` + case waitlist + case other + } + // swiftlint:enable nesting + + let enableSource: Source + let betaParticipant: Bool + let hasToken: Bool + let subscriptionActive: Bool + } + let appInfo: AppInfo let deviceInfo: DeviceInfo let networkInfo: NetworkInfo let vpnState: VPNState let vpnSettingsState: VPNSettingsState + let privacyProInfo: PrivacyProInfo func toPrettyPrintedJSON() -> String? { let encoder = JSONEncoder() @@ -99,15 +116,21 @@ protocol VPNMetadataCollector { final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let statusObserver: ConnectionStatusObserver private let serverInfoObserver: ConnectionServerInfoObserver + private let accessManager: NetworkProtectionAccessController + private let tokenStore: NetworkProtectionTokenStore private let settings: VPNSettings private let defaults: UserDefaults init(statusObserver: ConnectionStatusObserver = ConnectionStatusObserverThroughSession(), serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), + networkProtectionAccessManager: NetworkProtectionAccessController = NetworkProtectionAccessController(), + tokenStore: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), settings: VPNSettings = .init(defaults: .networkProtectionGroupDefaults), defaults: UserDefaults = .networkProtectionGroupDefaults) { self.statusObserver = statusObserver self.serverInfoObserver = serverInfoObserver + self.accessManager = networkProtectionAccessManager + self.tokenStore = tokenStore self.settings = settings self.defaults = defaults } @@ -118,13 +141,15 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { let networkInfoMetadata = await collectNetworkInformation() let vpnState = await collectVPNState() let vpnSettingsState = collectVPNSettingsState() + let privacyProInfo = collectPrivacyProInfo() return VPNMetadata( appInfo: appInfoMetadata, deviceInfo: deviceInfoMetadata, networkInfo: networkInfoMetadata, vpnState: vpnState, - vpnSettingsState: vpnSettingsState + vpnSettingsState: vpnSettingsState, + privacyProInfo: privacyProInfo ) } @@ -242,7 +267,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } func collectVPNSettingsState() -> VPNMetadata.VPNSettingsState { - return .init( + .init( connectOnLoginEnabled: settings.connectOnLogin, includeAllNetworksEnabled: settings.includeAllNetworks, enforceRoutesEnabled: settings.enforceRoutes, @@ -251,25 +276,35 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { selectedServer: settings.selectedServer.stringValue ?? "automatic" ) } -} - -extension Network.NWPath { - /// A description that's safe from a privacy standpoint. - /// - /// Ref: https://app.asana.com/0/0/1206712493935053/1206712516729780/f - /// - var anonymousDescription: String { - var description = "NWPath(" - description += "status: \(status), " - - if #available(iOS 14.2, *), case .unsatisfied = status { - description += "unsatisfiedReason: \(unsatisfiedReason), " + func collectPrivacyProInfo() -> VPNMetadata.PrivacyProInfo { + let accessType = accessManager.networkProtectionAccessType() + var hasToken: Bool { + guard let token = try? tokenStore.fetchToken(), + !token.hasPrefix(NetworkProtectionKeychainTokenStore.authTokenPrefix) else { + return false + } + return true } - description += "availableInterfaces: \(availableInterfaces)" - description += ")" + return .init( + enableSource: .init(from: accessManager.networkProtectionAccessType()), + betaParticipant: accessType == .waitlistJoined, + hasToken: hasToken, + subscriptionActive: AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).accessToken != nil + ) + } +} - return description +extension VPNMetadata.PrivacyProInfo.Source { + init(from accessType: NetworkProtectionAccessType) { + switch accessType { + case .inviteCodeInvited: + self = .internal + case .waitlistInvited: + self = .waitlist + default: + self = .other + } } }