From 878704cb12e7a6de61a2b05e5f76d3d2fbc0dfef Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Sun, 9 Apr 2017 21:39:04 -0400 Subject: [PATCH 1/3] Create block offer when non-contacts send you a message. // FREEBIE --- Signal/src/ViewControllers/BlockListUIUtils.h | 5 + .../ViewControllers/MessagesViewController.m | 110 +++++++++++++----- Signal/src/util/ThreadUtil.h | 9 ++ Signal/src/util/ThreadUtil.m | 110 ++++++++++++++++++ .../translations/en.lproj/Localizable.strings | 9 ++ 5 files changed, 211 insertions(+), 32 deletions(-) diff --git a/Signal/src/ViewControllers/BlockListUIUtils.h b/Signal/src/ViewControllers/BlockListUIUtils.h index a7b6654bd..2960692ce 100644 --- a/Signal/src/ViewControllers/BlockListUIUtils.h +++ b/Signal/src/ViewControllers/BlockListUIUtils.h @@ -44,6 +44,11 @@ typedef void (^BlockActionCompletionBlock)(BOOL isBlocked); contactsManager:(OWSContactsManager *)contactsManager completionBlock:(nullable BlockActionCompletionBlock)completionBlock; +#pragma mark - UI Utils + ++ (NSString *)formatDisplayNameForAlertTitle:(NSString *)displayName; ++ (NSString *)formatDisplayNameForAlertMessage:(NSString *)displayName; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index f0e75f826..c35a6ff56 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -6,8 +6,8 @@ #import "AppDelegate.h" #import "AttachmentSharing.h" #import "BlockListUIUtils.h" -#import "DebugUITableViewController.h" #import "BlockListViewController.h" +#import "DebugUITableViewController.h" #import "Environment.h" #import "FingerprintViewController.h" #import "FullImageViewController.h" @@ -23,6 +23,7 @@ #import "OWSIncomingMessageCollectionViewCell.h" #import "OWSMessagesBubblesSizeCalculator.h" #import "OWSOutgoingMessageCollectionViewCell.h" +#import "OWSUnknownContactBlockOfferMessage.h" #import "PropertyListPreferences.h" #import "Signal-Swift.h" #import "SignalKeyingStorage.h" @@ -383,6 +384,14 @@ typedef enum : NSUInteger { self.senderDisplayName = ME_MESSAGE_IDENTIFIER; [self initializeToolbars]; + + if ([self.thread isKindOfClass:[TSContactThread class]]) { + TSContactThread *contactThread = (TSContactThread *)self.thread; + [ThreadUtil createBlockOfferIfNecessary:contactThread + storageManager:self.storageManager + contactsManager:self.contactsManager + blockingManager:self.blockingManager]; + } } - (void)viewDidLayoutSubviews @@ -2003,6 +2012,8 @@ typedef enum : NSUInteger { { if ([message isKindOfClass:[TSInvalidIdentityKeyErrorMessage class]]) { [self tappedInvalidIdentityKeyErrorMessage:(TSInvalidIdentityKeyErrorMessage *)message]; + } else if ([message isKindOfClass:[OWSUnknownContactBlockOfferMessage class]]) { + [self tappedUnknownContactBlockOfferMessage:(OWSUnknownContactBlockOfferMessage *)message]; } else if (message.errorType == TSErrorMessageInvalidMessage) { [self tappedCorruptedMessage:message]; } else { @@ -2050,48 +2061,83 @@ typedef enum : NSUInteger { NSString *titleFormat = NSLocalizedString(@"SAFETY_NUMBERS_ACTIONSHEET_TITLE", @"Action sheet heading"); NSString *titleText = [NSString stringWithFormat:titleFormat, keyOwner]; - UIAlertController *actionSheetController = [UIAlertController alertControllerWithTitle:titleText - message:nil - preferredStyle:UIAlertControllerStyleActionSheet]; - + UIAlertController *actionSheetController = + [UIAlertController alertControllerWithTitle:titleText + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"") style:UIAlertActionStyleCancel handler:nil]; [actionSheetController addAction:dismissAction]; - UIAlertAction *showSafteyNumberAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"SHOW_SAFETY_NUMBER_ACTION", @"Action sheet item") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * _Nonnull action) { - DDLogInfo(@"%@ Remote Key Changed actions: Show fingerprint display", self.tag); - [self showFingerprintWithTheirIdentityKey:errorMessage.newIdentityKey - theirSignalId:errorMessage.theirSignalId]; - }]; + UIAlertAction *showSafteyNumberAction = + [UIAlertAction actionWithTitle:NSLocalizedString(@"SHOW_SAFETY_NUMBER_ACTION", @"Action sheet item") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + DDLogInfo(@"%@ Remote Key Changed actions: Show fingerprint display", self.tag); + [self showFingerprintWithTheirIdentityKey:errorMessage.newIdentityKey + theirSignalId:errorMessage.theirSignalId]; + }]; [actionSheetController addAction:showSafteyNumberAction]; - - UIAlertAction *acceptSafetyNumberAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"ACCEPT_NEW_IDENTITY_ACTION", @"Action sheet item") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * _Nonnull action) { - DDLogInfo(@"%@ Remote Key Changed actions: Accepted new identity key", self.tag); - [errorMessage acceptNewIdentityKey]; - if ([errorMessage isKindOfClass:[TSInvalidIdentityKeySendingErrorMessage class]]) { - [self.messageSender - resendMessageFromKeyError:(TSInvalidIdentityKeySendingErrorMessage *) - errorMessage - success:^{ - DDLogDebug(@"%@ Successfully resent key-error message.", self.tag); - } - failure:^(NSError *_Nonnull error) { - DDLogError(@"%@ Failed to resend key-error message with error:%@", - self.tag, - error); - }]; - } - }]; + + UIAlertAction *acceptSafetyNumberAction = [UIAlertAction + actionWithTitle:NSLocalizedString(@"ACCEPT_NEW_IDENTITY_ACTION", @"Action sheet item") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + DDLogInfo(@"%@ Remote Key Changed actions: Accepted new identity key", self.tag); + [errorMessage acceptNewIdentityKey]; + if ([errorMessage isKindOfClass:[TSInvalidIdentityKeySendingErrorMessage class]]) { + [self.messageSender + resendMessageFromKeyError:(TSInvalidIdentityKeySendingErrorMessage *)errorMessage + success:^{ + DDLogDebug(@"%@ Successfully resent key-error message.", self.tag); + } + failure:^(NSError *_Nonnull error) { + DDLogError(@"%@ Failed to resend key-error message with error:%@", self.tag, error); + }]; + } + }]; [actionSheetController addAction:acceptSafetyNumberAction]; [self presentViewController:actionSheetController animated:YES completion:nil]; } +- (void)tappedUnknownContactBlockOfferMessage:(OWSUnknownContactBlockOfferMessage *)errorMessage +{ + NSString *displayName = [self.contactsManager displayNameForPhoneIdentifier:errorMessage.contactId]; + NSString *title = + [NSString stringWithFormat:NSLocalizedString(@"BLOCK_OFFER_ACTIONSHEET_TITLE_FORMAT", + @"Title format for action sheet that offers to block an unknown user." + @"Embeds {{the unknown user's name or phone number}}."), + [BlockListUIUtils formatDisplayNameForAlertTitle:displayName]]; + + UIAlertController *actionSheetController = + [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + + UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"") + style:UIAlertActionStyleCancel + handler:nil]; + [actionSheetController addAction:dismissAction]; + + UIAlertAction *blockAction = + [UIAlertAction actionWithTitle:NSLocalizedString(@"BLOCK_OFFER_ACTIONSHEET_BLOCK_ACTION", + @"Action sheet that will block an unknown user.") + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction *_Nonnull action) { + DDLogInfo(@"%@ Blocking an unknown user.", self.tag); + [self.blockingManager addBlockedPhoneNumber:errorMessage.contactId]; + // Delete the block offer. + [self.storageManager.dbConnection + readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [errorMessage removeWithTransaction:transaction]; + }]; + }]; + [actionSheetController addAction:blockAction]; + + [self presentViewController:actionSheetController animated:YES completion:nil]; +} + #pragma mark - UIImagePickerController /* diff --git a/Signal/src/util/ThreadUtil.h b/Signal/src/util/ThreadUtil.h index 6c38895c9..f62c10f07 100644 --- a/Signal/src/util/ThreadUtil.h +++ b/Signal/src/util/ThreadUtil.h @@ -5,6 +5,10 @@ @class TSThread; @class OWSMessageSender; @class SignalAttachment; +@class TSContactThread; +@class TSStorageManager; +@class OWSContactsManager; +@class OWSBlockingManager; NS_ASSUME_NONNULL_BEGIN @@ -18,6 +22,11 @@ NS_ASSUME_NONNULL_BEGIN inThread:(TSThread *)thread messageSender:(OWSMessageSender *)messageSender; ++ (void)createBlockOfferIfNecessary:(TSContactThread *)contactThread + storageManager:(TSStorageManager *)storageManager + contactsManager:(OWSContactsManager *)contactsManager + blockingManager:(OWSBlockingManager *)blockingManager; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/util/ThreadUtil.m b/Signal/src/util/ThreadUtil.m index dcae0fccf..66bb98acd 100644 --- a/Signal/src/util/ThreadUtil.m +++ b/Signal/src/util/ThreadUtil.m @@ -3,10 +3,14 @@ // #import "ThreadUtil.h" +#import "OWSContactsManager.h" #import "Signal-Swift.h" #import +#import #import #import +#import +#import #import #import @@ -84,6 +88,112 @@ NS_ASSUME_NONNULL_BEGIN }]; } ++ (void)createBlockOfferIfNecessary:(TSContactThread *)contactThread + storageManager:(TSStorageManager *)storageManager + contactsManager:(OWSContactsManager *)contactsManager + blockingManager:(OWSBlockingManager *)blockingManager +{ + OWSAssert(contactThread); + OWSAssert(storageManager); + OWSAssert(contactsManager); + OWSAssert(blockingManager); + + if ([[blockingManager blockedPhoneNumbers] containsObject:contactThread.contactIdentifier]) { + // Only create block offers for users which are not already blocked. + return; + } + + Contact *contact = [contactsManager contactForPhoneIdentifier:contactThread.contactIdentifier]; + if (contact) { + // Only create block offers for non-contacts. + return; + } + + [storageManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + const int kMaxOutgoingMessageCount = 10; + + __block TSIncomingMessage *firstIncomingMessage = nil; + __block TSOutgoingMessage *firstOutgoingMessage = nil; + __block long outgoingMessageCount = 0; + __block BOOL hasUnknownContactBlockOffer = NO; + + NSMutableArray *blockOffers = [NSMutableArray new]; + [[transaction ext:TSMessageDatabaseViewExtensionName] + enumerateRowsInGroup:contactThread.uniqueId + usingBlock:^( + NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) { + + if ([object isKindOfClass:[OWSUnknownContactBlockOfferMessage class]]) { + hasUnknownContactBlockOffer = YES; + [blockOffers addObject:object]; + } else if ([object isKindOfClass:[TSIncomingMessage class]]) { + TSIncomingMessage *incomingMessage = (TSIncomingMessage *)object; + if (!firstIncomingMessage || + [[firstIncomingMessage receiptDateForSorting] + compare:[incomingMessage receiptDateForSorting]] + == NSOrderedDescending) { + firstIncomingMessage = incomingMessage; + } + } else if ([object isKindOfClass:[TSOutgoingMessage class]]) { + TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)object; + if (!firstOutgoingMessage || + [[firstOutgoingMessage receiptDateForSorting] + compare:[outgoingMessage receiptDateForSorting]] + == NSOrderedDescending) { + firstOutgoingMessage = outgoingMessage; + } + outgoingMessageCount++; + if (outgoingMessageCount > kMaxOutgoingMessageCount) { + // If the user has sent more than N interactions, abort. + *stop = YES; + } + } + }]; + + if (!firstIncomingMessage && !firstOutgoingMessage) { + // If the thread has no interactions, abort. + return; + } + + if (outgoingMessageCount > kMaxOutgoingMessageCount) { + // If the user has sent more than N messages, abort. + return; + } + + if (hasUnknownContactBlockOffer) { + // If there already is a block offer, abort. + return; + } + + BOOL hasOutgoingBeforeIncomingInteraction = (firstOutgoingMessage + && (!firstIncomingMessage || + [[firstOutgoingMessage receiptDateForSorting] compare:[firstIncomingMessage receiptDateForSorting]] + == NSOrderedAscending)); + if (hasOutgoingBeforeIncomingInteraction) { + // If there is an outgoing message before an incoming message + // the local user initiated this conversation, abort. + return; + } + + DDLogInfo(@"Creating block offer for unknown contact"); + + // We want the block offer to be the first interaction in their + // conversation's timeline, so we back-date it to slightly before + // the first incoming message (which we know is the first message). + TSIncomingMessage *firstMessage = firstIncomingMessage; + uint64_t blockOfferTimestamp = firstMessage.timestamp - 1; + + // Create an error message we may or may not user. + // We create it eagerly to ensure that it's timestamps make it the + // first message in the thread. + TSErrorMessage *errorMessage = + [OWSUnknownContactBlockOfferMessage unknownContactBlockOfferMessage:blockOfferTimestamp + thread:contactThread + contactId:contactThread.contactIdentifier]; + [errorMessage saveWithTransaction:transaction]; + }]; +} + #pragma mark - Logging + (NSString *)tag diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 26899b7e5..7dcffe793 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -154,6 +154,12 @@ /* The title of the 'user unblocked' alert. */ "BLOCK_LIST_VIEW_UNBLOCKED_ALERT_TITLE" = "User Unblocked"; +/* Action sheet that will block an unknown user. */ +"BLOCK_OFFER_ACTIONSHEET_BLOCK_ACTION" = "Block"; + +/* Title format for action sheet that offers to block an unknown user.Embeds {{the unknown user's name or phone number}}. */ +"BLOCK_OFFER_ACTIONSHEET_TITLE_FORMAT" = "Block %@?"; + /* Accessibilty label for placing call button */ "CALL_LABEL" = "Call"; @@ -1021,6 +1027,9 @@ /* In Inbox view, last message label for thread with corrupted attachment. */ "UNKNOWN_ATTACHMENT_LABEL" = "Unknown attachment"; +/* No comment provided by engineer. */ +"UNKNOWN_CONTACT_BLOCK_OFFER" = "User not in your contacts. Would you like to block this user?"; + /* Displayed if for some reason we can't determine a contacts phone number *or* name */ "UNKNOWN_CONTACT_NAME" = "Unknown Contact"; From 55706e9bb937c5aabea88a7686a87c7297db14f0 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 10 Apr 2017 15:14:53 -0400 Subject: [PATCH 2/3] Respond to CR. // FREEBIE --- Signal/src/util/ThreadUtil.m | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Signal/src/util/ThreadUtil.m b/Signal/src/util/ThreadUtil.m index 66bb98acd..03c26ff31 100644 --- a/Signal/src/util/ThreadUtil.m +++ b/Signal/src/util/ThreadUtil.m @@ -117,7 +117,6 @@ NS_ASSUME_NONNULL_BEGIN __block long outgoingMessageCount = 0; __block BOOL hasUnknownContactBlockOffer = NO; - NSMutableArray *blockOffers = [NSMutableArray new]; [[transaction ext:TSMessageDatabaseViewExtensionName] enumerateRowsInGroup:contactThread.uniqueId usingBlock:^( @@ -125,22 +124,25 @@ NS_ASSUME_NONNULL_BEGIN if ([object isKindOfClass:[OWSUnknownContactBlockOfferMessage class]]) { hasUnknownContactBlockOffer = YES; - [blockOffers addObject:object]; + // If there already is a block offer, abort. + *stop = YES; } else if ([object isKindOfClass:[TSIncomingMessage class]]) { TSIncomingMessage *incomingMessage = (TSIncomingMessage *)object; - if (!firstIncomingMessage || - [[firstIncomingMessage receiptDateForSorting] - compare:[incomingMessage receiptDateForSorting]] - == NSOrderedDescending) { + if (!firstIncomingMessage) { firstIncomingMessage = incomingMessage; + } else { + OWSAssert([[firstIncomingMessage receiptDateForSorting] + compare:[incomingMessage receiptDateForSorting]] + == NSOrderedAscending); } } else if ([object isKindOfClass:[TSOutgoingMessage class]]) { TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)object; - if (!firstOutgoingMessage || - [[firstOutgoingMessage receiptDateForSorting] - compare:[outgoingMessage receiptDateForSorting]] - == NSOrderedDescending) { + if (!firstOutgoingMessage) { firstOutgoingMessage = outgoingMessage; + } else { + OWSAssert([[firstOutgoingMessage receiptDateForSorting] + compare:[outgoingMessage receiptDateForSorting]] + == NSOrderedAscending); } outgoingMessageCount++; if (outgoingMessageCount > kMaxOutgoingMessageCount) { @@ -183,9 +185,6 @@ NS_ASSUME_NONNULL_BEGIN TSIncomingMessage *firstMessage = firstIncomingMessage; uint64_t blockOfferTimestamp = firstMessage.timestamp - 1; - // Create an error message we may or may not user. - // We create it eagerly to ensure that it's timestamps make it the - // first message in the thread. TSErrorMessage *errorMessage = [OWSUnknownContactBlockOfferMessage unknownContactBlockOfferMessage:blockOfferTimestamp thread:contactThread From 1d2e5d218d1e34d469dcad7fc208607b9d3856db Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 12 Apr 2017 08:42:22 -0400 Subject: [PATCH 3/3] [SSK] Create block offer when non-contacts send you a message. // FREEBIE --- Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile.lock b/Podfile.lock index a50c4061f..286cb44d0 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -132,7 +132,7 @@ CHECKOUT OPTIONS: :commit: 139bde37738ccb6ebcc68bf1b02c8a28400b6760 :git: https://github.com/WhisperSystems/SignalProtocolKit.git SignalServiceKit: - :commit: adee71ba9b1fd955e3b4972233ab83548158f85b + :commit: 0ee09323f6052adb804af67dbd4330838477bb79 :git: https://github.com/WhisperSystems/SignalServiceKit.git SocketRocket: :commit: 877ac7438be3ad0b45ef5ca3969574e4b97112bf