migration to fix any half-registered users

// FREEBIE
This commit is contained in:
Michael Kirk 2017-07-24 10:14:58 -04:00
parent 7c28805442
commit a5f067936c
8 changed files with 188 additions and 42 deletions

View File

@ -125,7 +125,7 @@ EXTERNAL SOURCES:
:branch: signal-master
:git: https://github.com/WhisperSystems/JSQMessagesViewController.git
SignalServiceKit:
:path: .
:path: "."
SocketRocket:
:git: https://github.com/facebook/SocketRocket.git

View File

@ -134,6 +134,7 @@
4542F0961EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542F0951EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift */; };
4542F0971EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542F0951EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift */; };
45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */; };
4563ADF11F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4563ADF01F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift */; };
45666EC61D99483D008FE134 /* OWSAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666EC51D99483D008FE134 /* OWSAvatarBuilder.m */; };
45666EC91D994C0D008FE134 /* OWSGroupAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666EC81D994C0D008FE134 /* OWSGroupAvatarBuilder.m */; };
45666F561D9B2827008FE134 /* OWSScrubbingLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666F551D9B2827008FE134 /* OWSScrubbingLogFormatter.m */; };
@ -566,6 +567,7 @@
4542F0951EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Promise+retainUntilComplete.swift"; sourceTree = "<group>"; };
45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataChannelMessage.swift; sourceTree = "<group>"; };
454B35071D08EED80026D658 /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = translations/mk.lproj/Localizable.strings; sourceTree = "<group>"; };
4563ADF01F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS106EnsureProfileComplete.swift; sourceTree = "<group>"; };
45666EC41D99483D008FE134 /* OWSAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAvatarBuilder.h; sourceTree = "<group>"; };
45666EC51D99483D008FE134 /* OWSAvatarBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAvatarBuilder.m; sourceTree = "<group>"; };
45666EC71D994C0D008FE134 /* OWSGroupAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSGroupAvatarBuilder.h; sourceTree = "<group>"; };
@ -1130,6 +1132,7 @@
45666F7A1D9C0533008FE134 /* OWSDatabaseMigration.m */,
45666F7C1D9C0814008FE134 /* OWSDatabaseMigrationRunner.h */,
45666F7D1D9C0814008FE134 /* OWSDatabaseMigrationRunner.m */,
4563ADF01F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift */,
);
name = Migrations;
sourceTree = "<group>";
@ -2144,6 +2147,7 @@
34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */,
76EB058A18170B33006006FC /* Release.m in Sources */,
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */,
4563ADF11F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift in Sources */,
450873C71D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.m in Sources */,
76EB057A18170B33006006FC /* OWSContactsManager.m in Sources */,
76EB064218170B33006006FC /* StringUtil.m in Sources */,

View File

@ -3,6 +3,7 @@
//
import Foundation
import PromiseKit
@objc
class ProfileFetcherJob: NSObject {
@ -12,34 +13,54 @@ class ProfileFetcherJob: NSObject {
let networkManager: TSNetworkManager
let storageManager: TSStorageManager
let thread: TSThread
// This property is only accessed on the main queue.
static var fetchDateMap = [String: Date]()
public class func run(thread: TSThread, networkManager: TSNetworkManager) {
ProfileFetcherJob(thread: thread, networkManager: networkManager).run()
ProfileFetcherJob(networkManager: networkManager).run(thread: thread)
}
init(thread: TSThread, networkManager: TSNetworkManager) {
init(networkManager: TSNetworkManager) {
self.networkManager = networkManager
self.storageManager = TSStorageManager.shared()
self.thread = thread
}
public func run() {
public func run(thread: TSThread) {
AssertIsOnMainThread()
DispatchQueue.main.async {
for recipientId in self.thread.recipientIdentifiers {
self.getProfile(recipientId: recipientId)
for recipientId in thread.recipientIdentifiers {
self.updateProfile(recipientId: recipientId)
}
}
}
public func getProfile(recipientId: String, remainingRetries: Int = 3) {
enum ProfileFetcherJobError: Error {
case throttled(lastTimeInterval: TimeInterval),
unknownNetworkError
}
public func updateProfile(recipientId: String, remainingRetries: Int = 3) {
self.getProfile(recipientId: recipientId).then { profile in
self.updateProfile(signalServiceProfile: profile)
}.catch { error in
switch error {
case ProfileFetcherJobError.throttled(let lastTimeInterval):
Logger.info("\(self.TAG) skipping updateProfile: \(recipientId), lastTimeInterval: \(lastTimeInterval)")
case let error as SignalServiceProfile.ValidationError:
Logger.warn("\(self.TAG) skipping updateProfile retry. Invalid profile for: \(recipientId) error: \(error)")
default:
if remainingRetries > 0 {
self.updateProfile(recipientId: recipientId, remainingRetries: remainingRetries - 1)
} else {
owsFail("\(self.TAG) in \(#function) failed to get profile with error: \(error)")
}
}
}.retainUntilComplete()
}
public func getProfile(recipientId: String) -> Promise<SignalServiceProfile> {
AssertIsOnMainThread()
if let lastDate = ProfileFetcherJob.fetchDateMap[recipientId] {
let lastTimeInterval = fabs(lastDate.timeIntervalSinceNow)
// Don't check a profile more often than every N minutes.
@ -48,8 +69,7 @@ class ProfileFetcherJob: NSObject {
// facilitate debugging.
let kGetProfileMaxFrequencySeconds = _isDebugAssertConfiguration() ? 0 : 60.0 * 5.0
guard lastTimeInterval > kGetProfileMaxFrequencySeconds else {
Logger.info("\(self.TAG) skipping getProfile: \(recipientId), lastTimeInterval: \(lastTimeInterval)")
return
return Promise(error: ProfileFetcherJobError.throttled(lastTimeInterval: lastTimeInterval))
}
}
ProfileFetcherJob.fetchDateMap[recipientId] = Date()
@ -58,33 +78,31 @@ class ProfileFetcherJob: NSObject {
let request = OWSGetProfileRequest(recipientId: recipientId)
let (promise, fulfill, reject) = Promise<SignalServiceProfile>.pending()
self.networkManager.makeRequest(
request,
success: { (_: URLSessionDataTask?, responseObject: Any?) -> Void in
guard let profileResponse = SignalServiceProfile(recipientId: recipientId, rawResponse: responseObject) else {
owsFail("\(self.TAG) response object had unexpected content")
return
do {
let profile = try SignalServiceProfile(recipientId: recipientId, rawResponse: responseObject)
fulfill(profile)
} catch {
reject(error)
}
self.processResponse(signalServiceProfile: profileResponse)
},
failure: { (_: URLSessionDataTask?, error: Error?) in
guard let error = error else {
owsFail("\(self.TAG) error in \(#function) was surpringly nil. sheesh rough day.")
return
if let error = error {
reject(error)
}
Logger.error("\(self.TAG) failed to fetch profile for recipient: \(recipientId) with error: \(error)")
if remainingRetries > 1 {
DispatchQueue.global().async {
self.getProfile(recipientId: recipientId, remainingRetries:remainingRetries - 1)
}
}
reject(ProfileFetcherJobError.unknownNetworkError)
})
return promise
}
private func processResponse(signalServiceProfile: SignalServiceProfile) {
private func updateProfile(signalServiceProfile: SignalServiceProfile) {
verifyIdentityUpToDateAsync(recipientId: signalServiceProfile.recipientId, latestIdentityKey: signalServiceProfile.identityKey)
// Eventually we'll want to do more things with new SignalServiceProfile fields here.
@ -105,31 +123,32 @@ class ProfileFetcherJob: NSObject {
struct SignalServiceProfile {
let TAG = "[SignalServiceProfile]"
enum ValidationError: Error {
case invalid(description: String)
case invalidIdentityKey(description: String)
}
public let recipientId: String
public let identityKey: Data
init?(recipientId: String, rawResponse: Any?) {
init(recipientId: String, rawResponse: Any?) throws {
self.recipientId = recipientId
guard let responseDict = rawResponse as? [String: Any?] else {
Logger.error("\(TAG) unexpected type: \(String(describing: rawResponse))")
return nil
throw ValidationError.invalid(description: "\(TAG) unexpected type: \(String(describing: rawResponse))")
}
guard let identityKeyString = responseDict["identityKey"] as? String else {
Logger.error("\(TAG) missing identity key: \(String(describing: rawResponse))")
return nil
throw ValidationError.invalidIdentityKey(description: "\(TAG) missing identity key: \(String(describing: rawResponse))")
}
guard let identityKeyWithType = Data(base64Encoded: identityKeyString) else {
Logger.error("\(TAG) unable to parse identity key: \(identityKeyString)")
return nil
throw ValidationError.invalidIdentityKey(description: "\(TAG) unable to parse identity key: \(identityKeyString)")
}
let kIdentityKeyLength = 33
guard identityKeyWithType.count == kIdentityKeyLength else {
Logger.error("\(TAG) malformed key \(identityKeyString) with decoded length: \(identityKeyWithType.count)")
return nil
throw ValidationError.invalidIdentityKey(description: "\(TAG) malformed key \(identityKeyString) with decoded length: \(identityKeyWithType.count)")
}
// `removeKeyType` is an objc category method only on NSData, so temporarily cast.

View File

@ -4,7 +4,6 @@
#import <Foundation/Foundation.h>
#import "TSMessageAdapter.h"
#import "AttachmentSharing.h"
#import "Environment.h"
#import "FLAnimatedImage.h"
@ -15,6 +14,7 @@
#import "OWSCallNotificationsAdaptee.h"
#import "OWSContactAvatarBuilder.h"
#import "OWSContactsManager.h"
#import "OWSDatabaseMigration.h"
#import "OWSLogger.h"
#import "OWSMessageEditing.h"
#import "OWSProgressView.h"
@ -24,6 +24,7 @@
#import "PushManager.h"
#import "SettingsTableViewController.h"
#import "SignalsViewController.h"
#import "TSMessageAdapter.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIUtil.h"
@ -77,6 +78,7 @@
#import <SignalServiceKit/TSInfoMessage.h>
#import <SignalServiceKit/TSMessagesManager.h>
#import <SignalServiceKit/TSNetworkManager.h>
#import <SignalServiceKit/TSPreKeyManager.h>
#import <SignalServiceKit/TSSocketManager.h>
#import <SignalServiceKit/TSStorageManager+Calling.h>
#import <SignalServiceKit/TSStorageManager+SessionStore.h>

View File

@ -8,6 +8,7 @@
#import "OWS103EnableVideoCalling.h"
#import "OWS104CreateRecipientIdentities.h"
#import "OWS105AttachmentFilePaths.h"
#import "Signal-Swift.h"
NS_ASSUME_NONNULL_BEGIN
@ -32,7 +33,8 @@ NS_ASSUME_NONNULL_BEGIN
[[OWS102MoveLoggingPreferenceToUserDefaults alloc] initWithStorageManager:self.storageManager],
[[OWS103EnableVideoCalling alloc] initWithStorageManager:self.storageManager],
// OWS104CreateRecipientIdentities is run separately. See runSafeBlockingMigrations.
[[OWS105AttachmentFilePaths alloc] initWithStorageManager:self.storageManager]
[[OWS105AttachmentFilePaths alloc] initWithStorageManager:self.storageManager],
[[OWS106EnsureProfileComplete alloc] initWithStorageManager:self.storageManager]
];
}

View File

@ -0,0 +1,110 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
class OWS106EnsureProfileComplete: OWSDatabaseMigration {
let TAG = "[OWS106EnsureProfileComplete]"
// 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 func runUp() {
CompleteRegistrationFixerJob(completionHandler: {
Logger.info("\(self.TAG) Completed. Saving.")
self.save()
}).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.
static let kRetryInterval: TimeInterval = 5 * 60
var timer: Timer?
let completionHandler: () -> Void
init (completionHandler: @escaping () -> Void) {
self.completionHandler = completionHandler
}
func start() {
assert(self.timer == nil)
let timer = WeakTimer.scheduledTimer(timeInterval: CompleteRegistrationFixerJob.kRetryInterval, target: self, userInfo: nil, repeats: true) { [weak self] timer in
guard let strongSelf = self else {
return
}
var isCompleted = false
strongSelf.ensureProfileComplete().then { _ -> Void in
guard isCompleted == false else {
Logger.info("Already saved. Skipping redundant call.")
return
}
Logger.info("\(strongSelf.TAG) complete. Canceling timer and saving.")
isCompleted = true
timer.invalidate()
strongSelf.completionHandler()
}.catch { error in
Logger.error("\(strongSelf.TAG) failed with \(error). We'll try again in \(CompleteRegistrationFixerJob.kRetryInterval) seconds.")
}.retainUntilComplete()
}
self.timer = timer
timer.fire()
}
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.getCurrent().networkManager else {
owsFail("\(TAG) network manager was unexpectedly not set")
return Promise(error: OWSErrorMakeAssertionError())
}
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
}
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright © 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
/**
@ -33,7 +33,7 @@ final class WeakTimer {
action: action).timer!
}
@objc fileprivate func fire(timer: Timer) {
@objc public func fire(timer: Timer) {
if target != nil {
action(timer)
} else {

View File

@ -78,6 +78,15 @@
#pragma mark Class Methods
+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey
{
if ([propertyKey isEqualToString:@"TAG"]) {
return MTLPropertyStorageNone;
} else {
return [super storageBehaviorForPropertyWithKey:propertyKey];
}
}
+ (YapDatabaseConnection *)dbReadConnection
{
// We use TSYapDatabaseObject's dbReadWriteConnection (not TSStorageManager's