// // 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. 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 { 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.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 } } }