Supports bypassing “Undeclared Permissions” error while sources are in beta

Presents error alert that can be explicitly bypassed by user when sideloading apps with undeclared permissions, and also allows user to view all undeclared permissions.
This commit is contained in:
Riley Testut
2023-05-22 15:02:26 -05:00
committed by Magesh K
parent e8f676b10b
commit 2afaf73fc5
2 changed files with 64 additions and 10 deletions

View File

@@ -162,11 +162,51 @@ final class VerifyAppOperation: ResultOperation<Void>
Task<Void, Never> { Task<Void, Never> {
do do
{ {
guard let ipaURL = self.context.ipaURL else { throw OperationError.appNotFound(name: app.name) } do
{
try await self.verifyHash(of: app, at: ipaURL, matches: appVersion) guard let ipaURL = self.context.ipaURL else { throw OperationError.appNotFound(name: app.name) }
try await self.verifyDownloadedVersion(of: app, matches: appVersion)
try await self.verifyPermissions(of: app, match: appVersion) try await self.verifyHash(of: app, at: ipaURL, matches: appVersion)
try await self.verifyDownloadedVersion(of: app, matches: appVersion)
// Verify permissions last in case user bypasses error.
try await self.verifyPermissions(of: app, match: appVersion)
}
catch let error as VerificationError where error.code == .undeclaredPermissions
{
#if !BETA
throw error
#endif
if let trustedSources = UserDefaults.shared.trustedSources, let sourceID = await self.context.$appVersion.sourceID
{
let isTrusted = trustedSources.contains { $0.identifier == sourceID }
guard !isTrusted else {
// Don't enforce permission checking for Trusted Sources while 2.0 is in beta.
return self.finish(.success(()))
}
}
// While in beta, allow users to temporarily bypass permissions alert
// so source maintainers have time to update their sources.
guard let presentingViewController = self.context.presentingViewController else { throw error }
let message = NSLocalizedString("While AltStore 2.0 is in beta, you may choose to ignore this warning at your own risk until the source is updated.", comment: "")
let ignoreAction = await UIAlertAction(title: NSLocalizedString("Install Anyway", comment: ""), style: .destructive)
let viewPermissionsAction = await UIAlertAction(title: NSLocalizedString("View Permisions", comment: ""), style: .default)
while true
{
let action = try await presentingViewController.presentConfirmationAlert(title: error.errorFailureReason,
message: message,
actions: [ignoreAction, viewPermissionsAction])
guard action == viewPermissionsAction else { break } // break loop to continue with installation (unless we're viewing permissions).
await presentingViewController.presentAlert(title: NSLocalizedString("Undeclared Permissions", comment: ""), message: error.recoverySuggestion)
}
}
self.finish(.success(())) self.finish(.success(()))
} }

View File

@@ -63,7 +63,7 @@ public extension NSManagedObjectContext
public extension UIViewController public extension UIViewController
{ {
@MainActor @MainActor
func presentAlert(title: String, message: String, action: UIAlertAction? = nil) async func presentAlert(title: String, message: String?, action: UIAlertAction? = nil) async
{ {
let action = action ?? .ok let action = action ?? .ok
@@ -79,19 +79,33 @@ public extension UIViewController
@MainActor @MainActor
func presentConfirmationAlert(title: String, message: String, primaryAction: UIAlertAction, cancelAction: UIAlertAction? = nil) async throws func presentConfirmationAlert(title: String, message: String, primaryAction: UIAlertAction, cancelAction: UIAlertAction? = nil) async throws
{
_ = try await self.presentConfirmationAlert(title: title, message: message, actions: [primaryAction], cancelAction: cancelAction)
}
@MainActor
func presentConfirmationAlert(title: String, message: String, actions: [UIAlertAction], cancelAction: UIAlertAction? = nil) async throws -> UIAlertAction
{ {
let cancelAction = cancelAction ?? .cancel let cancelAction = cancelAction ?? .cancel
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in let action = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<UIAlertAction, Error>) in
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: cancelAction.title, style: cancelAction.style) { _ in alertController.addAction(UIAlertAction(title: cancelAction.title, style: cancelAction.style) { _ in
continuation.resume(throwing: CancellationError()) continuation.resume(throwing: CancellationError())
}) })
alertController.addAction(UIAlertAction(title: primaryAction.title, style: primaryAction.style) { _ in
continuation.resume() for action in actions
}) {
alertController.addAction(UIAlertAction(title: action.title, style: action.style) { alertAction in
// alertAction is different than the action provided,
// so return original action instead for == comparison.
continuation.resume(returning: action)
})
}
self.present(alertController, animated: true) self.present(alertController, animated: true)
} }
return action
} }
} }