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

Handle expired entitlement in NetP #692

Merged
merged 27 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ac4b3a0
Add separate SubscriptionTokenStorage to store token to be shared
miasma13 Feb 21, 2024
50a6344
Store and read access token from its separate storage
miasma13 Feb 21, 2024
d4dfb75
Refactor how queries are built
miasma13 Feb 21, 2024
257f8d1
Clean up
miasma13 Feb 21, 2024
c0c2963
Update to SubscriptionTokenKeychainStorage to support testString
miasma13 Feb 21, 2024
fa7fab4
Add notification for messaging changes
quanganhdo Feb 28, 2024
ffa482c
wip
graeme Feb 28, 2024
5d056b5
Remove test string code
graeme Feb 28, 2024
ff7d1ae
REmove label constant etc
graeme Feb 28, 2024
4348c74
Fix bug caused by mismatch of key values
graeme Feb 28, 2024
ad650ce
Add basic migration of token
graeme Mar 1, 2024
6bb195f
Move migration to dedicated public function
graeme Mar 1, 2024
815903c
Refactor
quanganhdo Feb 28, 2024
fc8ee8d
Use Darwin notification instead
quanganhdo Feb 29, 2024
6d46796
Show notification from app extension right away
quanganhdo Feb 29, 2024
7f854c2
Move presentation logic inside TogglableDecorator
quanganhdo Feb 29, 2024
c104e60
Refactoring
quanganhdo Feb 29, 2024
e69f05f
Block all traffic
quanganhdo Mar 1, 2024
5092a36
Fix Swiftlint
quanganhdo Mar 4, 2024
1ab1e29
Attempt shutdown
quanganhdo Mar 6, 2024
1de20d1
Refactor
quanganhdo Mar 6, 2024
dfc2e5c
More refactoring
quanganhdo Mar 6, 2024
1866d5e
Merge branch 'main' into anh/netp-handle-expired-entitlement
quanganhdo Mar 6, 2024
d1d42f9
Remove unnecessary import
quanganhdo Mar 6, 2024
ac7db80
Update property visibility
quanganhdo Mar 6, 2024
4e3a1b6
Revert "Update property visibility"
quanganhdo Mar 6, 2024
e88e24f
Merge branch 'main' into anh/netp-handle-expired-entitlement
quanganhdo Mar 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
}
62 changes: 1 addition & 61 deletions Sources/NetworkProtection/Settings/VPNSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import Combine
import Foundation
import Common

/// Persists and publishes changes to tunnel settings.
///
Expand All @@ -41,8 +42,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 +184,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 +196,6 @@ public final class VPNSettings {
showInMenuBarPublisher,
vpnFirstEnabledPublisher,
networkPathChangePublisher,
showEntitlementAlertPublisher,
showEntitlementNotificationPublisher,
disableRekeyingPublisher).eraseToAnyPublisher()
}()

Expand Down Expand Up @@ -267,10 +250,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 +489,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()
}
}
}
Loading
Loading