diff --git a/Podfile.lock b/Podfile.lock index 8c6a410d9..235a7ed58 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -136,7 +136,7 @@ CHECKOUT OPTIONS: :commit: 7054e4b13ee5bcd6d524adb6dc9a726e8c466308 :git: https://github.com/WhisperSystems/JSQMessagesViewController.git SignalServiceKit: - :commit: b89d16ea905c08f368c7320fb32bbd31e127ecca + :commit: 0201fa34ce760351149dd33674353d9e7edea32f :git: https://github.com/WhisperSystems/SignalServiceKit.git SocketRocket: :commit: 877ac7438be3ad0b45ef5ca3969574e4b97112bf diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 4670bcd0a..faece5f8a 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -142,6 +142,7 @@ 45855F371D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */; }; 45855F381D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */; }; 4585C4601ED4FD0400896AEA /* OWS104CreateRecipientIdentities.m in Sources */ = {isa = PBXBuildFile; fileRef = 4585C45F1ED4FD0400896AEA /* OWS104CreateRecipientIdentities.m */; }; + 4585C4661ED5DF7A00896AEA /* ProfileFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4585C4651ED5DF7A00896AEA /* ProfileFetcherJob.swift */; }; 458967111DC117CC00E9DD21 /* AccountManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458967101DC117CC00E9DD21 /* AccountManagerTest.swift */; }; 458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458DE9D51DEE3FD00071BB03 /* PeerConnectionClient.swift */; }; 458DE9D91DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.m in Sources */ = {isa = PBXBuildFile; fileRef = 458DE9D81DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.m */; }; @@ -557,6 +558,7 @@ 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactAvatarBuilder.m; sourceTree = ""; }; 4585C45E1ED4FD0400896AEA /* OWS104CreateRecipientIdentities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWS104CreateRecipientIdentities.h; path = Migrations/OWS104CreateRecipientIdentities.h; sourceTree = ""; }; 4585C45F1ED4FD0400896AEA /* OWS104CreateRecipientIdentities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWS104CreateRecipientIdentities.m; path = Migrations/OWS104CreateRecipientIdentities.m; sourceTree = ""; }; + 4585C4651ED5DF7A00896AEA /* ProfileFetcherJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileFetcherJob.swift; sourceTree = ""; }; 4589670F1DC117CC00E9DD21 /* SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SignalTests-Bridging-Header.h"; sourceTree = ""; }; 458967101DC117CC00E9DD21 /* AccountManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AccountManagerTest.swift; path = Models/AccountManagerTest.swift; sourceTree = ""; }; 458DE9D51DEE3FD00071BB03 /* PeerConnectionClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerConnectionClient.swift; sourceTree = ""; }; @@ -1147,6 +1149,7 @@ 45D231761DC7E8F10034FA89 /* SessionResetJob.swift */, 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */, 459B1C661ED480BB00506A04 /* MarkIdentityAsSeenJob.swift */, + 4585C4651ED5DF7A00896AEA /* ProfileFetcherJob.swift */, ); name = Jobs; sourceTree = ""; @@ -2040,6 +2043,7 @@ buildActionMask = 2147483647; files = ( 34B3F8761E8DF1700035BE1A /* CodeVerificationViewController.m in Sources */, + 4585C4661ED5DF7A00896AEA /* ProfileFetcherJob.swift in Sources */, 76EB063E18170B33006006FC /* Operation.m in Sources */, 34B3F8741E8DF1700035BE1A /* AttachmentSharing.m in Sources */, 34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */, diff --git a/Signal/src/Jobs/MarkIdentityAsSeenJob.swift b/Signal/src/Jobs/MarkIdentityAsSeenJob.swift index 5704fc37a..3efd7c4d4 100644 --- a/Signal/src/Jobs/MarkIdentityAsSeenJob.swift +++ b/Signal/src/Jobs/MarkIdentityAsSeenJob.swift @@ -19,21 +19,8 @@ class MarkIdentityAsSeenJob: NSObject { } public func run() { - switch self.thread { - case let contactThread as TSContactThread: - markAsSeenIfNecessary(recipientId: contactThread.contactIdentifier()) - case let groupThread as TSGroupThread: - groupThread.groupModel.groupMemberIds?.forEach { memberId in - guard let recipientId = memberId as? String else { - Logger.error("\(TAG) unexecpted type in group members.") - assertionFailure("\(TAG) unexecpted type in group members.") - return - } - - markAsSeenIfNecessary(recipientId: recipientId) - } - default: - assertionFailure("Unexpected thread type: \(self.thread)") + for recipientId in self.thread.recipientIdentifiers { + markAsSeenIfNecessary(recipientId: recipientId) } } diff --git a/Signal/src/ProfileFetcherJob.swift b/Signal/src/ProfileFetcherJob.swift new file mode 100644 index 000000000..f8a0c137d --- /dev/null +++ b/Signal/src/ProfileFetcherJob.swift @@ -0,0 +1,113 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +class ProfileFetcherJob: NSObject { + + let TAG = "[ProfileFetcherJob]" + + let networkManager: TSNetworkManager + let storageManager: TSStorageManager + + let thread: TSThread + + public class func run(thread: TSThread, networkManager: TSNetworkManager) { + ProfileFetcherJob(thread: thread, networkManager: networkManager).run() + } + + init(thread: TSThread, networkManager: TSNetworkManager) { + self.networkManager = networkManager + self.storageManager = TSStorageManager.shared() + + self.thread = thread + } + + public func run() { + for recipientId in self.thread.recipientIdentifiers { + let request = OWSGetProfileRequest(recipientId: recipientId) + + self.networkManager.makeRequest( + request, + success: { (_: URLSessionDataTask?, responseObject: Any?) -> Void in + guard let profileResponse = SignalServiceProfile(recipientId: recipientId, rawResponse: responseObject) else { + Logger.error("\(self.TAG) response object had unexpected content") + assertionFailure("\(self.TAG) response object had unexpected content") + return + } + + self.processResponse(signalServiceProfile: profileResponse) + }, + failure: { (_: URLSessionDataTask?, error: Error?) in + guard let error = error else { + Logger.error("\(self.TAG) error in \(#function) was surpringly nil. sheesh rough day.") + assertionFailure("\(self.TAG) error in \(#function) was surpringly nil. sheesh rough day.") + return + } + + Logger.error("\(self.TAG) failed to fetch profile for recipient: \(recipientId) with error: \(error)") + }) + } + } + + private func processResponse(signalServiceProfile: SignalServiceProfile) { + Logger.debug("\(TAG) in \(#function) for \(signalServiceProfile)") + + verifyIdentityUpToDateAsync(recipientId: signalServiceProfile.recipientId, latestIdentityKey: signalServiceProfile.identityKey) + + // Eventually we'll want to do more things with new SignalServiceProfile fields here. + } + + private func verifyIdentityUpToDateAsync(recipientId: String, latestIdentityKey: Data) { + OWSDispatch.sessionStoreQueue().async { + if self.storageManager.identityKey(forRecipientId: recipientId) == nil { + // first time use, do nothing, since there's no change. + return + } + + if self.storageManager.saveRemoteIdentity(latestIdentityKey, recipientId: recipientId) { + Logger.info("\(self.TAG) updated identity key in fetched profile for recipient: \(recipientId)") + self.storageManager.deleteAllSessions(forContact: recipientId) + } else { + // no change in identity. + } + } + } +} + +struct SignalServiceProfile { + let TAG = "[ProfileResponse]" + + public let recipientId: String + public let identityKey: Data + + init?(recipientId: String, rawResponse: Any?) { + self.recipientId = recipientId + + guard let responseDict = rawResponse as? [String: Any?] else { + Logger.error("\(TAG) unexpected type: \(String(describing: rawResponse))") + return nil + } + + guard let identityKeyString = responseDict["identityKey"] as? String else { + Logger.error("\(TAG) missing identity key: \(String(describing: rawResponse))") + return nil + } + + guard let identityKeyWithType = Data(base64Encoded: identityKeyString) else { + Logger.error("\(TAG) unable to parse identity key: \(identityKeyString)") + return nil + } + + let kIdentityKeyLength = 33 + guard identityKeyWithType.count == kIdentityKeyLength else { + Logger.error("\(TAG) malformed key \(identityKeyString) with decoded length: \(identityKeyWithType.count)") + return nil + } + + // `removeKeyType` is an objc category method only on NSData, so temporarily cast. + self.identityKey = (identityKeyWithType as NSData).removeKeyType() as Data + } +} diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index f33721a18..aaae1d437 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -31,6 +31,7 @@ #import "UIUtil.h" #import "UIView+OWS.h" #import "ViewControllerUtils.h" +#import #import #import #import @@ -54,6 +55,7 @@ #import #import #import +#import #import #import #import diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index 2409328b7..65b5ceb9d 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -1212,6 +1212,7 @@ typedef enum : NSUInteger { } [self updateNavigationBarSubtitleLabel]; [MarkIdentityAsSeenJob runWithThread:self.thread]; + [ProfileFetcherJob runWithThread:self.thread networkManager:self.networkManager]; } - (void)viewWillDisappear:(BOOL)animated {