[AltStore] Uses GrandSlam Authentication

Retrieves anisette data from AltServer so we can authenticate with GSA.
This commit is contained in:
Riley Testut
2019-11-18 14:49:17 -08:00
parent 9535595df1
commit 691e08202d
14 changed files with 636 additions and 182 deletions

View File

@@ -12,7 +12,8 @@ import AltSign
class AuthenticationViewController: UIViewController
{
var authenticationHandler: (((ALTAccount, String)?) -> Void)?
var authenticationHandler: ((String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void)?
var completionHandler: (((ALTAccount, ALTAppleAPISession, String)?) -> Void)?
private weak var toastView: ToastView?
@@ -96,14 +97,16 @@ private extension AuthenticationViewController
self.signInButton.isIndicatingActivity = true
ALTAppleAPI.shared.authenticate(appleID: emailAddress, password: password) { (account, error) in
do
{
let account = try Result(account, error).get()
self.authenticationHandler?((account, password))
}
catch
self.authenticationHandler?(emailAddress, password) { (result) in
switch result
{
case .failure(ALTAppleAPIError.requiresTwoFactorAuthentication):
// Ignore
DispatchQueue.main.async {
self.signInButton.isIndicatingActivity = false
}
case .failure(let error):
DispatchQueue.main.async {
let toastView = ToastView(text: NSLocalizedString("Failed to Log In", comment: ""), detailText: error.localizedDescription)
toastView.textLabel.textColor = .altPink
@@ -113,6 +116,9 @@ private extension AuthenticationViewController
self.signInButton.isIndicatingActivity = false
}
case .success(let account, let session):
self.completionHandler?((account, session, password))
}
DispatchQueue.main.async {
@@ -123,7 +129,7 @@ private extension AuthenticationViewController
@IBAction func cancel(_ sender: UIBarButtonItem)
{
self.authenticationHandler?(nil)
self.completionHandler?(nil)
}
}

View File

@@ -14,6 +14,7 @@ import Roxas
class RefreshAltStoreViewController: UIViewController
{
var signer: ALTSigner!
var session: ALTAppleAPISession!
var completionHandler: ((Result<Void, Error>) -> Void)?
@@ -49,6 +50,7 @@ private extension RefreshAltStoreViewController
let group = OperationGroup()
group.signer = self.signer // Prevent us from trying to authenticate a second time.
group.session = self.session // ^
group.completionHandler = { (result) in
if let error = result.error ?? result.value?.values.compactMap({ $0.error }).first
{

View File

@@ -80,12 +80,26 @@ extension AppManager
#endif
}
func authenticate(presentingViewController: UIViewController?, completionHandler: @escaping (Result<ALTSigner, Error>) -> Void)
func authenticate(presentingViewController: UIViewController?, completionHandler: @escaping (Result<(ALTSigner, ALTAppleAPISession), Error>) -> Void)
{
let authenticationOperation = AuthenticationOperation(presentingViewController: presentingViewController)
let group = OperationGroup()
let findServerOperation = FindServerOperation(group: group)
findServerOperation.resultHandler = { (result) in
switch result
{
case .failure(let error): group.error = error
case .success(let server): group.server = server
}
}
let authenticationOperation = AuthenticationOperation(group: group, presentingViewController: presentingViewController)
authenticationOperation.addDependency(findServerOperation)
authenticationOperation.resultHandler = { (result) in
completionHandler(result)
}
self.operationQueue.addOperation(findServerOperation)
self.operationQueue.addOperation(authenticationOperation)
}
}
@@ -194,21 +208,30 @@ private extension AppManager
}
operations.append(findServerOperation)
if group.signer == nil
let authenticationOperation: AuthenticationOperation?
if group.signer == nil || group.session == nil
{
/* Authenticate */
let authenticationOperation = AuthenticationOperation(presentingViewController: presentingViewController)
authenticationOperation.resultHandler = { (result) in
let operation = AuthenticationOperation(group: group, presentingViewController: presentingViewController)
operation.resultHandler = { (result) in
switch result
{
case .failure(let error): group.error = error
case .success(let signer): group.signer = signer
case .success(let signer, let session):
group.signer = signer
group.session = session
}
}
operations.append(authenticationOperation)
operations.append(operation)
operation.addDependency(findServerOperation)
findServerOperation.addDependency(authenticationOperation)
}
authenticationOperation = operation
}
else
{
authenticationOperation = nil
}
for app in apps
{
@@ -222,7 +245,7 @@ private extension AppManager
guard let resignedApp = self.process(result, context: context) else { return }
context.resignedApp = resignedApp
}
resignAppOperation.addDependency(findServerOperation)
resignAppOperation.addDependency(authenticationOperation ?? findServerOperation)
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
operations.append(resignAppOperation)

View File

@@ -8,7 +8,9 @@
import Foundation
import Roxas
import Network
import AltKit
import AltSign
enum AuthenticationError: LocalizedError
@@ -30,8 +32,10 @@ enum AuthenticationError: LocalizedError
}
@objc(AuthenticationOperation)
class AuthenticationOperation: ResultOperation<ALTSigner>
class AuthenticationOperation: ResultOperation<(ALTSigner, ALTAppleAPISession)>
{
let group: OperationGroup
private weak var presentingViewController: UIViewController?
private lazy var navigationController: UINavigationController = {
@@ -49,9 +53,15 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
private var shouldShowInstructions = false
private var signer: ALTSigner?
private var session: ALTAppleAPISession?
init(presentingViewController: UIViewController?)
private let dispatchQueue = DispatchQueue(label: "com.altstore.AuthenticationOperation")
private var submitCodeAction: UIAlertAction?
init(group: OperationGroup, presentingViewController: UIViewController?)
{
self.group = group
self.presentingViewController = presentingViewController
super.init()
@@ -63,18 +73,25 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
{
super.main()
if let error = self.group.error
{
self.finish(.failure(error))
return
}
// Sign In
self.signIn { (result) in
self.signIn() { (result) in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result
{
case .failure(let error): self.finish(.failure(error))
case .success(let account):
case .success(let account, let session):
self.session = session
self.progress.completedUnitCount += 1
// Fetch Team
self.fetchTeam(for: account) { (result) in
self.fetchTeam(for: account, session: session) { (result) in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result
@@ -84,7 +101,7 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
self.progress.completedUnitCount += 1
// Fetch Certificate
self.fetchCertificate(for: team) { (result) in
self.fetchCertificate(for: team, session: session) { (result) in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result
@@ -97,8 +114,8 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
self.signer = signer
self.showInstructionsIfNecessary() { (didShowInstructions) in
self.finish(.success(signer))
}
self.finish(.success((signer, session)))
}
}
}
}
@@ -107,7 +124,7 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
}
}
override func finish(_ result: Result<ALTSigner, Error>)
override func finish(_ result: Result<(ALTSigner, ALTAppleAPISession), Error>)
{
guard !self.isFinished else { return }
@@ -117,7 +134,7 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
context.performAndWait {
do
{
let signer = try result.get()
let (signer, session) = try result.get()
let altAccount = signer.team.account
// Account
@@ -158,7 +175,7 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
// Refresh screen must go last since a successful refresh will cause the app to quit.
self.showRefreshScreenIfNecessary() { (didShowRefreshAlert) in
super.finish(.success(signer))
super.finish(.success((signer, session)))
DispatchQueue.main.async {
self.navigationController.dismiss(animated: true, completion: nil)
@@ -204,21 +221,53 @@ private extension AuthenticationOperation
private extension AuthenticationOperation
{
func signIn(completionHandler: @escaping (Result<ALTAccount, Swift.Error>) -> Void)
func connect(to server: Server, completionHandler: @escaping (Result<NWConnection, Error>) -> Void)
{
let connection = NWConnection(to: .service(name: server.service.name, type: server.service.type, domain: server.service.domain, interface: nil), using: .tcp)
connection.stateUpdateHandler = { [unowned connection] (state) in
switch state
{
case .failed(let error):
print("Failed to connect to service \(server.service.name).", error)
completionHandler(.failure(ConnectionError.connectionFailed))
case .cancelled:
completionHandler(.failure(OperationError.cancelled))
case .ready:
completionHandler(.success(connection))
case .waiting: break
case .setup: break
case .preparing: break
@unknown default: break
}
}
connection.start(queue: self.dispatchQueue)
}
func signIn(completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Swift.Error>) -> Void)
{
func authenticate()
{
DispatchQueue.main.async {
let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController
authenticationViewController.authenticationHandler = { (result) in
if let (account, password) = result
authenticationViewController.authenticationHandler = { (appleID, password, completionHandler) in
self.authenticate(appleID: appleID, password: password) { (result) in
completionHandler(result)
}
}
authenticationViewController.completionHandler = { (result) in
if let (account, session, password) = result
{
// We presented the Auth UI and the user signed in.
// In this case, we'll assume we should show the instructions again.
self.shouldShowInstructions = true
self.appleIDPassword = password
completionHandler(.success(account))
completionHandler(.success((account, session)))
}
else
{
@@ -235,24 +284,17 @@ private extension AuthenticationOperation
if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword
{
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password) { (account, error) in
do
self.authenticate(appleID: appleID, password: password) { (result) in
switch result
{
case .success(let account, let session):
self.appleIDPassword = password
completionHandler(.success((account, session)))
let account = try Result(account, error).get()
completionHandler(.success(account))
}
catch ALTAppleAPIError.incorrectCredentials
{
case .failure(ALTAppleAPIError.incorrectCredentials), .failure(ALTAppleAPIError.appSpecificPasswordRequired):
authenticate()
}
catch ALTAppleAPIError.appSpecificPasswordRequired
{
authenticate()
}
catch
{
case .failure(let error):
completionHandler(.failure(error))
}
}
@@ -263,7 +305,103 @@ private extension AuthenticationOperation
}
}
func fetchTeam(for account: ALTAccount, completionHandler: @escaping (Result<ALTTeam, Swift.Error>) -> Void)
func authenticate(appleID: String, password: String, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Swift.Error>) -> Void)
{
guard let server = self.group.server else { return completionHandler(.failure(OperationError.invalidParameters)) }
self.connect(to: server) { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success(let connection):
let request = AnisetteDataRequest()
server.send(request, via: connection) { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success:
server.receiveResponse(from: connection) { (result) in
switch result
{
case .failure(let error):
completionHandler(.failure(error))
case .success(.error(let response)):
completionHandler(.failure(response.error))
case .success(.anisetteData(let response)):
let verificationHandler: ((@escaping (String?) -> Void) -> Void)?
if let presentingViewController = self.presentingViewController
{
verificationHandler = { (completionHandler) in
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""),
message: nil, preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.keyboardType = .numberPad
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField)
}
let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { (action) in
let textField = alertController.textFields?.first
let code = textField?.text ?? ""
completionHandler(code)
}
submitAction.isEnabled = false
alertController.addAction(submitAction)
self.submitCodeAction = submitAction
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel) { (action) in
completionHandler(nil)
})
if self.navigationController.presentingViewController != nil
{
self.navigationController.present(alertController, animated: true, completion: nil)
}
else
{
presentingViewController.present(alertController, animated: true, completion: nil)
}
}
}
}
else
{
// No view controller to present security code alert, so don't provide verificationHandler.
verificationHandler = nil
}
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: response.anisetteData,
verificationHandler: verificationHandler) { (account, session, error) in
if let account = account, let session = session
{
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(error ?? OperationError.unknown))
}
}
case .success:
completionHandler(.failure(ALTServerError(.unknownRequest)))
}
}
}
}
}
}
}
func fetchTeam(for account: ALTAccount, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTTeam, Swift.Error>) -> Void)
{
func selectTeam(from teams: [ALTTeam])
{
@@ -285,7 +423,7 @@ private extension AuthenticationOperation
}
}
ALTAppleAPI.shared.fetchTeams(for: account) { (teams, error) in
ALTAppleAPI.shared.fetchTeams(for: account, session: session) { (teams, error) in
switch Result(teams, error)
{
case .failure(let error): completionHandler(.failure(error))
@@ -304,18 +442,18 @@ private extension AuthenticationOperation
}
}
func fetchCertificate(for team: ALTTeam, completionHandler: @escaping (Result<ALTCertificate, Swift.Error>) -> Void)
func fetchCertificate(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTCertificate, Swift.Error>) -> Void)
{
func requestCertificate()
{
let machineName = "AltStore - " + UIDevice.current.name
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team) { (certificate, error) in
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session) { (certificate, error) in
do
{
let certificate = try Result(certificate, error).get()
guard let privateKey = certificate.privateKey else { throw AuthenticationError.missingPrivateKey }
ALTAppleAPI.shared.fetchCertificates(for: team) { (certificates, error) in
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
do
{
let certificates = try Result(certificates, error).get()
@@ -344,7 +482,7 @@ private extension AuthenticationOperation
{
guard let certificate = certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
ALTAppleAPI.shared.revoke(certificate, for: team) { (success, error) in
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
if let error = error, !success
{
completionHandler(.failure(error))
@@ -356,7 +494,7 @@ private extension AuthenticationOperation
}
}
ALTAppleAPI.shared.fetchCertificates(for: team) { (certificates, error) in
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
do
{
let certificates = try Result(certificates, error).get()
@@ -431,7 +569,7 @@ private extension AuthenticationOperation
func showRefreshScreenIfNecessary(completionHandler: @escaping (Bool) -> Void)
{
guard let signer = self.signer else { return completionHandler(false) }
guard let signer = self.signer, let session = self.session else { return completionHandler(false) }
guard let application = ALTApplication(fileURL: Bundle.main.bundleURL), let provisioningProfile = application.provisioningProfile else { return completionHandler(false) }
// If we're not using the same certificate used to install AltStore, warn user that they need to refresh.
@@ -440,6 +578,7 @@ private extension AuthenticationOperation
DispatchQueue.main.async {
let refreshViewController = self.storyboard.instantiateViewController(withIdentifier: "refreshAltStoreViewController") as! RefreshAltStoreViewController
refreshViewController.signer = signer
refreshViewController.session = session
refreshViewController.completionHandler = { _ in
completionHandler(true)
}
@@ -451,3 +590,13 @@ private extension AuthenticationOperation
}
}
}
extension AuthenticationOperation
{
@objc func textFieldTextDidChange(_ notification: Notification)
{
guard let textField = notification.object as? UITextField else { return }
self.submitCodeAction?.isEnabled = (textField.text ?? "").count == 6
}
}

View File

@@ -94,25 +94,31 @@ class InstallAppOperation: ResultOperation<InstalledApp>
func receive(from connection: NWConnection, server: Server, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
server.receive(ServerResponse.self, from: connection) { (result) in
server.receiveResponse(from: connection) { (result) in
do
{
let response = try result.get()
print(response)
if let error = response.error
switch response
{
completionHandler(.failure(error))
}
else if response.progress == 1.0
{
self.progress.completedUnitCount = self.progress.totalUnitCount
completionHandler(.success(()))
}
else
{
self.progress.completedUnitCount = Int64(response.progress * 100)
self.receive(from: connection, server: server, completionHandler: completionHandler)
case .installationProgress(let response):
if response.progress == 1.0
{
self.progress.completedUnitCount = self.progress.totalUnitCount
completionHandler(.success(()))
}
else
{
self.progress.completedUnitCount = Int64(response.progress * 100)
self.receive(from: connection, server: server, completionHandler: completionHandler)
}
case .error(let response):
completionHandler(.failure(response.error))
default:
completionHandler(.failure(ALTServerError(.unknownRequest)))
}
}
catch

View File

@@ -18,6 +18,8 @@ class OperationGroup
var completionHandler: ((Result<[String: Result<InstalledApp, Error>], Error>) -> Void)?
var beginInstallationHandler: ((InstalledApp) -> Void)?
var session: ALTAppleAPISession?
var server: Server?
var signer: ALTSigner?

View File

@@ -37,15 +37,16 @@ class ResignAppOperation: ResultOperation<ALTApplication>
guard
let app = self.context.app,
let signer = self.context.group.signer
let signer = self.context.group.signer,
let session = self.context.group.session
else { return self.finish(.failure(OperationError.invalidParameters)) }
// Register Device
self.registerCurrentDevice(for: signer.team) { (result) in
self.registerCurrentDevice(for: signer.team, session: session) { (result) in
guard let _ = self.process(result) else { return }
// Prepare Provisioning Profiles
self.prepareProvisioningProfiles(app.fileURL, team: signer.team) { (result) in
self.prepareProvisioningProfiles(app.fileURL, team: signer.team, session: session) { (result) in
guard let profiles = self.process(result) else { return }
// Prepare app bundle
@@ -104,13 +105,13 @@ class ResignAppOperation: ResultOperation<ALTApplication>
private extension ResignAppOperation
{
func registerCurrentDevice(for team: ALTTeam, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
{
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else {
return completionHandler(.failure(OperationError.unknownUDID))
}
ALTAppleAPI.shared.fetchDevices(for: team) { (devices, error) in
ALTAppleAPI.shared.fetchDevices(for: team, session: session) { (devices, error) in
do
{
let devices = try Result(devices, error).get()
@@ -121,7 +122,7 @@ private extension ResignAppOperation
}
else
{
ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, team: team) { (device, error) in
ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, team: team, session: session) { (device, error) in
completionHandler(Result(device, error))
}
}
@@ -133,7 +134,7 @@ private extension ResignAppOperation
}
}
func prepareProvisioningProfiles(_ fileURL: URL, team: ALTTeam, completionHandler: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void)
func prepareProvisioningProfiles(_ fileURL: URL, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void)
{
guard let bundle = Bundle(url: fileURL), let app = ALTApplication(fileURL: fileURL) else { return completionHandler(.failure(OperationError.invalidApp)) }
@@ -144,7 +145,7 @@ private extension ResignAppOperation
dispatchGroup.enter()
self.prepareProvisioningProfile(for: app, team: team) { (result) in
self.prepareProvisioningProfile(for: app, team: team, session: session) { (result) in
switch result
{
case .failure(let e): error = e
@@ -162,7 +163,7 @@ private extension ResignAppOperation
dispatchGroup.enter()
self.prepareProvisioningProfile(for: appExtension, team: team) { (result) in
self.prepareProvisioningProfile(for: appExtension, team: team, session: session) { (result) in
switch result
{
case .failure(let e): error = e
@@ -186,31 +187,31 @@ private extension ResignAppOperation
}
}
func prepareProvisioningProfile(for app: ALTApplication, team: ALTTeam, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
func prepareProvisioningProfile(for app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
{
// Register
self.register(app, team: team) { (result) in
self.register(app, team: team, session: session) { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success(let appID):
// Update features
self.updateFeatures(for: appID, app: app, team: team) { (result) in
self.updateFeatures(for: appID, app: app, team: team, session: session) { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success(let appID):
// Update app groups
self.updateAppGroups(for: appID, app: app, team: team) { (result) in
self.updateAppGroups(for: appID, app: app, team: team, session: session) { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success(let appID):
// Fetch Provisioning Profile
self.fetchProvisioningProfile(for: appID, team: team) { (result) in
self.fetchProvisioningProfile(for: appID, team: team, session: session) { (result) in
completionHandler(result)
}
}
@@ -221,12 +222,12 @@ private extension ResignAppOperation
}
}
func register(_ app: ALTApplication, team: ALTTeam, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
func register(_ app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
let appName = app.name
let bundleID = "com.\(team.identifier).\(app.bundleIdentifier)"
ALTAppleAPI.shared.fetchAppIDs(for: team) { (appIDs, error) in
ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) { (appIDs, error) in
do
{
let appIDs = try Result(appIDs, error).get()
@@ -237,7 +238,7 @@ private extension ResignAppOperation
}
else
{
ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleID, team: team) { (appID, error) in
ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleID, team: team, session: session) { (appID, error) in
completionHandler(Result(appID, error))
}
}
@@ -249,7 +250,7 @@ private extension ResignAppOperation
}
}
func updateFeatures(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
func updateFeatures(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
let requiredFeatures = app.entitlements.compactMap { (entitlement, value) -> (ALTFeature, Any)? in
guard let feature = ALTFeature(entitlement: entitlement) else { return nil }
@@ -266,12 +267,12 @@ private extension ResignAppOperation
let appID = appID.copy() as! ALTAppID
appID.features = features
ALTAppleAPI.shared.update(appID, team: team) { (appID, error) in
ALTAppleAPI.shared.update(appID, team: team, session: session) { (appID, error) in
completionHandler(Result(appID, error))
}
}
func updateAppGroups(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
func updateAppGroups(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
// TODO: Handle apps belonging to more than one app group.
guard let applicationGroups = app.entitlements[.appGroups] as? [String], let groupIdentifier = applicationGroups.first else {
@@ -287,7 +288,7 @@ private extension ResignAppOperation
// Assign App Group
// TODO: Determine whether app already belongs to app group.
ALTAppleAPI.shared.add(appID, to: group, team: team) { (success, error) in
ALTAppleAPI.shared.add(appID, to: group, team: team, session: session) { (success, error) in
let result = result.map { _ in appID }
completionHandler(result)
}
@@ -296,7 +297,7 @@ private extension ResignAppOperation
let adjustedGroupIdentifier = "group.\(team.identifier)." + groupIdentifier
ALTAppleAPI.shared.fetchAppGroups(for: team) { (groups, error) in
ALTAppleAPI.shared.fetchAppGroups(for: team, session: session) { (groups, error) in
switch Result(groups, error)
{
case .failure(let error): completionHandler(.failure(error))
@@ -311,7 +312,7 @@ private extension ResignAppOperation
// Not all characters are allowed in group names, so we replace periods with spaces (like Apple does).
let name = "AltStore " + groupIdentifier.replacingOccurrences(of: ".", with: " ")
ALTAppleAPI.shared.addAppGroup(withName: name, groupIdentifier: adjustedGroupIdentifier, team: team) { (group, error) in
ALTAppleAPI.shared.addAppGroup(withName: name, groupIdentifier: adjustedGroupIdentifier, team: team, session: session) { (group, error) in
finish(Result(group, error))
}
}
@@ -319,23 +320,23 @@ private extension ResignAppOperation
}
}
func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
{
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team) { (profile, error) in
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team, session: session) { (profile, error) in
switch Result(profile, error)
{
case .failure(let error): completionHandler(.failure(error))
case .success(let profile):
// Delete existing profile
ALTAppleAPI.shared.delete(profile, for: team) { (success, error) in
ALTAppleAPI.shared.delete(profile, for: team, session: session) { (success, error) in
switch Result(success, error)
{
case .failure(let error): completionHandler(.failure(error))
case .success:
// Fetch new provisiong profile
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team) { (profile, error) in
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team, session: session) { (profile, error) in
completionHandler(Result(profile, error))
}
}

View File

@@ -116,7 +116,7 @@ struct Server: Equatable
}
}
func receive<T: Decodable>(_ type: T.Type, from connection: NWConnection, completionHandler: @escaping (Result<T, Error>) -> Void)
func receiveResponse(from connection: NWConnection, completionHandler: @escaping (Result<ServerResponse, Error>) -> Void)
{
let size = MemoryLayout<Int32>.size
@@ -131,7 +131,7 @@ struct Server: Equatable
{
let data = try self.process(data: data, error: error, from: connection)
let response = try JSONDecoder().decode(T.self, from: data)
let response = try JSONDecoder().decode(ServerResponse.self, from: data)
completionHandler(.success(response))
}
catch

View File

@@ -184,6 +184,19 @@ private extension SettingsViewController
{
AppManager.shared.authenticate(presentingViewController: self) { (result) in
DispatchQueue.main.async {
switch result
{
case .failure(OperationError.cancelled):
// Ignore
break
case .failure(let error):
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
case .success: break
}
self.update()
}
}