mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-20 04:03:26 +01:00
[AltStore] Uses GrandSlam Authentication
Retrieves anisette data from AltServer so we can authenticate with GSA.
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user