Skip to content

Commit

Permalink
Freemium PIR: Disable and Delete PIR If the Freemium Feature Is Disab…
Browse files Browse the repository at this point in the history
…led (#3200)

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

**Description**: This PR implements disabling of PIR and deletion of
related data when:

1. The user had onboarded to Freemium AND
2. The feature flag is disabled
  • Loading branch information
aataraxiaa authored Sep 4, 2024
1 parent 14a2386 commit e607649
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 119 deletions.
49 changes: 46 additions & 3 deletions DuckDuckGo/Freemium/PIR/FreemiumPIRFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ final class DefaultFreemiumPIRFeature: FreemiumPIRFeature {
private let featureFlagger: FeatureFlagger
private let subscriptionManager: SubscriptionManager
private let accountManager: AccountManager
private var freemiumPIRUserStateManager: FreemiumPIRUserStateManager
private let featureDisabler: DataBrokerProtectionFeatureDisabling

var isAvailable: Bool {
/* Freemium PIR availability criteria:
Expand All @@ -41,18 +43,59 @@ final class DefaultFreemiumPIRFeature: FreemiumPIRFeature {
4. (Temp) In experiment cohort
*/
featureFlagger.isFeatureOn(.freemiumPIR) // #1
&& subscriptionManager.isPrivacyProPurchaseAvailable // #2
&& !accountManager.isUserAuthenticated // #3
&& isPotentialPrivacyProSubscriber // #2 & #3
// TODO: - Also check experiment cohort here
}

init(featureFlagger: FeatureFlagger = NSApp.delegateTyped.featureFlagger,
subscriptionManager: SubscriptionManager,
accountManager: AccountManager) {
accountManager: AccountManager,
freemiumPIRUserStateManager: FreemiumPIRUserStateManager,
featureDisabler: DataBrokerProtectionFeatureDisabling = DataBrokerProtectionFeatureDisabler()) {

self.featureFlagger = featureFlagger
self.subscriptionManager = subscriptionManager
self.accountManager = accountManager
self.freemiumPIRUserStateManager = freemiumPIRUserStateManager
self.featureDisabler = featureDisabler

offBoardIfNecessary()
}
}

private extension DefaultFreemiumPIRFeature {

/// Returns true if a user is a "potential" Privacy Pro subscriber. This means:
///
/// 1. Is eligible to purchase
/// 2. Is not a current subscriber
var isPotentialPrivacyProSubscriber: Bool {
subscriptionManager.isPrivacyProPurchaseAvailable
&& !accountManager.isUserAuthenticated
}

/// Returns true IFF:
///
/// 1. The user did onboard to Freemium PIR
/// 2. The feature flag is disabled
/// 3. The user `isPotentialPrivacyProSubscriber` (see definition)
var shouldDisableAndDelete: Bool {
guard freemiumPIRUserStateManager.didOnboard else { return false }

return !featureFlagger.isFeatureOn(.freemiumPIR)
&& isPotentialPrivacyProSubscriber
}

/// This method offboards a Freemium user if the feature flag was disabled
///
/// Offboarding involves:
/// - Resettting `FreemiumPIRUserStateManager`state
/// - Disabling and deleting PIR data
func offBoardIfNecessary() {
if shouldDisableAndDelete {
freemiumPIRUserStateManager.didOnboard = false
featureDisabler.disableAndDelete()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ final class NavigationBarViewController: NSViewController {
@IBAction func optionsButtonAction(_ sender: NSButton) {
let internalUserDecider = NSApp.delegateTyped.internalUserDecider
let freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp)
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager)
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager, freemiumPIRUserStateManager: freemiumPIRUserStateManager)
let menu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel,
passwordManagerCoordinator: PasswordManagerCoordinator.shared,
vpnFeatureGatekeeper: DefaultVPNFeatureGatekeeper(subscriptionManager: subscriptionManager),
Expand Down
5 changes: 3 additions & 2 deletions DuckDuckGo/Tab/View/BrowserTabViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -675,8 +675,8 @@ final class BrowserTabViewController: NSViewController {
private func homePageViewControllerCreatingIfNeeded() -> HomePageViewController {
return homePageViewController ?? {
let subscriptionManager = Application.appDelegate.subscriptionManager
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager)
let freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp)
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager, freemiumPIRUserStateManager: freemiumPIRUserStateManager)
let homePageViewController = HomePageViewController(tabCollectionViewModel: tabCollectionViewModel, bookmarkManager: bookmarkManager,
freemiumPIRFeature: freemiumPIRFeature,
freemiumPIRUserStateManager: freemiumPIRUserStateManager)
Expand All @@ -691,7 +691,8 @@ final class BrowserTabViewController: NSViewController {
private func dataBrokerProtectionHomeViewControllerCreatingIfNeeded() -> DBPHomeViewController {
return dataBrokerProtectionHomeViewController ?? {
let subscriptionManager = Application.appDelegate.subscriptionManager
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager)
let freemiumPIRUserStateManager = DefaultFreemiumPIRUserStateManager(userDefaults: .dbp)
let freemiumPIRFeature = DefaultFreemiumPIRFeature(subscriptionManager: subscriptionManager, accountManager: subscriptionManager.accountManager, freemiumPIRUserStateManager: freemiumPIRUserStateManager)
let dataBrokerProtectionHomeViewController = DBPHomeViewController(dataBrokerProtectionManager: DataBrokerProtectionManager.shared, freemiumPIRFeature: freemiumPIRFeature)
self.dataBrokerProtectionHomeViewController = dataBrokerProtectionHomeViewController
return dataBrokerProtectionHomeViewController
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
private var mockProfile: DataBrokerProtectionProfile!
private var mockAgentStopper: MockAgentStopper!
private var mockAuthenticationManager: MockAuthenticationManager!
private var mockFreemiumPIRUserState: MockFreemiumPIRUserState!
private var mockFreemiumPIRUserStateManager: MockFreemiumPIRUserStateManager!

override func setUpWithError() throws {

Expand Down Expand Up @@ -70,7 +70,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
phones: [],
birthYear: 1992)

mockFreemiumPIRUserState = MockFreemiumPIRUserState()
mockFreemiumPIRUserStateManager = MockFreemiumPIRUserStateManager()
}

func testWhenAgentStart_andProfileExists_andUserIsNotFreemium_thenActivityIsScheduled_andScheduledAllOperationsRun() async throws {
Expand All @@ -85,11 +85,11 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockAuthenticationManager.isUserAuthenticatedValue = true
mockFreemiumPIRUserState.didOnboard = false
mockFreemiumPIRUserStateManager.didOnboard = false

let schedulerStartedExpectation = XCTestExpectation(description: "Scheduler started")
var schedulerStarted = false
Expand Down Expand Up @@ -126,10 +126,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockFreemiumPIRUserState.didOnboard = true
mockFreemiumPIRUserStateManager.didOnboard = true

let schedulerStartedExpectation = XCTestExpectation(description: "Scheduler started")
var schedulerStarted = false
Expand Down Expand Up @@ -161,7 +161,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
entitlementMonitor: DataBrokerProtectionEntitlementMonitor(),
authenticationManager: MockAuthenticationManager(),
pixelHandler: mockPixelHandler,
stopAction: mockStopAction, freemiumPIRUserStateManager: MockFreemiumPIRUserState())
stopAction: mockStopAction, freemiumPIRUserStateManager: MockFreemiumPIRUserStateManager())
sut = DataBrokerProtectionAgentManager(
userNotificationService: mockNotificationService,
activityScheduler: mockActivityScheduler,
Expand All @@ -172,10 +172,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: agentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = nil
mockFreemiumPIRUserState.didOnboard = true
mockFreemiumPIRUserStateManager.didOnboard = true

let stopAgentExpectation = XCTestExpectation(description: "Stop agent expectation")

Expand Down Expand Up @@ -207,7 +207,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = nil

Expand Down Expand Up @@ -246,11 +246,11 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockAuthenticationManager.isUserAuthenticatedValue = true
mockFreemiumPIRUserState.didOnboard = false
mockFreemiumPIRUserStateManager.didOnboard = false

var startScheduledScansCalled = false
mockQueueManager.startScheduledAllOperationsIfPermittedCalledCompletion = { _ in
Expand All @@ -276,10 +276,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockFreemiumPIRUserState.didOnboard = true
mockFreemiumPIRUserStateManager.didOnboard = true

var startScheduledScansCalled = false
mockQueueManager.startScheduledScanOperationsIfPermittedCalledCompletion = { _ in
Expand All @@ -305,10 +305,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockFreemiumPIRUserState.didOnboard = false
mockFreemiumPIRUserStateManager.didOnboard = false

var startImmediateScansCalled = false
mockQueueManager.startImmediateScanOperationsIfPermittedCalledCompletion = { _ in
Expand All @@ -334,10 +334,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = mockProfile
mockFreemiumPIRUserState.didOnboard = true
mockFreemiumPIRUserStateManager.didOnboard = true

var startImmediateScansCalled = false
mockQueueManager.startImmediateScanOperationsIfPermittedCalledCompletion = { _ in
Expand All @@ -363,7 +363,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()

Expand All @@ -386,7 +386,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()

Expand All @@ -409,7 +409,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()
mockQueueManager.startImmediateScanOperationsIfPermittedCompletionError = DataBrokerProtectionAgentErrorCollection(oneTimeError: NSError(domain: "test", code: 10))
Expand All @@ -433,7 +433,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()
mockDataManager.shouldReturnHasMatches = true
Expand All @@ -457,7 +457,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()
mockDataManager.shouldReturnHasMatches = false
Expand All @@ -481,10 +481,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockAuthenticationManager.isUserAuthenticatedValue = true
mockFreemiumPIRUserState.didOnboard = false
mockFreemiumPIRUserStateManager.didOnboard = false

var startScheduledScansCalled = false
mockQueueManager.startScheduledAllOperationsIfPermittedCalledCompletion = { _ in
Expand All @@ -510,9 +510,9 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
pixelHandler: mockPixelHandler,
agentStopper: mockAgentStopper,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockFreemiumPIRUserState.didOnboard = true
mockFreemiumPIRUserStateManager.didOnboard = true

var startScheduledScansCalled = false
mockQueueManager.startScheduledScanOperationsIfPermittedCalledCompletion = { _ in
Expand Down
Loading

0 comments on commit e607649

Please sign in to comment.