Merge branch 'handle-malicious-refunds'

This commit is contained in:
Davide De Rosa 2019-11-09 18:08:17 +01:00
commit 1ed4d32d5d
4 changed files with 80 additions and 5 deletions

View File

@ -27,6 +27,9 @@ import UIKit
import TunnelKit
import PassepartoutCore
import Convenience
import SwiftyBeaver
private let log = SwiftyBeaver.self
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
@ -79,6 +82,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
ProductManager.shared.reviewPurchases()
}
func applicationWillTerminate(_ application: UIApplication) {
@ -161,6 +165,9 @@ extension UISplitViewController {
extension AppDelegate {
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard ProductManager.shared.isEligible(forFeature: .siriShortcuts) else {
return false
}
guard let interaction = userActivity.interaction else {
return false
}

View File

@ -34,6 +34,8 @@ private let log = SwiftyBeaver.self
class ProductManager: NSObject {
static let didReloadReceipt = Notification.Name("ProductManagerDidReloadReceipt")
static let didReviewPurchases = Notification.Name("ProductManagerDidReviewPurchases")
private static let lastFullVersionBuild = 2016 // 1.8.1
@ -96,7 +98,7 @@ class ProductManager: NSObject {
// MARK: In-app eligibility
private func reloadReceipt() {
private func reloadReceipt(andNotify: Bool = true) {
guard let url = Bundle.main.appStoreReceiptURL else {
log.warning("No App Store receipt found!")
return
@ -122,21 +124,27 @@ class ProductManager: NSObject {
if let iapReceipts = receipt.inAppPurchaseReceipts {
log.debug("In-app receipts:")
iapReceipts.forEach {
guard let pid = $0.productIdentifier, let date = $0.originalPurchaseDate else {
guard let pid = $0.productIdentifier, let purchaseDate = $0.originalPurchaseDate else {
return
}
log.debug("\t\(pid) [\(date)]")
log.debug("\t\(pid) [purchased on: \(purchaseDate)]")
}
for r in iapReceipts {
guard let pid = r.productIdentifier, let product = Product(rawValue: pid) else {
continue
}
if let cancellationDate = r.cancellationDate {
log.debug("\t\(pid) [cancelled on: \(cancellationDate)]")
continue
}
purchasedFeatures.insert(product)
}
}
log.info("Purchased features: \(purchasedFeatures)")
NotificationCenter.default.post(name: ProductManager.didReloadReceipt, object: nil)
if andNotify {
NotificationCenter.default.post(name: ProductManager.didReloadReceipt, object: nil)
}
}
func isFullVersion() -> Bool {
@ -165,6 +173,60 @@ class ProductManager: NSObject {
func isEligibleForFeedback() -> Bool {
return AppConstants.Flags.isBeta || !purchasedFeatures.isEmpty
}
// MARK: Review
func reviewPurchases() {
let service = TransientStore.shared.service
reloadReceipt(andNotify: false)
var shouldReinstall = false
// review features and potentially revert them if they were used (Siri is handled in AppDelegate)
log.debug("Checking 'Trusted networks'")
if !isEligible(forFeature: .trustedNetworks) {
if service.preferences.trustsMobileNetwork || !service.preferences.trustedWifis.isEmpty {
service.preferences.trustsMobileNetwork = false
service.preferences.trustedWifis.removeAll()
log.debug("\tRefunded")
shouldReinstall = true
}
}
log.debug("Checking 'Unlimited hosts'")
if !isEligible(forFeature: .unlimitedHosts) {
let ids = service.ids(forContext: .host)
if ids.count > AppConstants.InApp.limitedNumberOfHosts {
for id in ids {
service.removeProfile(ProfileKey(.host, id))
}
log.debug("\tRefunded")
shouldReinstall = true
}
}
log.debug("Checking providers")
for name in service.currentProviderNames() {
if !isEligible(forProvider: name) {
service.removeProfile(ProfileKey(name))
log.debug("\tRefunded provider: \(name)")
shouldReinstall = true
}
}
// no refunds
guard shouldReinstall else {
return
}
//
// save reverts and remove fraud VPN profile
TransientStore.shared.serialize(withProfiles: true)
VPN.shared.uninstall(completionHandler: nil)
NotificationCenter.default.post(name: ProductManager.didReviewPurchases, object: nil)
}
}
extension ConnectionService {

View File

@ -120,6 +120,7 @@ class ServiceViewController: UIViewController, StrongTableHost {
}
nc.addObserver(self, selector: #selector(serviceDidUpdateDataCount(_:)), name: ConnectionService.didUpdateDataCount, object: nil)
nc.addObserver(self, selector: #selector(productManagerDidReloadReceipt), name: ProductManager.didReloadReceipt, object: nil)
nc.addObserver(self, selector: #selector(productManagerDidReviewPurchases), name: ProductManager.didReviewPurchases, object: nil)
// run this no matter what
// XXX: convenient here vs AppDelegate for updating table
@ -141,6 +142,7 @@ class ServiceViewController: UIViewController, StrongTableHost {
super.viewDidAppear(animated)
clearSelection()
hideProfileIfDeleted()
}
override func didReceiveMemoryWarning() {
@ -674,6 +676,10 @@ class ServiceViewController: UIViewController, StrongTableHost {
reloadModel()
tableView.reloadData()
}
@objc private func productManagerDidReviewPurchases() {
hideProfileIfDeleted()
}
}
// MARK: -

@ -1 +1 @@
Subproject commit 7476e0751d1a64efca0180a1e31f0e24f04fce84
Subproject commit dad47f3581ab25d966d204de93634d82b9fe808b