|  |  |  | // | 
					
						
							|  |  |  | //  Copyright (c) 2018 Open Whisper Systems. All rights reserved. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import Foundation | 
					
						
							|  |  |  | import PromiseKit | 
					
						
							|  |  |  | import SignalServiceKit | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @objc | 
					
						
							|  |  |  | public class OWS106EnsureProfileComplete: OWSDatabaseMigration { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let TAG = "[OWS106EnsureProfileComplete]" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static var sharedCompleteRegistrationFixerJob: CompleteRegistrationFixerJob? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // increment a similar constant for each migration. | 
					
						
							|  |  |  |     @objc | 
					
						
							|  |  |  |     class func migrationId() -> String { | 
					
						
							|  |  |  |         return "106" | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Overriding runUp since we have some specific completion criteria which | 
					
						
							|  |  |  |     // is more likely to fail since it involves network requests. | 
					
						
							|  |  |  |     override public func runUp(completion:@escaping () -> Void) { | 
					
						
							|  |  |  |         let job = CompleteRegistrationFixerJob(completionHandler: { (didSucceed) in | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (didSucceed) { | 
					
						
							|  |  |  |                 Logger.info("\(self.TAG) Completed. Saving.") | 
					
						
							|  |  |  |                 self.save() | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 Logger.error("\(self.TAG) Failed.") | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             completion() | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         type(of: self).sharedCompleteRegistrationFixerJob = job | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         job.start() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /**
 | 
					
						
							|  |  |  |      * A previous client bug made it possible for re-registering users to register their new account | 
					
						
							|  |  |  |      * but never upload new pre-keys. The symptom is that there will be accounts with no uploaded | 
					
						
							|  |  |  |      * identity key. We detect that here and fix the situation | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private class CompleteRegistrationFixerJob { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let TAG = "[CompleteRegistrationFixerJob]" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Duration between retries if update fails. | 
					
						
							|  |  |  |         let kRetryInterval: TimeInterval = 5 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let completionHandler: (Bool) -> Void | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         init(completionHandler: @escaping (Bool) -> Void) { | 
					
						
							|  |  |  |             self.completionHandler = completionHandler | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         func start() { | 
					
						
							|  |  |  |             guard TSAccountManager.isRegistered() else { | 
					
						
							|  |  |  |                 self.completionHandler(true) | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             self.ensureProfileComplete().then { _ -> Void in | 
					
						
							|  |  |  |                 Logger.info("\(self.TAG) complete. Canceling timer and saving.") | 
					
						
							|  |  |  |                 self.completionHandler(true) | 
					
						
							|  |  |  |             }.catch { error in | 
					
						
							|  |  |  |                 let nserror = error as NSError | 
					
						
							|  |  |  |                 if nserror.domain == TSNetworkManagerDomain { | 
					
						
							|  |  |  |                     // Don't retry if we had an unrecoverable error. | 
					
						
							|  |  |  |                     // In particular, 401 (invalid auth) is unrecoverable. | 
					
						
							|  |  |  |                     let isUnrecoverableError = nserror.code == 401 | 
					
						
							|  |  |  |                     if isUnrecoverableError { | 
					
						
							|  |  |  |                         Logger.error("\(self.TAG) failed due to unrecoverable error: \(error). Aborting.") | 
					
						
							|  |  |  |                         self.completionHandler(true) | 
					
						
							|  |  |  |                         return | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 Logger.error("\(self.TAG) failed with \(error).") | 
					
						
							|  |  |  |                 self.completionHandler(false) | 
					
						
							|  |  |  |             }.retainUntilComplete() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         func ensureProfileComplete() -> Promise<Void> { | 
					
						
							|  |  |  |             guard let localRecipientId = TSAccountManager.localNumber() else { | 
					
						
							|  |  |  |                 // local app doesn't think we're registered, so nothing to worry about. | 
					
						
							|  |  |  |                 return Promise(value: ()) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let (promise, fulfill, reject) = Promise<Void>.pending() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             guard let networkManager = Environment.current().networkManager else { | 
					
						
							|  |  |  |                 return Promise(error: OWSErrorMakeAssertionError("\(TAG) network manager was unexpectedly not set")) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             ProfileFetcherJob(networkManager: networkManager).getProfile(recipientId: localRecipientId).then { _ -> Void in | 
					
						
							|  |  |  |                 Logger.info("\(self.TAG) verified recipient profile is in good shape: \(localRecipientId)") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 fulfill(()) | 
					
						
							|  |  |  |             }.catch { error in | 
					
						
							|  |  |  |                 switch error { | 
					
						
							|  |  |  |                 case SignalServiceProfile.ValidationError.invalidIdentityKey(let description): | 
					
						
							|  |  |  |                     Logger.warn("\(self.TAG) detected incomplete profile for \(localRecipientId) error: \(description)") | 
					
						
							|  |  |  |                     // This is the error condition we're looking for. Update prekeys to properly set the identity key, completing registration. | 
					
						
							|  |  |  |                     TSPreKeyManager.registerPreKeys(with: .signedAndOneTime, | 
					
						
							|  |  |  |                                                     success: { | 
					
						
							|  |  |  |                                                         Logger.info("\(self.TAG) successfully uploaded pre-keys. Profile should be fixed.") | 
					
						
							|  |  |  |                                                         fulfill(()) | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                                                     failure: { _ in | 
					
						
							|  |  |  |                                                         reject(OWSErrorWithCodeDescription(.signalServiceFailure, "\(self.TAG) Unknown error in \(#function)")) | 
					
						
							|  |  |  |                     }) | 
					
						
							|  |  |  |                 default: | 
					
						
							|  |  |  |                     reject(error) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }.retainUntilComplete() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return promise | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |