Skip to content

Commit

Permalink
Check DBP Prerequisites in App and Disable Login Item If Necessary (#…
Browse files Browse the repository at this point in the history
…2850)

Task/Issue URL:
https://app.asana.com/0/1206488453854252/1207501562611619/f

**Description**:
This PR introduces changes to check DBP prerequisites (user is
authenticated and has valid entitlements) from the App when the app
becomes active. When prerequisites are not satisfied, the app disables
the login item. This is to prevent a scenario where the background agent
checks prerequisites, exits due to failed prerequisites, and then is
automatically re-started by the login item.
  • Loading branch information
aataraxiaa authored Jun 12, 2024
1 parent b360f96 commit 75a5c9a
Show file tree
Hide file tree
Showing 28 changed files with 519 additions and 405 deletions.
60 changes: 41 additions & 19 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

19 changes: 4 additions & 15 deletions DuckDuckGo/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,15 +339,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

networkProtectionSubscriptionEventHandler?.registerForSubscriptionAccountManagerEvents()

NetworkProtectionAppEvents(featureVisibility: DefaultNetworkProtectionVisibility(subscriptionManager: subscriptionManager)).applicationDidFinishLaunching()
NetworkProtectionAppEvents(featureGatekeeper: DefaultVPNFeatureGatekeeper(subscriptionManager: subscriptionManager)).applicationDidFinishLaunching()
UNUserNotificationCenter.current().delegate = self

#if DBP
dataBrokerProtectionSubscriptionEventHandler.registerForSubscriptionAccountManagerEvents()
#endif

#if DBP
DataBrokerProtectionAppEvents().applicationDidFinishLaunching()
DataBrokerProtectionAppEvents(featureGatekeeper: DefaultDataBrokerProtectionFeatureGatekeeper(accountManager: accountManager)).applicationDidFinishLaunching()
#endif

setUpAutoClearHandler()
Expand Down Expand Up @@ -375,9 +375,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
syncService?.initializeIfNeeded()
syncService?.scheduler.notifyAppLifecycleEvent()

NetworkProtectionAppEvents(featureVisibility: DefaultNetworkProtectionVisibility(subscriptionManager: subscriptionManager)).applicationDidBecomeActive()
NetworkProtectionAppEvents(featureGatekeeper: DefaultVPNFeatureGatekeeper(subscriptionManager: subscriptionManager)).applicationDidBecomeActive()
#if DBP
DataBrokerProtectionAppEvents().applicationDidBecomeActive()
DataBrokerProtectionAppEvents(featureGatekeeper: DefaultDataBrokerProtectionFeatureGatekeeper(accountManager: accountManager)).applicationDidBecomeActive()
#endif

AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager.toggleProtectionsCounter.sendEventsIfNeeded()
Expand Down Expand Up @@ -677,17 +677,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
if response.actionIdentifier == UNNotificationDefaultActionIdentifier {

#if DBP
if response.notification.request.identifier == DataBrokerProtectionWaitlist.notificationIdentifier {
DispatchQueue.main.async {
DataBrokerProtectionAppEvents().handleWaitlistInvitedNotification(source: .localPush)
}
}
#endif
}

completionHandler()
}

Expand Down
1 change: 0 additions & 1 deletion DuckDuckGo/Common/Surveys/SurveyRemoteMessaging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ final class DefaultSurveyRemoteMessaging: SurveyRemoteMessaging {
subscriptionFetcher: SurveyRemoteMessageSubscriptionFetching,
vpnActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP),
pirActivationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .dbp),
networkProtectionVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(subscriptionManager: Application.appDelegate.subscriptionManager),
minimumRefreshInterval: TimeInterval,
userDefaults: UserDefaults = .standard
) {
Expand Down
63 changes: 34 additions & 29 deletions DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,41 @@ import Common
import DataBrokerProtection

struct DataBrokerProtectionAppEvents {
let pixelHandler: EventMapping<DataBrokerProtectionPixels> = DataBrokerProtectionPixelsHandler()

private let featureGatekeeper: DataBrokerProtectionFeatureGatekeeper
private let pixelHandler: EventMapping<DataBrokerProtectionPixels>
private let loginItemsManager: LoginItemsManaging
private let loginItemInterface: DataBrokerProtectionLoginItemInterface

enum WaitlistNotificationSource {
case localPush
case cardUI
}

func applicationDidFinishLaunching() {
let loginItemsManager = LoginItemsManager()
let featureVisibility = DefaultDataBrokerProtectionFeatureVisibility()
let loginItemInterface = DataBrokerProtectionManager.shared.loginItemInterface
init(featureGatekeeper: DataBrokerProtectionFeatureGatekeeper,
pixelHandler: EventMapping<DataBrokerProtectionPixels> = DataBrokerProtectionPixelsHandler(),
loginItemsManager: LoginItemsManaging = LoginItemsManager(),
loginItemInterface: DataBrokerProtectionLoginItemInterface = DataBrokerProtectionManager.shared.loginItemInterface) {
self.featureGatekeeper = featureGatekeeper
self.pixelHandler = pixelHandler
self.loginItemsManager = loginItemsManager
self.loginItemInterface = loginItemInterface
}

guard !featureVisibility.cleanUpDBPForPrivacyProIfNecessary() else { return }
func applicationDidFinishLaunching() {
guard !featureGatekeeper.cleanUpDBPForPrivacyProIfNecessary() else { return }

/// If the user is not in the waitlist and Privacy Pro flag is false, we want to remove the data for waitlist users
/// since the waitlist flag might have been turned off
if !featureVisibility.isFeatureVisible() && !featureVisibility.isPrivacyProEnabled() {
featureVisibility.disableAndDeleteForWaitlistUsers()
if !featureGatekeeper.isFeatureVisible() && !featureGatekeeper.isPrivacyProEnabled() {
featureGatekeeper.disableAndDeleteForWaitlistUsers()
return
}

Task {
try? await DataBrokerProtectionWaitlist().redeemDataBrokerProtectionInviteCodeIfAvailable()
let loginItemsManager = LoginItemsManager()
let loginItemInterface = DataBrokerProtectionManager.shared.loginItemInterface

Task {
// If we don't have profileQueries it means there's no user profile saved in our DB
// In this case, let's disable the agent and delete any left-over data because there's nothing for it to do
if let profileQueriesCount = try? DataBrokerProtectionManager.shared.dataManager.profileQueriesCount(),
Expand All @@ -58,27 +69,31 @@ struct DataBrokerProtectionAppEvents {
try await Task.sleep(nanoseconds: 1_000_000_000)
loginItemInterface.appLaunched()
} else {
featureVisibility.disableAndDeleteForWaitlistUsers()
featureGatekeeper.disableAndDeleteForWaitlistUsers()
}
}

}

func applicationDidBecomeActive() {
let featureVisibility = DefaultDataBrokerProtectionFeatureVisibility()

guard !featureVisibility.cleanUpDBPForPrivacyProIfNecessary() else { return }
// Check feature prerequisites and disable the login item if they are not satisfied
Task { @MainActor in
let prerequisitesMet = await featureGatekeeper.arePrerequisitesSatisfied()
guard prerequisitesMet else {
loginItemsManager.disableLoginItems([LoginItem.dbpBackgroundAgent])
return
}
}

guard !featureGatekeeper.cleanUpDBPForPrivacyProIfNecessary() else { return }

/// If the user is not in the waitlist and Privacy Pro flag is false, we want to remove the data for waitlist users
/// since the waitlist flag might have been turned off
if !featureVisibility.isFeatureVisible() && !featureVisibility.isPrivacyProEnabled() {
featureVisibility.disableAndDeleteForWaitlistUsers()
if !featureGatekeeper.isFeatureVisible() && !featureGatekeeper.isPrivacyProEnabled() {
featureGatekeeper.disableAndDeleteForWaitlistUsers()
return
}

Task {
try? await DataBrokerProtectionWaitlist().redeemDataBrokerProtectionInviteCodeIfAvailable()
}
}

@MainActor
Expand All @@ -95,16 +110,6 @@ struct DataBrokerProtectionAppEvents {
}
}

func windowDidBecomeMain() {
sendActiveDataBrokerProtectionWaitlistUserPixel()
}

private func sendActiveDataBrokerProtectionWaitlistUserPixel() {
if DefaultDataBrokerProtectionFeatureVisibility().waitlistIsOngoing {
DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistUserActive, frequency: .daily)
}
}

private func restartBackgroundAgent(loginItemsManager: LoginItemsManager) {
DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerResetLoginItemDaily, frequency: .daily)
loginItemsManager.disableLoginItems([LoginItem.dbpBackgroundAgent])
Expand Down
37 changes: 0 additions & 37 deletions DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ final class DataBrokerProtectionDebugMenu: NSMenu {
private let waitlistTimestampItem = NSMenuItem(title: "Waitlist Timestamp:")
private let waitlistInviteCodeItem = NSMenuItem(title: "Waitlist Invite Code:")
private let waitlistTermsAndConditionsAcceptedItem = NSMenuItem(title: "T&C Accepted:")
private let waitlistBypassItem = NSMenuItem(title: "Bypass Waitlist", action: #selector(DataBrokerProtectionDebugMenu.toggleBypassWaitlist))

private let productionURLMenuItem = NSMenuItem(title: "Use Production URL", action: #selector(DataBrokerProtectionDebugMenu.useWebUIProductionURL))

Expand Down Expand Up @@ -67,14 +66,8 @@ final class DataBrokerProtectionDebugMenu: NSMenu {
NSMenuItem(title: "Send Notification", action: #selector(DataBrokerProtectionDebugMenu.sendWaitlistAvailableNotification))
.targetting(self)

NSMenuItem(title: "Fetch Invite Code", action: #selector(DataBrokerProtectionDebugMenu.fetchInviteCode))
.targetting(self)

NSMenuItem.separator()

waitlistBypassItem
.targetting(self)

NSMenuItem.separator()

waitlistTokenItem
Expand Down Expand Up @@ -172,7 +165,6 @@ final class DataBrokerProtectionDebugMenu: NSMenu {
// MARK: - Menu State Update

override func update() {
updateWaitlistItems()
updateWebUIMenuItemsState()
updateEnvironmentMenu()
updateShowStatusMenuIconMenu()
Expand Down Expand Up @@ -311,10 +303,6 @@ final class DataBrokerProtectionDebugMenu: NSMenu {
os_log("DBP waitlist state cleaned", log: .dataBrokerProtection)
}

@objc private func toggleBypassWaitlist() {
DefaultDataBrokerProtectionFeatureVisibility.bypassWaitlist.toggle()
}

@objc private func resetTermsAndConditionsAcceptance() {
UserDefaults().removeObject(forKey: UserDefaultsWrapper<Bool>.Key.dataBrokerProtectionTermsAndConditionsAccepted.rawValue)
NotificationCenter.default.post(name: .dataBrokerProtectionWaitlistAccessChanged, object: nil)
Expand All @@ -327,14 +315,6 @@ final class DataBrokerProtectionDebugMenu: NSMenu {
os_log("DBP waitlist notification sent", log: .dataBrokerProtection)
}

@objc private func fetchInviteCode() {
os_log("Fetching invite code...", log: .dataBrokerProtection)

Task {
try? await DataBrokerProtectionWaitlist().redeemDataBrokerProtectionInviteCodeIfAvailable()
}
}

@objc private func toggleShowStatusMenuItem() {
settings.showInMenuBar.toggle()
}
Expand Down Expand Up @@ -386,23 +366,6 @@ final class DataBrokerProtectionDebugMenu: NSMenu {
return menuItem
}

private func updateWaitlistItems() {
let waitlistStorage = WaitlistKeychainStore(waitlistIdentifier: DataBrokerProtectionWaitlist.identifier, keychainAppGroup: Bundle.main.appGroup(bundle: .dbp))
waitlistTokenItem.title = "Waitlist Token: \(waitlistStorage.getWaitlistToken() ?? "N/A")"
waitlistInviteCodeItem.title = "Waitlist Invite Code: \(waitlistStorage.getWaitlistInviteCode() ?? "N/A")"

if let timestamp = waitlistStorage.getWaitlistTimestamp() {
waitlistTimestampItem.title = "Waitlist Timestamp: \(String(describing: timestamp))"
} else {
waitlistTimestampItem.title = "Waitlist Timestamp: N/A"
}

let accepted = UserDefaults().bool(forKey: UserDefaultsWrapper<Bool>.Key.dataBrokerProtectionTermsAndConditionsAccepted.rawValue)
waitlistTermsAndConditionsAcceptedItem.title = "T&C Accepted: \(accepted ? "Yes" : "No")"

waitlistBypassItem.state = DefaultDataBrokerProtectionFeatureVisibility.bypassWaitlist ? .on : .off
}

private func updateEnvironmentMenu() {
let selectedEnvironment = settings.selectedEnvironment
guard environmentMenu.items.count == 3 else { return }
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct DataBrokerProtectionFeatureDisabler: DataBrokerProtectionFeatureDisabling
}

func disableAndDelete() {
if !DefaultDataBrokerProtectionFeatureVisibility.bypassWaitlist {
if !DefaultDataBrokerProtectionFeatureGatekeeper.bypassWaitlist {

do {
try dataManager.removeAllData()
Expand Down
Loading

0 comments on commit 75a5c9a

Please sign in to comment.