Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for entitlement in DBP agent #2802

Merged
merged 17 commits into from
May 23, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,16 @@

func registerForSubscriptionAccountManagerEvents() {
NotificationCenter.default.addObserver(self, selector: #selector(handleAccountDidSignOut), name: .accountDidSignOut, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(entitlementsDidChange), name: .entitlementsDidChange, object: nil)
}

@objc private func handleAccountDidSignOut() {
featureDisabler.disableAndDelete()
}

@objc private func entitlementsDidChange() {
#warning("Validate if valid and delete if necessary after sending pixels")

Check warning on line 42 in DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift

View workflow job for this annotation

GitHub Actions / Make Release Build

Validate if valid and delete if necessary after sending pixels
}
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// DataBrokerProtectionEntitlementMonitoring.swift
//
// Copyright © 2024 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

protocol DataBrokerProtectionEntitlementMonitoring {
func start(checkEntitlementFunction: @escaping () async throws -> Bool, intervalInMinutes: Int, callback: @escaping (DataBrokerProtectionEntitlementMonitorResult) -> Void)
func stop()
}

public enum DataBrokerProtectionEntitlementMonitorResult {
case enabled
case disabled
case error
}

final class DataBrokerProtectionEntitlementMonitor: DataBrokerProtectionEntitlementMonitoring {
private var timer: Timer?

func start(checkEntitlementFunction: @escaping () async throws -> Bool, intervalInMinutes: Int, callback: @escaping (DataBrokerProtectionEntitlementMonitorResult) -> Void) {
timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(intervalInMinutes * 60), repeats: true) { _ in
Task {
do {
switch try await checkEntitlementFunction() {
case true:
callback(.enabled)
case false:
callback(.disabled)
}
} catch {
callback(.error)
}
}
}
}

func stop() {
timer?.invalidate()
timer = nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@
contentScopeProperties: contentScopeProperties,
emailService: emailService,
captchaService: captchaService)
let operationDependencies = DefaultDataBrokerOperationDependencies(

let agentstopper = DefaultDataBrokerProtectionAgentStopper(dataManager: dataManager,
entitlementMonitor: DataBrokerProtectionEntitlementMonitor(),
authenticationManager: authenticationManager)

let operationDependencies = DefaultDataBrokerOperationDependencies(
database: dataManager.database,
config: executionConfig,
runnerProvider: runnerProvider,
Expand All @@ -86,7 +91,8 @@
queueManager: queueManager,
dataManager: dataManager,
operationDependencies: operationDependencies,
pixelHandler: pixelHandler)
pixelHandler: pixelHandler,
agentStopper: agentstopper)
}
}

Expand All @@ -99,6 +105,7 @@
private let dataManager: DataBrokerProtectionDataManaging
private let operationDependencies: DataBrokerOperationDependencies
private let pixelHandler: EventMapping<DataBrokerProtectionPixels>
private let agentStopper: DataBrokerProtectionAgentStopper

// Used for debug functions only, so not injected
private lazy var browserWindowManager = BrowserWindowManager()
Expand All @@ -111,14 +118,17 @@
queueManager: DataBrokerProtectionQueueManager,
dataManager: DataBrokerProtectionDataManaging,
operationDependencies: DataBrokerOperationDependencies,
pixelHandler: EventMapping<DataBrokerProtectionPixels>) {
pixelHandler: EventMapping<DataBrokerProtectionPixels>,
agentStopper: DataBrokerProtectionAgentStopper
) {
self.userNotificationService = userNotificationService
self.activityScheduler = activityScheduler
self.ipcServer = ipcServer
self.queueManager = queueManager
self.dataManager = dataManager
self.operationDependencies = operationDependencies
self.pixelHandler = pixelHandler
self.agentStopper = agentStopper

self.activityScheduler.delegate = self
self.ipcServer.serverDelegate = self
Expand All @@ -127,20 +137,18 @@

public func agentFinishedLaunching() {

do {
// If there's no saved profile we don't need to start the scheduler
// Theoretically this should never happen, if there's no data, the agent shouldn't be running
guard (try dataManager.fetchProfile()) != nil else {
return
}
} catch {
os_log("Error during AgentManager.agentFinishedLaunching when trying to fetchProfile, error: %{public}@", log: .dataBrokerProtection, error.localizedDescription)
return
}
Task { @MainActor in
// The browser shouldn't start the agent if these prerequisites aren't met.
// However, since the agent can auto-start after a reboot without the browser, we need to validate it again.
// If the agent needs to be stopped, this function will stop it, so the subsequent calls after it will not be made.
await agentStopper.validateRunPrerequisitesAndStopAgentIfNecessary()

activityScheduler.startScheduler()
didStartActivityScheduler = true
queueManager.startScheduledOperationsIfPermitted(showWebView: false, operationDependencies: operationDependencies, completion: nil)

activityScheduler.startScheduler()
didStartActivityScheduler = true
queueManager.startScheduledOperationsIfPermitted(showWebView: false, operationDependencies: operationDependencies, completion: nil)
agentStopper.monitorEntitlementAndStopAgentIfEntitlementIsInvalid()
}
}
}

Expand Down Expand Up @@ -283,3 +291,4 @@
extension DataBrokerProtectionAgentManager: DataBrokerProtectionAppToAgentInterface {

}

Check failure on line 294 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Files should have a single trailing newline (trailing_newline)
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// DataBrokerProtectionAgentStopper.swift
//
// 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 Common

protocol DataBrokerProtectionAgentStopper {
/// Validates if the user has profile data, is authenticated, and has valid entitlement. If any of these conditions are not met, the agent will be stopped.
func validateRunPrerequisitesAndStopAgentIfNecessary() async

/// Monitors the entitlement package. If the entitlement check returns false, the agent will be stopped.
Bunn marked this conversation as resolved.
Show resolved Hide resolved
/// This function ensures that the agent is stopped if the user's subscription has expired, even if the browser is not active. Regularly checking for entitlement is required since notifications are not posted to agents.
func monitorEntitlementAndStopAgentIfEntitlementIsInvalid()

Check failure on line 29 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Lines should not have trailing whitespace (trailing_whitespace)
/// Stops the agent
func stopAgent()
Bunn marked this conversation as resolved.
Show resolved Hide resolved
}

struct DefaultDataBrokerProtectionAgentStopper: DataBrokerProtectionAgentStopper {
private let dataManager: DataBrokerProtectionDataManaging
private let entitlementMonitor: DataBrokerProtectionEntitlementMonitoring
private let authenticationManager: DataBrokerProtectionAuthenticationManaging

init(dataManager: DataBrokerProtectionDataManaging,
entitlementMonitor: DataBrokerProtectionEntitlementMonitoring,
authenticationManager: DataBrokerProtectionAuthenticationManaging) {
self.dataManager = dataManager
self.entitlementMonitor = entitlementMonitor
self.authenticationManager = authenticationManager
}

public func validateRunPrerequisitesAndStopAgentIfNecessary() async {
do {
guard try dataManager.fetchProfile() != nil,
authenticationManager.isUserAuthenticated,
try await authenticationManager.hasValidEntitlement() else {

os_log("Prerequisites are invalid", log: .dataBrokerProtection)
stopAgent()
return
}
os_log("Prerequisites are valid", log: .dataBrokerProtection)
} catch {
os_log("Error validating prerequisites, error: %{public}@", log: .dataBrokerProtection, error.localizedDescription)
stopAgent()
}
}

public func monitorEntitlementAndStopAgentIfEntitlementIsInvalid() {
entitlementMonitor.start(checkEntitlementFunction: authenticationManager.hasValidEntitlement, intervalInMinutes: 60) { result in
switch result {
case .enabled:
#warning("Send pixel enabled")

Check warning on line 68 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Test (Non-Sandbox)

Send pixel enabled

Check warning on line 68 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Make Release Build

Send pixel enabled

Check warning on line 68 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Make Release Build

Send pixel enabled
stopAgent()
case .disabled:
#warning("Send pixel disabled")

Check warning on line 71 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Test (Non-Sandbox)

Send pixel disabled

Check warning on line 71 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Make Release Build

Send pixel disabled

Check warning on line 71 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Make Release Build

Send pixel disabled
case .error:
#warning("Send pixel error")

Check warning on line 73 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Test (Non-Sandbox)

Send pixel error

Check warning on line 73 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Make Release Build

Send pixel error

Check warning on line 73 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Make Release Build

Send pixel error
}
}
}

func stopAgent() {
os_log("Stopping DataBrokerProtection Agent", log: .dataBrokerProtection)
exit(EXIT_SUCCESS)
}
}
Loading