Skip to content

Commit

Permalink
Handle expired entitlement in NetP (#692)
Browse files Browse the repository at this point in the history
  • Loading branch information
quanganhdo authored Mar 7, 2024
1 parent 5b354c6 commit 9bafa02
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 114 deletions.
82 changes: 44 additions & 38 deletions Sources/NetworkProtection/PacketTunnelProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {

private let settings: VPNSettings

// MARK: - User Defaults

private let defaults: UserDefaults

// MARK: - Server Selection

public var lastSelectedServerInfo: NetworkProtectionServerInfo? {
Expand Down Expand Up @@ -299,6 +303,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
debugEvents: EventMapping<NetworkProtectionError>?,
providerEvents: EventMapping<Event>,
settings: VPNSettings,
defaults: UserDefaults,
isSubscriptionEnabled: Bool,
entitlementCheck: (() async -> Result<Bool, Error>)?) {
os_log("[+] PacketTunnelProvider", log: .networkProtectionMemoryLog, type: .debug)
Expand All @@ -311,6 +316,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
self.tunnelHealth = tunnelHealthStore
self.controllerErrorStore = controllerErrorStore
self.settings = settings
self.defaults = defaults
self.isSubscriptionEnabled = isSubscriptionEnabled
self.entitlementCheck = isSubscriptionEnabled ? entitlementCheck : nil

Expand Down Expand Up @@ -775,8 +781,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
)
} catch {
if isSubscriptionEnabled, let error = error as? NetworkProtectionError, case .vpnAccessRevoked = error {
os_log("🔵 Expired subscription", log: .networkProtection, type: .error)
settings.enableEntitlementMessaging()
await handleInvalidEntitlement(attemptsShutdown: false)
throw TunnelError.vpnAccessRevoked
}

Expand All @@ -795,13 +800,6 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
return configurationResult.0
}

/// Placeholder configuration to switch to when the entitlement expires
/// This will block all traffic
@MainActor
private func updatePlaceholderTunnelConfiguration() async throws {
// todo
}

// MARK: - App Messages

// swiftlint:disable:next cyclomatic_complexity
Expand Down Expand Up @@ -866,7 +864,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
settings.apply(change: change)
}

// swiftlint:disable:next cyclomatic_complexity function_body_length
// swiftlint:disable:next cyclomatic_complexity
private func handleSettingsChange(_ change: VPNSettings.Change, completionHandler: ((Data?) -> Void)? = nil) {
switch change {
case .setExcludeLocalNetworks:
Expand Down Expand Up @@ -908,15 +906,6 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
}
completionHandler?(nil)
}
case .setShowEntitlementNotification:
// todo - https://app.asana.com/0/0/1206409081785857/f
if settings.showEntitlementNotification {
notificationsPresenter.showEntitlementNotification { [weak self] error in
guard error == nil else { return }
self?.settings.apply(change: .setShowEntitlementNotification(false))
}
}
completionHandler?(nil)
case .setConnectOnLogin,
.setIncludeAllNetworks,
.setEnforceRoutes,
Expand All @@ -926,8 +915,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
.setShowInMenuBar,
.setVPNFirstEnabled,
.setNetworkPathChange,
.setDisableRekeying,
.setShowEntitlementAlert:
.setDisableRekeying:
// Intentional no-op, as some setting changes don't require any further operation
completionHandler?(nil)
}
Expand All @@ -943,8 +931,10 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
case .sendTestNotification:
handleSendTestNotification(completionHandler: completionHandler)
case .disableConnectOnDemandAndShutDown:
if #available(iOS 17, *) {
handleShutDown(completionHandler: completionHandler)
Task { [weak self] in
await self?.attemptShutdown {
completionHandler?(nil)
}
}
case .removeVPNConfiguration:
// Since the VPN configuration is being removed we may as well reset all state
Expand Down Expand Up @@ -1193,20 +1183,46 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
guard isSubscriptionEnabled, let entitlementCheck else { return }

await entitlementMonitor.start(entitlementCheck: entitlementCheck) { [weak self] result in
/// Attempt tunnel shutdown & show messaging iff the entitlement is verified to be invalid
/// Ignore otherwise
switch result {
case .validEntitlement:
self?.settings.resetEntitlementMessaging()
case .invalidEntitlement:
self?.settings.enableEntitlementMessaging()
Task { [weak self] in
await self?.attemptToShutdown()
await self?.handleInvalidEntitlement(attemptsShutdown: true)
}
case .error:
case .validEntitlement, .error:
break
}
}
}

@MainActor
private func handleInvalidEntitlement(attemptsShutdown: Bool) async {
defaults.enableEntitlementMessaging()
notificationsPresenter.showEntitlementNotification()

await stopMonitors()

// We add a delay here so the notification has a chance to show up
try? await Task.sleep(interval: .seconds(5))

if attemptsShutdown {
await attemptShutdown()
}
}

// Attempt to shut down the tunnel
// On iOS 16 and below, as a workaround, we rekey to force a 403 error so that the tunnel fails to restart
@MainActor
private func attemptShutdown(completion: (() -> Void)? = nil) async {
if #available(iOS 17, *) {
handleShutDown()
} else {
await rekey()
}
completion?()
}

@MainActor
public func startMonitors(testImmediately: Bool) async throws {
await startTunnelFailureMonitor()
Expand Down Expand Up @@ -1236,16 +1252,6 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
return true
}

private func attemptToShutdown() async {
await stopMonitors()

if #available(iOS 17, *) {
handleShutDown()
} else {
try? await updatePlaceholderTunnelConfiguration()
}
}

// MARK: - Connection Tester

private enum ConnectionTesterError: Error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import Common

extension UserDefaults {
private var showEntitlementAlertKey: String {
"networkProtectionShowEntitlementAlertRawValue"
"showEntitlementAlert"
}

@objc
dynamic var showEntitlementAlert: Bool {
public dynamic var showEntitlementAlert: Bool {
get {
value(forKey: showEntitlementAlertKey) as? Bool ?? false
}
Expand All @@ -43,16 +43,12 @@ extension UserDefaults {
}
}

var showEntitlementAlertPublisher: AnyPublisher<Bool, Never> {
publisher(for: \.showEntitlementAlert).eraseToAnyPublisher()
}

private var showEntitlementNotificationKey: String {
"networkProtectionShowEntitlementNotificationRawValue"
"showEntitlementNotification"
}

@objc
dynamic var showEntitlementNotification: Bool {
public dynamic var showEntitlementNotification: Bool {
get {
value(forKey: showEntitlementNotificationKey) as? Bool ?? false
}
Expand All @@ -69,12 +65,23 @@ extension UserDefaults {
}
}

var showEntitlementNotificationPublisher: AnyPublisher<Bool, Never> {
publisher(for: \.showEntitlementNotification).eraseToAnyPublisher()
public func enableEntitlementMessaging() {
showEntitlementAlert = true
showEntitlementNotification = true

#if os(iOS)
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(),
CFNotificationName(rawValue: Notification.Name.vpnEntitlementMessagingDidChange.rawValue as CFString),
nil, nil, true)
#endif
}

func resetEntitlementMessaging() {
public func resetEntitlementMessaging() {
removeObject(forKey: showEntitlementAlertKey)
removeObject(forKey: showEntitlementNotificationKey)
}
}

public extension Notification.Name {
static let vpnEntitlementMessagingDidChange = Notification.Name("com.duckduckgo.network-protection.entitlement-messaging-changed")
}
61 changes: 0 additions & 61 deletions Sources/NetworkProtection/Settings/VPNSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ public final class VPNSettings {
case setVPNFirstEnabled(_ vpnFirstEnabled: Date?)
case setNetworkPathChange(_ newPath: String?)
case setDisableRekeying(_ disableRekeying: Bool)
case setShowEntitlementAlert(_ showsAlert: Bool)
case setShowEntitlementNotification(_ showsNotification: Bool)
}

public enum RegistrationKeyValidity: Codable, Equatable {
Expand Down Expand Up @@ -185,20 +183,6 @@ public final class VPNSettings {
Change.setDisableRekeying(disableRekeying)
}.eraseToAnyPublisher()

let showEntitlementAlertPublisher = showEntitlementAlertPublisher
.dropFirst()
.removeDuplicates()
.map { showsAlert in
Change.setShowEntitlementAlert(showsAlert)
}.eraseToAnyPublisher()

let showEntitlementNotificationPublisher = showEntitlementNotificationPublisher
.dropFirst()
.removeDuplicates()
.map { showsNotification in
Change.setShowEntitlementNotification(showsNotification)
}.eraseToAnyPublisher()

return Publishers.MergeMany(
connectOnLoginPublisher,
includeAllNetworksPublisher,
Expand All @@ -211,8 +195,6 @@ public final class VPNSettings {
showInMenuBarPublisher,
vpnFirstEnabledPublisher,
networkPathChangePublisher,
showEntitlementAlertPublisher,
showEntitlementNotificationPublisher,
disableRekeyingPublisher).eraseToAnyPublisher()
}()

Expand Down Expand Up @@ -267,10 +249,6 @@ public final class VPNSettings {
newPath: newPath ?? "unknown")
case .setDisableRekeying(let disableRekeying):
self.disableRekeying = disableRekeying
case .setShowEntitlementAlert(let showsAlert):
self.showEntitlementAlert = showsAlert
case .setShowEntitlementNotification(let showsNotification):
self.showEntitlementNotification = showsNotification
}
}
// swiftlint:enable cyclomatic_complexity
Expand Down Expand Up @@ -510,43 +488,4 @@ public final class VPNSettings {
defaults.networkProtectionSettingDisableRekeying = newValue
}
}

// MARK: - Whether to show expired entitlement messaging

public var showEntitlementAlertPublisher: AnyPublisher<Bool, Never> {
defaults.showEntitlementAlertPublisher
}

public var showEntitlementAlert: Bool {
get {
defaults.showEntitlementAlert
}

set {
defaults.showEntitlementAlert = newValue
}
}

public var showEntitlementNotificationPublisher: AnyPublisher<Bool, Never> {
defaults.showEntitlementNotificationPublisher
}

public var showEntitlementNotification: Bool {
get {
defaults.showEntitlementNotification
}

set {
defaults.showEntitlementNotification = newValue
}
}

public func enableEntitlementMessaging() {
apply(change: .setShowEntitlementAlert(true))
apply(change: .setShowEntitlementNotification(true))
}

public func resetEntitlementMessaging() {
defaults.resetEntitlementMessaging()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ public protocol NetworkProtectionNotificationsPresenter {
func showTestNotification()

/// Present a "expired subscription" notification to the user.
func showEntitlementNotification(completion: @escaping (Error?) -> Void)
func showEntitlementNotification()
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import Foundation

final public class NetworkProtectionNotificationsPresenterTogglableDecorator: NetworkProtectionNotificationsPresenter {
private let settings: VPNSettings
private let defaults: UserDefaults
private let wrappeePresenter: NetworkProtectionNotificationsPresenter

public init(settings: VPNSettings, wrappee: NetworkProtectionNotificationsPresenter) {
public init(settings: VPNSettings, defaults: UserDefaults, wrappee: NetworkProtectionNotificationsPresenter) {
self.settings = settings
self.defaults = defaults
self.wrappeePresenter = wrappee
}

Expand Down Expand Up @@ -57,7 +59,10 @@ final public class NetworkProtectionNotificationsPresenterTogglableDecorator: Ne
}
}

public func showEntitlementNotification(completion: @escaping (Error?) -> Void) {
wrappeePresenter.showEntitlementNotification(completion: completion)
public func showEntitlementNotification() {
if defaults.showEntitlementNotification {
defaults.showEntitlementNotification = false
wrappeePresenter.showEntitlementNotification()
}
}
}

0 comments on commit 9bafa02

Please sign in to comment.