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 committed Sep 18, 2024
1 parent 9338127 commit fe9d1bb
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 @@ -287,7 +287,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 @@ -790,8 +790,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 @@ -806,7 +806,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 @@ -37,7 +37,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
private var mockConfigurationManager: MockConfigurationManager!
private var mockPrivacyConfigurationManager: DBPPrivacyConfigurationManager!
private var mockAuthenticationManager: MockAuthenticationManager!
private var mockFreemiumPIRUserState: MockFreemiumPIRUserState!
private var mockFreemiumPIRUserStateManager: MockFreemiumPIRUserStateManager!

override func setUpWithError() throws {

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

mockFreemiumPIRUserState = MockFreemiumPIRUserState()
mockFreemiumPIRUserStateManager = MockFreemiumPIRUserStateManager()
}

func testWhenAgentStart_andProfileExists_andUserIsNotFreemium_thenActivityIsScheduled_andScheduledAllOperationsRun() async throws {
Expand All @@ -93,11 +93,11 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
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 @@ -136,10 +136,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
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 @@ -171,7 +171,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 @@ -184,10 +184,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
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 @@ -221,7 +221,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockDataManager.profileToReturn = nil

Expand Down Expand Up @@ -262,11 +262,11 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
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 Down Expand Up @@ -294,10 +294,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

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

var startScheduledScansCalled = false
mockQueueManager.startScheduledScanOperationsIfPermittedCalledCompletion = { _ in
Expand Down Expand Up @@ -325,10 +325,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

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

var startImmediateScansCalled = false
mockQueueManager.startImmediateScanOperationsIfPermittedCalledCompletion = { _ in
Expand Down Expand Up @@ -356,10 +356,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

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

var startImmediateScansCalled = false
mockQueueManager.startImmediateScanOperationsIfPermittedCalledCompletion = { _ in
Expand Down Expand Up @@ -387,7 +387,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()

Expand All @@ -412,7 +412,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()

Expand All @@ -437,7 +437,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()
mockQueueManager.startImmediateScanOperationsIfPermittedCompletionError = DataBrokerProtectionAgentErrorCollection(oneTimeError: NSError(domain: "test", code: 10))
Expand All @@ -463,7 +463,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()
mockDataManager.shouldReturnHasMatches = true
Expand All @@ -489,7 +489,7 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

mockNotificationService.reset()
mockDataManager.shouldReturnHasMatches = false
Expand All @@ -515,10 +515,10 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
authenticationManager: mockAuthenticationManager,
freemiumPIRUserStateManager: mockFreemiumPIRUserState)
freemiumPIRUserStateManager: mockFreemiumPIRUserStateManager)

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

var startScheduledScansCalled = false
mockQueueManager.startScheduledAllOperationsIfPermittedCalledCompletion = { _ in
Expand Down Expand Up @@ -546,9 +546,9 @@ final class DataBrokerProtectionAgentManagerTests: XCTestCase {
configurationManager: mockConfigurationManager,
privacyConfigurationManager: mockPrivacyConfigurationManager,
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 fe9d1bb

Please sign in to comment.