2019-06-05 18:05:21 -07:00
//
// A u t h e n t i c a t i o n O p e r a t i o n . s w i f t
// A l t S t o r e
//
// C r e a t e d b y R i l e y T e s t u t o n 6 / 5 / 1 9 .
// C o p y r i g h t © 2 0 1 9 R i l e y T e s t u t . A l l r i g h t s r e s e r v e d .
//
import Foundation
import Roxas
2019-11-18 14:49:17 -08:00
import Network
2019-06-05 18:05:21 -07:00
2019-11-18 14:49:17 -08:00
import AltKit
2019-06-05 18:05:21 -07:00
import AltSign
2019-06-10 15:03:47 -07:00
enum AuthenticationError : LocalizedError
2019-06-05 18:05:21 -07:00
{
2019-06-10 15:03:47 -07:00
case noTeam
case noCertificate
case missingPrivateKey
case missingCertificate
var errorDescription : String ? {
switch self {
case . noTeam : return NSLocalizedString ( " Developer team could not be found. " , comment : " " )
case . noCertificate : return NSLocalizedString ( " Developer certificate could not be found. " , comment : " " )
case . missingPrivateKey : return NSLocalizedString ( " The certificate's private key could not be found. " , comment : " " )
case . missingCertificate : return NSLocalizedString ( " The certificate could not be found. " , comment : " " )
2019-06-05 18:05:21 -07:00
}
}
}
2019-06-10 15:03:47 -07:00
@objc ( AuthenticationOperation )
2019-11-18 14:49:17 -08:00
class AuthenticationOperation : ResultOperation < ( ALTSigner , ALTAppleAPISession ) >
2019-06-05 18:05:21 -07:00
{
2019-11-18 14:49:17 -08:00
let group : OperationGroup
2019-06-05 18:05:21 -07:00
private weak var presentingViewController : UIViewController ?
2019-09-07 15:29:19 -07:00
private lazy var navigationController : UINavigationController = {
let navigationController = self . storyboard . instantiateViewController ( withIdentifier : " navigationController " ) as ! UINavigationController
2019-10-28 13:16:55 -07:00
if #available ( iOS 13.0 , * )
{
navigationController . isModalInPresentation = true
}
2019-09-07 15:29:19 -07:00
return navigationController
} ( )
2019-06-05 18:05:21 -07:00
private lazy var storyboard = UIStoryboard ( name : " Authentication " , bundle : nil )
private var appleIDPassword : String ?
2019-09-07 15:29:19 -07:00
private var shouldShowInstructions = false
2019-06-05 18:05:21 -07:00
2019-10-03 13:09:38 -07:00
private var signer : ALTSigner ?
2019-11-18 14:49:17 -08:00
private var session : ALTAppleAPISession ?
private let dispatchQueue = DispatchQueue ( label : " com.altstore.AuthenticationOperation " )
2019-10-03 13:09:38 -07:00
2019-11-18 14:49:17 -08:00
private var submitCodeAction : UIAlertAction ?
init ( group : OperationGroup , presentingViewController : UIViewController ? )
2019-06-05 18:05:21 -07:00
{
2019-11-18 14:49:17 -08:00
self . group = group
2019-06-05 18:05:21 -07:00
self . presentingViewController = presentingViewController
super . init ( )
2019-09-07 15:29:19 -07:00
2019-06-10 15:03:47 -07:00
self . progress . totalUnitCount = 3
2019-06-05 18:05:21 -07:00
}
override func main ( )
{
super . main ( )
2019-11-18 14:49:17 -08:00
if let error = self . group . error
{
self . finish ( . failure ( error ) )
return
}
2019-06-05 18:05:21 -07:00
// S i g n I n
2019-11-18 14:49:17 -08:00
self . signIn ( ) { ( result ) in
2019-06-10 15:03:47 -07:00
guard ! self . isCancelled else { return self . finish ( . failure ( OperationError . cancelled ) ) }
2019-06-05 18:05:21 -07:00
switch result
{
2019-06-10 15:03:47 -07:00
case . failure ( let error ) : self . finish ( . failure ( error ) )
2019-11-18 14:49:17 -08:00
case . success ( let account , let session ) :
self . session = session
2019-06-10 15:03:47 -07:00
self . progress . completedUnitCount += 1
2019-06-05 18:05:21 -07:00
// F e t c h T e a m
2019-11-18 14:49:17 -08:00
self . fetchTeam ( for : account , session : session ) { ( result ) in
2019-06-10 15:03:47 -07:00
guard ! self . isCancelled else { return self . finish ( . failure ( OperationError . cancelled ) ) }
2019-06-05 18:05:21 -07:00
switch result
{
2019-06-10 15:03:47 -07:00
case . failure ( let error ) : self . finish ( . failure ( error ) )
2019-06-05 18:05:21 -07:00
case . success ( let team ) :
2019-06-10 15:03:47 -07:00
self . progress . completedUnitCount += 1
2019-06-05 18:05:21 -07:00
// F e t c h C e r t i f i c a t e
2019-11-18 14:49:17 -08:00
self . fetchCertificate ( for : team , session : session ) { ( result ) in
2019-06-10 15:03:47 -07:00
guard ! self . isCancelled else { return self . finish ( . failure ( OperationError . cancelled ) ) }
2019-06-05 18:05:21 -07:00
switch result
{
2019-06-10 15:03:47 -07:00
case . failure ( let error ) : self . finish ( . failure ( error ) )
case . success ( let certificate ) :
self . progress . completedUnitCount += 1
2019-10-03 13:09:38 -07:00
let signer = ALTSigner ( team : team , certificate : certificate )
self . signer = signer
2019-09-07 15:29:19 -07:00
self . showInstructionsIfNecessary ( ) { ( didShowInstructions ) in
2019-11-18 14:49:17 -08:00
self . finish ( . success ( ( signer , session ) ) )
}
2019-06-05 18:05:21 -07:00
}
}
}
}
}
}
}
2019-06-10 15:03:47 -07:00
2019-11-18 14:49:17 -08:00
override func finish ( _ result : Result < ( ALTSigner , ALTAppleAPISession ) , Error > )
2019-06-10 15:03:47 -07:00
{
guard ! self . isFinished else { return }
print ( " Finished authenticating with result: " , result )
let context = DatabaseManager . shared . persistentContainer . newBackgroundContext ( )
context . performAndWait {
do
{
2019-11-18 14:49:17 -08:00
let ( signer , session ) = try result . get ( )
2019-06-10 15:03:47 -07:00
let altAccount = signer . team . account
// A c c o u n t
let account = Account ( altAccount , context : context )
account . isActiveAccount = true
let otherAccountsFetchRequest = Account . fetchRequest ( ) as NSFetchRequest < Account >
otherAccountsFetchRequest . predicate = NSPredicate ( format : " %K != %@ " , # keyPath ( Account . identifier ) , account . identifier )
let otherAccounts = try context . fetch ( otherAccountsFetchRequest )
for account in otherAccounts
{
account . isActiveAccount = false
}
// T e a m
let team = Team ( signer . team , account : account , context : context )
team . isActiveTeam = true
let otherTeamsFetchRequest = Team . fetchRequest ( ) as NSFetchRequest < Team >
otherTeamsFetchRequest . predicate = NSPredicate ( format : " %K != %@ " , # keyPath ( Team . identifier ) , team . identifier )
let otherTeams = try context . fetch ( otherTeamsFetchRequest )
for team in otherTeams
{
team . isActiveTeam = false
}
// S a v e
try context . save ( )
// U p d a t e k e y c h a i n
Keychain . shared . appleIDEmailAddress = altAccount . appleID // " a c c o u n t " m a y h a v e n i l a p p l e I D s i n c e w e j u s t s a v e d .
Keychain . shared . appleIDPassword = self . appleIDPassword
2019-10-28 13:16:55 -07:00
Keychain . shared . signingCertificate = signer . certificate . p12Data ( )
Keychain . shared . signingCertificatePassword = signer . certificate . machineIdentifier
2019-06-10 15:03:47 -07:00
2019-10-28 13:16:55 -07:00
// R e f r e s h s c r e e n m u s t g o l a s t s i n c e a s u c c e s s f u l r e f r e s h w i l l c a u s e t h e a p p t o q u i t .
self . showRefreshScreenIfNecessary ( ) { ( didShowRefreshAlert ) in
2019-11-18 14:49:17 -08:00
super . finish ( . success ( ( signer , session ) ) )
2019-10-28 13:16:55 -07:00
DispatchQueue . main . async {
self . navigationController . dismiss ( animated : true , completion : nil )
}
}
2019-06-10 15:03:47 -07:00
}
catch
{
super . finish ( . failure ( error ) )
2019-10-28 13:16:55 -07:00
DispatchQueue . main . async {
self . navigationController . dismiss ( animated : true , completion : nil )
}
2019-06-10 15:03:47 -07:00
}
}
}
2019-06-05 18:05:21 -07:00
}
private extension AuthenticationOperation
{
func present ( _ viewController : UIViewController ) -> Bool
{
guard let presentingViewController = self . presentingViewController else { return false }
2019-09-07 15:29:19 -07:00
self . navigationController . view . tintColor = . white
2019-06-05 18:05:21 -07:00
if self . navigationController . viewControllers . isEmpty
{
2019-06-06 12:56:13 -07:00
guard presentingViewController . presentedViewController = = nil else { return false }
self . navigationController . setViewControllers ( [ viewController ] , animated : false )
2019-06-05 18:05:21 -07:00
presentingViewController . present ( self . navigationController , animated : true , completion : nil )
}
else
{
viewController . navigationItem . leftBarButtonItem = nil
self . navigationController . pushViewController ( viewController , animated : true )
}
return true
}
}
private extension AuthenticationOperation
{
2019-11-18 14:49:17 -08:00
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 )
2019-06-05 18:05:21 -07:00
{
func authenticate ( )
{
DispatchQueue . main . async {
let authenticationViewController = self . storyboard . instantiateViewController ( withIdentifier : " authenticationViewController " ) as ! AuthenticationViewController
2019-11-18 14:49:17 -08:00
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
2019-06-05 18:05:21 -07:00
{
2019-09-07 15:29:19 -07:00
// W e p r e s e n t e d t h e A u t h U I a n d t h e u s e r s i g n e d i n .
// I n t h i s c a s e , w e ' l l a s s u m e w e s h o u l d s h o w t h e i n s t r u c t i o n s a g a i n .
self . shouldShowInstructions = true
2019-06-05 18:05:21 -07:00
2019-09-07 15:29:19 -07:00
self . appleIDPassword = password
2019-11-18 14:49:17 -08:00
completionHandler ( . success ( ( account , session ) ) )
2019-06-05 18:05:21 -07:00
}
else
{
2019-06-10 15:03:47 -07:00
completionHandler ( . failure ( OperationError . cancelled ) )
2019-06-05 18:05:21 -07:00
}
}
if ! self . present ( authenticationViewController )
{
2019-06-10 15:03:47 -07:00
completionHandler ( . failure ( OperationError . notAuthenticated ) )
2019-06-05 18:05:21 -07:00
}
}
}
if let appleID = Keychain . shared . appleIDEmailAddress , let password = Keychain . shared . appleIDPassword
{
2019-11-18 14:49:17 -08:00
self . authenticate ( appleID : appleID , password : password ) { ( result ) in
switch result
2019-06-05 18:05:21 -07:00
{
2019-11-18 14:49:17 -08:00
case . success ( let account , let session ) :
2019-06-05 18:05:21 -07:00
self . appleIDPassword = password
2019-11-18 14:49:17 -08:00
completionHandler ( . success ( ( account , session ) ) )
2019-06-05 18:05:21 -07:00
2019-11-18 14:49:17 -08:00
case . failure ( ALTAppleAPIError . incorrectCredentials ) , . failure ( ALTAppleAPIError . appSpecificPasswordRequired ) :
2019-06-05 18:05:21 -07:00
authenticate ( )
2019-11-18 14:49:17 -08:00
case . failure ( let error ) :
2019-06-05 18:05:21 -07:00
completionHandler ( . failure ( error ) )
}
}
}
else
{
authenticate ( )
}
}
2019-11-18 14:49:17 -08:00
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
{
// N o v i e w c o n t r o l l e r t o p r e s e n t s e c u r i t y c o d e a l e r t , s o d o n ' t p r o v i d e v e r i f i c a t i o n H a n d l e r .
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 )
2019-06-05 18:05:21 -07:00
{
func selectTeam ( from teams : [ ALTTeam ] )
{
2019-09-07 15:29:19 -07:00
if let team = teams . first ( where : { $0 . type = = . free } )
2019-07-31 14:33:23 -07:00
{
return completionHandler ( . success ( team ) )
}
2019-09-07 15:29:19 -07:00
else if let team = teams . first ( where : { $0 . type = = . individual } )
{
return completionHandler ( . success ( team ) )
}
else if let team = teams . first
{
return completionHandler ( . success ( team ) )
}
else
{
return completionHandler ( . failure ( AuthenticationError . noTeam ) )
2019-06-05 18:05:21 -07:00
}
}
2019-09-07 15:29:19 -07:00
2019-11-18 14:49:17 -08:00
ALTAppleAPI . shared . fetchTeams ( for : account , session : session ) { ( teams , error ) in
2019-06-05 18:05:21 -07:00
switch Result ( teams , error )
{
case . failure ( let error ) : completionHandler ( . failure ( error ) )
case . success ( let teams ) :
DatabaseManager . shared . persistentContainer . performBackgroundTask { ( context ) in
2019-06-06 14:46:23 -07:00
if let activeTeam = DatabaseManager . shared . activeTeam ( in : context ) , let altTeam = teams . first ( where : { $0 . identifier = = activeTeam . identifier } )
2019-06-05 18:05:21 -07:00
{
2019-06-06 14:46:23 -07:00
completionHandler ( . success ( altTeam ) )
2019-06-05 18:05:21 -07:00
}
2019-06-06 14:46:23 -07:00
else
2019-06-05 18:05:21 -07:00
{
selectTeam ( from : teams )
}
}
}
}
}
2019-11-18 14:49:17 -08:00
func fetchCertificate ( for team : ALTTeam , session : ALTAppleAPISession , completionHandler : @ escaping ( Result < ALTCertificate , Swift . Error > ) -> Void )
2019-06-05 18:05:21 -07:00
{
func requestCertificate ( )
{
let machineName = " AltStore - " + UIDevice . current . name
2019-11-18 14:49:17 -08:00
ALTAppleAPI . shared . addCertificate ( machineName : machineName , to : team , session : session ) { ( certificate , error ) in
2019-06-05 18:05:21 -07:00
do
{
let certificate = try Result ( certificate , error ) . get ( )
2019-06-10 15:03:47 -07:00
guard let privateKey = certificate . privateKey else { throw AuthenticationError . missingPrivateKey }
2019-06-05 18:05:21 -07:00
2019-11-18 14:49:17 -08:00
ALTAppleAPI . shared . fetchCertificates ( for : team , session : session ) { ( certificates , error ) in
2019-06-05 18:05:21 -07:00
do
{
let certificates = try Result ( certificates , error ) . get ( )
2019-06-18 17:40:30 -07:00
guard let certificate = certificates . first ( where : { $0 . serialNumber = = certificate . serialNumber } ) else {
2019-06-10 15:03:47 -07:00
throw AuthenticationError . missingCertificate
2019-06-05 18:05:21 -07:00
}
certificate . privateKey = privateKey
completionHandler ( . success ( certificate ) )
}
catch
{
completionHandler ( . failure ( error ) )
}
}
}
catch
{
completionHandler ( . failure ( error ) )
}
}
}
func replaceCertificate ( from certificates : [ ALTCertificate ] )
{
2019-09-07 15:29:19 -07:00
guard let certificate = certificates . first else { return completionHandler ( . failure ( AuthenticationError . noCertificate ) ) }
2019-07-31 14:33:23 -07:00
2019-11-18 14:49:17 -08:00
ALTAppleAPI . shared . revoke ( certificate , for : team , session : session ) { ( success , error ) in
2019-09-07 15:29:19 -07:00
if let error = error , ! success
{
completionHandler ( . failure ( error ) )
2019-06-05 18:05:21 -07:00
}
2019-09-07 15:29:19 -07:00
else
2019-06-05 18:05:21 -07:00
{
2019-09-07 15:29:19 -07:00
requestCertificate ( )
2019-06-05 18:05:21 -07:00
}
}
}
2019-11-18 14:49:17 -08:00
ALTAppleAPI . shared . fetchCertificates ( for : team , session : session ) { ( certificates , error ) in
2019-06-05 18:05:21 -07:00
do
{
let certificates = try Result ( certificates , error ) . get ( )
if
2019-10-28 13:16:55 -07:00
let data = Keychain . shared . signingCertificate ,
let localCertificate = ALTCertificate ( p12Data : data , password : nil ) ,
let certificate = certificates . first ( where : { $0 . serialNumber = = localCertificate . serialNumber } )
{
// W e h a v e a c e r t i f i c a t e s t o r e d i n t h e k e y c h a i n a n d i t h a s n ' t b e e n r e v o k e d .
localCertificate . machineIdentifier = certificate . machineIdentifier
completionHandler ( . success ( localCertificate ) )
}
else if
2019-06-18 17:40:30 -07:00
let serialNumber = Keychain . shared . signingCertificateSerialNumber ,
2019-06-05 18:05:21 -07:00
let privateKey = Keychain . shared . signingCertificatePrivateKey ,
2019-06-18 17:40:30 -07:00
let certificate = certificates . first ( where : { $0 . serialNumber = = serialNumber } )
2019-06-05 18:05:21 -07:00
{
2019-10-28 13:16:55 -07:00
// L E G A C Y
// W e h a v e t h e p r i v a t e k e y f o r o n e o f t h e c e r t i f i c a t e s , s o a d d i t t o c e r t i f i c a t e a n d u s e i t .
2019-06-05 18:05:21 -07:00
certificate . privateKey = privateKey
completionHandler ( . success ( certificate ) )
}
2019-10-28 13:16:55 -07:00
else if
let serialNumber = Bundle . main . object ( forInfoDictionaryKey : Bundle . Info . certificateID ) as ? String ,
let certificate = certificates . first ( where : { $0 . serialNumber = = serialNumber } ) ,
let machineIdentifier = certificate . machineIdentifier ,
FileManager . default . fileExists ( atPath : Bundle . main . certificateURL . path ) ,
let data = try ? Data ( contentsOf : Bundle . main . certificateURL ) ,
let localCertificate = ALTCertificate ( p12Data : data , password : machineIdentifier )
{
// W e h a v e a n e m b e d d e d c e r t i f i c a t e t h a t h a s n ' t b e e n r e v o k e d .
localCertificate . machineIdentifier = machineIdentifier
completionHandler ( . success ( localCertificate ) )
}
2019-06-05 18:05:21 -07:00
else if certificates . isEmpty
{
2019-10-28 13:16:55 -07:00
// N o c e r t i f i c a t e s , s o r e q u e s t a n e w o n e .
2019-06-05 18:05:21 -07:00
requestCertificate ( )
}
else
{
2019-10-28 13:16:55 -07:00
// W e d o n ' t h a v e p r i v a t e k e y s f o r a n y o f t h e c e r t i f i c a t e s ,
// s o w e n e e d t o r e v o k e o n e a n d c r e a t e a n e w o n e .
2019-06-05 18:05:21 -07:00
replaceCertificate ( from : certificates )
}
}
catch
{
completionHandler ( . failure ( error ) )
}
}
}
2019-09-07 15:29:19 -07:00
func showInstructionsIfNecessary ( completionHandler : @ escaping ( Bool ) -> Void )
{
guard self . shouldShowInstructions else { return completionHandler ( false ) }
DispatchQueue . main . async {
let instructionsViewController = self . storyboard . instantiateViewController ( withIdentifier : " instructionsViewController " ) as ! InstructionsViewController
instructionsViewController . showsBottomButton = true
instructionsViewController . completionHandler = {
completionHandler ( true )
}
if ! self . present ( instructionsViewController )
{
completionHandler ( false )
}
}
}
2019-10-28 13:16:55 -07:00
func showRefreshScreenIfNecessary ( completionHandler : @ escaping ( Bool ) -> Void )
2019-10-03 13:09:38 -07:00
{
2019-11-18 14:49:17 -08:00
guard let signer = self . signer , let session = self . session else { return completionHandler ( false ) }
2019-10-28 13:16:55 -07:00
guard let application = ALTApplication ( fileURL : Bundle . main . bundleURL ) , let provisioningProfile = application . provisioningProfile else { return completionHandler ( false ) }
// I f w e ' r e n o t u s i n g t h e s a m e c e r t i f i c a t e u s e d t o i n s t a l l A l t S t o r e , w a r n u s e r t h a t t h e y n e e d t o r e f r e s h .
guard ! provisioningProfile . certificates . contains ( signer . certificate ) else { return completionHandler ( false ) }
DispatchQueue . main . async {
let refreshViewController = self . storyboard . instantiateViewController ( withIdentifier : " refreshAltStoreViewController " ) as ! RefreshAltStoreViewController
refreshViewController . signer = signer
2019-11-18 14:49:17 -08:00
refreshViewController . session = session
2019-10-28 13:16:55 -07:00
refreshViewController . completionHandler = { _ in
completionHandler ( true )
}
if ! self . present ( refreshViewController )
{
completionHandler ( false )
}
2019-10-03 13:09:38 -07:00
}
}
}
2019-11-18 14:49:17 -08:00
extension AuthenticationOperation
{
@objc func textFieldTextDidChange ( _ notification : Notification )
{
guard let textField = notification . object as ? UITextField else { return }
self . submitCodeAction ? . isEnabled = ( textField . text ? ? " " ) . count = = 6
}
}