diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 51cd7a1f5..0995189fa 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -1123,7 +1123,7 @@ typedef enum : NSUInteger { { [super viewDidAppear:animated]; - [ProfileFetcherJob runWithThread:self.thread networkManager:self.networkManager]; + [ProfileFetcherJob runWithThread:self.thread]; [self markVisibleMessagesAsRead]; [self startReadTimer]; [self updateNavigationBarSubtitleLabel]; diff --git a/SignalMessaging/environment/migrations/OWS106EnsureProfileComplete.swift b/SignalMessaging/environment/migrations/OWS106EnsureProfileComplete.swift index 0029c1e50..607f1032a 100644 --- a/SignalMessaging/environment/migrations/OWS106EnsureProfileComplete.swift +++ b/SignalMessaging/environment/migrations/OWS106EnsureProfileComplete.swift @@ -88,9 +88,7 @@ public class OWS106EnsureProfileComplete: OWSDatabaseMigration { let (promise, fulfill, reject) = Promise.pending() - let networkManager = SSKEnvironment.shared.networkManager - - ProfileFetcherJob(networkManager: networkManager).getProfile(recipientId: localRecipientId).then { _ -> Void in + ProfileFetcherJob().getProfile(recipientId: localRecipientId).then { _ -> Void in Logger.info("verified recipient profile is in good shape: \(localRecipientId)") fulfill(()) diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 8b8d3dd89..3079528b4 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -495,7 +495,7 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; if (!localNumber) { return; } - [ProfileFetcherJob runWithRecipientId:localNumber networkManager:self.networkManager ignoreThrottling:YES]; + [ProfileFetcherJob runWithRecipientId:localNumber ignoreThrottling:YES]; } #pragma mark - Profile Whitelist @@ -716,7 +716,6 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; completion:^{ dispatch_async(dispatch_get_main_queue(), ^(void) { [ProfileFetcherJob runWithRecipientId:recipientId - networkManager:self.networkManager ignoreThrottling:YES]; }); }]; diff --git a/SignalMessaging/profiles/ProfileFetcherJob.swift b/SignalMessaging/profiles/ProfileFetcherJob.swift index 10aa49475..0c1e60099 100644 --- a/SignalMessaging/profiles/ProfileFetcherJob.swift +++ b/SignalMessaging/profiles/ProfileFetcherJob.swift @@ -9,10 +9,6 @@ import SignalServiceKit @objc public class ProfileFetcherJob: NSObject { - let networkManager: TSNetworkManager - let socketManager: TSSocketManager - let primaryStorage: OWSPrimaryStorage - // This property is only accessed on the main queue. static var fetchDateMap = [String: Date]() @@ -21,22 +17,39 @@ public class ProfileFetcherJob: NSObject { var backgroundTask: OWSBackgroundTask? @objc - public class func run(thread: TSThread, networkManager: TSNetworkManager) { - ProfileFetcherJob(networkManager: networkManager).run(recipientIds: thread.recipientIdentifiers) + public class func run(thread: TSThread) { + ProfileFetcherJob().run(recipientIds: thread.recipientIdentifiers) } @objc - public class func run(recipientId: String, networkManager: TSNetworkManager, ignoreThrottling: Bool) { - ProfileFetcherJob(networkManager: networkManager, ignoreThrottling: ignoreThrottling).run(recipientIds: [recipientId]) + public class func run(recipientId: String, ignoreThrottling: Bool) { + ProfileFetcherJob(ignoreThrottling: ignoreThrottling).run(recipientIds: [recipientId]) } - public init(networkManager: TSNetworkManager, ignoreThrottling: Bool = false) { - self.networkManager = networkManager - self.socketManager = TSSocketManager.shared() - self.primaryStorage = OWSPrimaryStorage.shared() + public init(ignoreThrottling: Bool = false) { self.ignoreThrottling = ignoreThrottling } + // MARK: - Dependencies + + private var networkManager: TSNetworkManager { + return SSKEnvironment.shared.networkManager + } + + private var socketManager: TSSocketManager { + return TSSocketManager.shared() + } + + private var primaryStorage: OWSPrimaryStorage { + return SSKEnvironment.shared.primaryStorage + } + + private var udManager: OWSUDManager { + return SSKEnvironment.shared.udManager + } + + // MARK: - + public func run(recipientIds: [String]) { AssertIsOnMainThread() @@ -117,7 +130,7 @@ public class ProfileFetcherJob: NSObject { self.socketManager.make(request, success: { (responseObject: Any?) -> Void in do { - let profile = try SignalServiceProfile(recipientId: recipientId, rawResponse: responseObject) + let profile = try SignalServiceProfile(recipientId: recipientId, responseObject: responseObject) fulfill(profile) } catch { reject(error) @@ -130,7 +143,7 @@ public class ProfileFetcherJob: NSObject { self.networkManager.makeRequest(request, success: { (_: URLSessionDataTask?, responseObject: Any?) -> Void in do { - let profile = try SignalServiceProfile(recipientId: recipientId, rawResponse: responseObject) + let profile = try SignalServiceProfile(recipientId: recipientId, responseObject: responseObject) fulfill(profile) } catch { reject(error) @@ -155,6 +168,13 @@ public class ProfileFetcherJob: NSObject { OWSProfileManager.shared().updateProfile(forRecipientId: signalServiceProfile.recipientId, profileNameEncrypted: signalServiceProfile.profileNameEncrypted, avatarUrlPath: signalServiceProfile.avatarUrlPath) + + udManager.setShouldAllowUnrestrictedAccess(recipientId: signalServiceProfile.recipientId, shouldAllowUnrestrictedAccess: signalServiceProfile.hasUnrestrictedUnidentifiedAccess) + if signalServiceProfile.unidentifiedAccessKey != nil { + udManager.addUDRecipientId(signalServiceProfile.recipientId) + } else { + udManager.removeUDRecipientId(signalServiceProfile.recipientId) + } } private func verifyIdentityUpToDateAsync(recipientId: String, latestIdentityKey: Data) { @@ -182,37 +202,33 @@ public class SignalServiceProfile: NSObject { public let identityKey: Data public let profileNameEncrypted: Data? public let avatarUrlPath: String? + public let unidentifiedAccessKey: Data? + public let hasUnrestrictedUnidentifiedAccess: Bool - init(recipientId: String, rawResponse: Any?) throws { + init(recipientId: String, responseObject: Any?) throws { self.recipientId = recipientId - guard let responseDict = rawResponse as? [String: Any?] else { - throw ValidationError.invalid(description: "unexpected type: \(String(describing: rawResponse))") + guard let params = ParamParser(responseObject: responseObject) else { + throw ValidationError.invalid(description: "invalid response: \(String(describing: responseObject))") } - guard let identityKeyString = responseDict["identityKey"] as? String else { - throw ValidationError.invalidIdentityKey(description: "missing identity key: \(String(describing: rawResponse))") - } - guard let identityKeyWithType = Data(base64Encoded: identityKeyString) else { - throw ValidationError.invalidIdentityKey(description: "unable to parse identity key: \(identityKeyString)") - } + let identityKeyWithType = try params.requiredBase64EncodedData(key: "identityKey") let kIdentityKeyLength = 33 guard identityKeyWithType.count == kIdentityKeyLength else { - throw ValidationError.invalidIdentityKey(description: "malformed key \(identityKeyString) with decoded length: \(identityKeyWithType.count)") + throw ValidationError.invalidIdentityKey(description: "malformed identity key \(identityKeyWithType.hexadecimalString) with decoded length: \(identityKeyWithType.count)") } + // `removeKeyType` is an objc category method only on NSData, so temporarily cast. + self.identityKey = (identityKeyWithType as NSData).removeKeyType() as Data - if let profileNameString = responseDict["name"] as? String { - guard let data = Data(base64Encoded: profileNameString) else { - throw ValidationError.invalidProfileName(description: "unable to parse profile name: \(profileNameString)") - } - self.profileNameEncrypted = data - } else { - self.profileNameEncrypted = nil - } + self.profileNameEncrypted = try params.optionalBase64EncodedData(key: "name") - self.avatarUrlPath = responseDict["avatar"] as? String + let avatarUrlPath: String? = try params.optional(key: "avatar") + self.avatarUrlPath = avatarUrlPath - // `removeKeyType` is an objc category method only on NSData, so temporarily cast. - self.identityKey = (identityKeyWithType as NSData).removeKeyType() as Data + // TODO: Should this key be "unidentifiedAccessKey" or "unidentifiedAccess"? + // The docs don't agree with the response from staging. + self.unidentifiedAccessKey = try params.optionalBase64EncodedData(key: "unidentifiedAccess") + + self.hasUnrestrictedUnidentifiedAccess = try params.optionalBool(key: "unrestrictedUnidentifiedAccess", defaultValue: false) } } diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 33b747306..b0f10e70d 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -524,6 +524,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; [recipientIds addObjectsFromArray:message.sendingRecipientIds]; // Only send to members in the latest known group member list. [recipientIds intersectSet:[NSSet setWithArray:gThread.groupModel.groupMemberIds]]; + + if ([recipientIds containsObject:TSAccountManager.localNumber]) { + OWSFailDebug(@"Message send recipients should not include self."); + } } else if ([message isKindOfClass:[OWSOutgoingSyncMessage class]]) { [recipientIds addObject:[TSAccountManager localNumber]]; } else if ([thread isKindOfClass:[TSContactThread class]]) { @@ -545,6 +549,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; } [recipientIds addObject:recipientContactId]; + + if ([recipientIds containsObject:TSAccountManager.localNumber]) { + OWSFailDebug(@"Message send recipients should not include self."); + } } else { // Neither a group nor contact thread? This should never happen. OWSFailDebug(@"Unknown message type: %@", [message class]); @@ -552,15 +560,11 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); [error setIsRetryable:NO]; failureHandler(error); + return; } [recipientIds minusSet:[NSSet setWithArray:self.blockingManager.blockedPhoneNumbers]]; - if ([recipientIds containsObject:TSAccountManager.localNumber]) { - OWSFailDebug(@"Message send recipients should not include self."); - } - [recipientIds removeObject:TSAccountManager.localNumber]; - // Mark skipped recipients as such. We skip because: // // * Recipient is no longer in the group. diff --git a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift index 79c7be45d..12c5361e3 100644 --- a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift +++ b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift @@ -39,9 +39,13 @@ public enum OWSUDError: Error { // MARK: - Unrestricted Access - @objc func shouldAllowUnrestrictedAccess() -> Bool + @objc func shouldAllowUnrestrictedAccessLocal() -> Bool - @objc func setShouldAllowUnrestrictedAccess(_ value: Bool) + @objc func setShouldAllowUnrestrictedAccessLocal(_ value: Bool) + + @objc func shouldAllowUnrestrictedAccess(recipientId: String) -> Bool + + @objc func setShouldAllowUnrestrictedAccess(recipientId: String, shouldAllowUnrestrictedAccess: Bool) } // MARK: - @@ -51,10 +55,11 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { private let dbConnection: YapDatabaseConnection - private let kUDRecipientModeCollection = "kUDRecipientModeCollection" private let kUDCollection = "kUDCollection" private let kUDCurrentSenderCertificateKey = "kUDCurrentSenderCertificateKey" private let kUDUnrestrictedAccessKey = "kUDUnrestrictedAccessKey" + private let kUDRecipientModeCollection = "kUDRecipientModeCollection" + private let kUDUnrestrictedAccessCollection = "kUDUnrestrictedAccessCollection" @objc public required init(primaryStorage: OWSPrimaryStorage) { @@ -215,12 +220,22 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { // MARK: - Unrestricted Access @objc - public func shouldAllowUnrestrictedAccess() -> Bool { - return dbConnection.bool(forKey: kUDUnrestrictedAccessKey, inCollection: kUDRecipientModeCollection, defaultValue: false) + public func shouldAllowUnrestrictedAccessLocal() -> Bool { + return dbConnection.bool(forKey: kUDUnrestrictedAccessKey, inCollection: kUDCollection, defaultValue: false) + } + + @objc + public func setShouldAllowUnrestrictedAccessLocal(_ value: Bool) { + dbConnection.setBool(value, forKey: kUDUnrestrictedAccessKey, inCollection: kUDCollection) + } + + @objc + public func shouldAllowUnrestrictedAccess(recipientId: String) -> Bool { + return dbConnection.bool(forKey: recipientId, inCollection: kUDUnrestrictedAccessCollection, defaultValue: false) } @objc - public func setShouldAllowUnrestrictedAccess(_ value: Bool) { - dbConnection.setBool(value, forKey: kUDUnrestrictedAccessKey, inCollection: kUDRecipientModeCollection) + public func setShouldAllowUnrestrictedAccess(recipientId: String, shouldAllowUnrestrictedAccess: Bool) { + dbConnection.setBool(shouldAllowUnrestrictedAccess, forKey: recipientId, inCollection: kUDUnrestrictedAccessCollection) } } diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m index 361dd00da..c2b6d9d43 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m @@ -281,7 +281,7 @@ NS_ASSUME_NONNULL_BEGIN // Crash app if UD cannot be enabled. OWSFail(@"Could not determine UD access key: %@.", error); } - BOOL allowUnrestrictedUD = [self.udManager shouldAllowUnrestrictedAccess] && udAccessKey != nil; + BOOL allowUnrestrictedUD = [self.udManager shouldAllowUnrestrictedAccessLocal] && udAccessKey != nil; NSMutableDictionary *accountAttributes = [@{ @"signalingKey" : signalingKey, diff --git a/SignalServiceKit/src/Tests/OWSFakeUDManager.swift b/SignalServiceKit/src/Tests/OWSFakeUDManager.swift index a5238d0cf..c9875c397 100644 --- a/SignalServiceKit/src/Tests/OWSFakeUDManager.swift +++ b/SignalServiceKit/src/Tests/OWSFakeUDManager.swift @@ -69,16 +69,31 @@ public class OWSFakeUDManager: NSObject, OWSUDManager { // MARK: - Unrestricted Access - private var _shouldAllowUnrestrictedAccess = false + private var _shouldAllowUnrestrictedAccessLocal = false + private var _shouldAllowUnrestrictedAccessSet = Set() @objc - public func shouldAllowUnrestrictedAccess() -> Bool { - return _shouldAllowUnrestrictedAccess + public func shouldAllowUnrestrictedAccessLocal() -> Bool { + return _shouldAllowUnrestrictedAccessLocal } @objc - public func setShouldAllowUnrestrictedAccess(_ value: Bool) { - _shouldAllowUnrestrictedAccess = value + public func setShouldAllowUnrestrictedAccessLocal(_ value: Bool) { + _shouldAllowUnrestrictedAccessLocal = value + } + + @objc + public func shouldAllowUnrestrictedAccess(recipientId: String) -> Bool { + return _shouldAllowUnrestrictedAccessSet.contains(recipientId) + } + + @objc + public func setShouldAllowUnrestrictedAccess(recipientId: String, shouldAllowUnrestrictedAccess: Bool) { + if shouldAllowUnrestrictedAccess { + _shouldAllowUnrestrictedAccessSet.insert(recipientId) + } else { + _shouldAllowUnrestrictedAccessSet.remove(recipientId) + } } } diff --git a/SignalServiceKit/src/Util/ParamParser.swift b/SignalServiceKit/src/Util/ParamParser.swift index 9a470ebb6..ca011079e 100644 --- a/SignalServiceKit/src/Util/ParamParser.swift +++ b/SignalServiceKit/src/Util/ParamParser.swift @@ -139,4 +139,13 @@ public class ParamParser { return data } + + // MARK: Bool + + public func optionalBool(key: Key, defaultValue: Bool) throws -> Bool { + guard let optionalValue: Bool = try optional(key: key) else { + return defaultValue + } + return optionalValue + } }