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(handleAccountDidSignOut), name: .entitlementsDidChange, object: nil)
Bunn marked this conversation as resolved.
Show resolved Hide resolved
}

@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,65 @@
//
// 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()
}

extension DataBrokerProtectionEntitlementMonitoring {
func start(checkEntitlementFunction: @escaping () async throws -> Bool, callback: @escaping (DataBrokerProtectionEntitlementMonitorResult) -> Void) {
start(checkEntitlementFunction: checkEntitlementFunction,
intervalInMinutes: DataBrokerProtectionEntitlementMonitor.monitoringInterval,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: I don’t think it will cause issues, but something about the protocol extension referencing the concrete type static variable seems strange, or inverted. Maybe it’s a pattern, unsure, but wanted to highlight. Other options would be to just declare the constant in the function, or if we want every concrete type to have it’s own interval, define monitoringInterval as a protocol requirement.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declaring the const in the function is the way to go IMO. The static const there is a leftover from some tests. Good call, thanks. I'll fix it

callback: callback)
}
}

public enum DataBrokerProtectionEntitlementMonitorResult {
case enabled
case disabled
case error
}

final class DataBrokerProtectionEntitlementMonitor: DataBrokerProtectionEntitlementMonitoring {
private var timer: Timer?
static let monitoringInterval = 20

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,17 @@

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
// But since the agent can auto-start after a reboot without the browser, we need to validate it again
await agentStopper.validateRunPreRequisitesAndStopAgentIfNecessary()
Bunn marked this conversation as resolved.
Show resolved Hide resolved

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.monitorEntitlementAndStopAgentIfNecessary()
}
}
}

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

}

Check failure on line 293 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,76 @@
//
// 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 {
func validateRunPreRequisitesAndStopAgentIfNecessary() async
Bunn marked this conversation as resolved.
Show resolved Hide resolved
func monitorEntitlementAndStopAgentIfNecessary()
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 monitorEntitlementAndStopAgentIfNecessary() {
entitlementMonitor.start(checkEntitlementFunction: authenticationManager.hasValidEntitlement) { result in
switch result {
case .enabled:
#warning("Send pixel enabled")

Check warning on line 62 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 62 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 62 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 62 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 62 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Test (Non-Sandbox)

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

Check warning on line 65 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 65 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 65 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 65 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 65 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Test (Non-Sandbox)

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

Check warning on line 67 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 67 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 67 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 67 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 67 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift

View workflow job for this annotation

GitHub Actions / Test (Non-Sandbox)

Send pixel error
}
}
}

func stopAgent() {
os_log("Stopping DataBrokerProtection Agent", log: .dataBrokerProtection)
exit(EXIT_SUCCESS)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ struct Logging {

fileprivate static let backgroundAgentPixelLoggingEnabled = true
fileprivate static let backgroundAgentPixel: OSLog = OSLog(subsystem: subsystem, category: "Data Broker Protection Background Agent Pixel")

fileprivate static let dataBrokerProtectionMonitoringEnabled = true
fileprivate static let dataBrokerProtectionMonitoring: OSLog = OSLog(subsystem: subsystem, category: "Data Broker Protection Monitoring")
Bunn marked this conversation as resolved.
Show resolved Hide resolved

}

extension OSLog {
Expand Down Expand Up @@ -74,4 +78,8 @@ extension OSLog {
public static var dbpBackgroundAgentPixel: OSLog {
Logging.backgroundAgentPixelLoggingEnabled ? Logging.backgroundAgentPixel : .disabled
}

public static var dataBrokerProtectionMonitoring: OSLog {
Logging.dataBrokerProtectionMonitoringEnabled ? Logging.dataBrokerProtectionMonitoring : .disabled
}
}
Loading