Merge pull request #170 from loki-project/auto-generated-friend-requests

Auto Generated Friend Request Bug Fixes
pull/174/head
Niels Andriesse 5 years ago committed by GitHub
commit 7731da1bee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1 +1 @@
Subproject commit ad77d840df0857c6bdecce4aad305137d17a8ef6
Subproject commit 37d85c1574ddf246b62931bae0843fe3cc07cd64

@ -5,6 +5,7 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
private let mode: Mode
private let delegate: DeviceLinkingModalDelegate?
private var deviceLink: DeviceLink?
private var hasAuthorizedDeviceLink = false
// MARK: Types
enum Mode : String { case master, slave }
@ -78,6 +79,13 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
result.setTitle(NSLocalizedString("Authorize", comment: ""), for: UIControl.State.normal)
return result
}()
private lazy var mainStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ titleLabel, subtitleLabel, mnemonicLabel, buttonStackView ])
result.spacing = Values.largeSpacing
result.axis = .vertical
return result
}()
// MARK: Lifecycle
init(mode: Mode, delegate: DeviceLinkingModalDelegate?) {
@ -107,14 +115,11 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
}
override func populateContentView() {
let stackView = UIStackView(arrangedSubviews: [ titleLabel, subtitleLabel, mnemonicLabel, buttonStackView ])
switch mode {
case .master: stackView.insertArrangedSubview(qrCodeImageViewContainer, at: 0)
case .slave: stackView.insertArrangedSubview(spinner, at: 0)
case .master: mainStackView.insertArrangedSubview(qrCodeImageViewContainer, at: 0)
case .slave: mainStackView.insertArrangedSubview(spinner, at: 0)
}
contentView.addSubview(stackView)
stackView.spacing = Values.largeSpacing
stackView.axis = .vertical
contentView.addSubview(mainStackView)
switch mode {
case .master:
let hexEncodedPublicKey = getUserHexEncodedPublicKey()
@ -142,10 +147,10 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
}
authorizeButton.addTarget(self, action: #selector(authorizeDeviceLink), for: UIControl.Event.touchUpInside)
authorizeButton.isHidden = true
stackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing)
stackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing)
contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.largeSpacing)
contentView.pin(.bottom, to: .bottom, of: stackView, withInset: Values.largeSpacing)
mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing)
mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing)
contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing)
contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing)
}
// MARK: Device Linking
@ -161,29 +166,53 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
}
@objc private func authorizeDeviceLink() {
guard !hasAuthorizedDeviceLink else { return }
hasAuthorizedDeviceLink = true
mainStackView.removeArrangedSubview(qrCodeImageViewContainer)
mainStackView.insertArrangedSubview(spinner, at: 0)
spinner.set(.height, to: 64)
spinner.startAnimating()
titleLabel.text = NSLocalizedString("Authorizing Device Link", comment: "")
subtitleLabel.text = NSLocalizedString("Please wait while the device link is created. This can take up to a minute.", comment: "")
mnemonicLabel.isHidden = true
buttonStackView.isHidden = true
let deviceLink = self.deviceLink!
DeviceLinkingSession.current!.markLinkingRequestAsProcessed()
DeviceLinkingSession.current!.stopListeningForLinkingRequests()
let linkingAuthorizationMessage = DeviceLinkingUtilities.getLinkingAuthorizationMessage(for: deviceLink)
ThreadUtil.enqueue(linkingAuthorizationMessage)
SSKEnvironment.shared.messageSender.send(linkingAuthorizationMessage, success: {
let _ = SSKEnvironment.shared.syncManager.syncAllContacts()
let _ = SSKEnvironment.shared.syncManager.syncAllGroups()
let _ = SSKEnvironment.shared.syncManager.syncAllOpenGroups()
let thread = TSContactThread.getOrCreateThread(contactId: deviceLink.slave.hexEncodedPublicKey)
thread.friendRequestStatus = .friends
thread.save()
}) { _ in
print("[Loki] Failed to send device link authorization message.")
}
let session = DeviceLinkingSession.current!
session.stopListeningForLinkingRequests()
session.markLinkingRequestAsProcessed()
dismiss(animated: true, completion: nil)
let master = DeviceLink.Device(hexEncodedPublicKey: deviceLink.master.hexEncodedPublicKey, signature: linkingAuthorizationMessage.masterSignature)
let signedDeviceLink = DeviceLink(between: master, and: deviceLink.slave)
LokiFileServerAPI.addDeviceLink(signedDeviceLink).done {
self.delegate?.handleDeviceLinkAuthorized(signedDeviceLink) // Intentionally capture self strongly
}.catch { error in
LokiFileServerAPI.addDeviceLink(signedDeviceLink).done(on: DispatchQueue.main) { [weak self] in
SSKEnvironment.shared.messageSender.send(linkingAuthorizationMessage, success: {
let thread = TSContactThread.getOrCreateThread(contactId: deviceLink.slave.hexEncodedPublicKey)
thread.save()
let _ = SSKEnvironment.shared.syncManager.syncAllContacts()
let _ = SSKEnvironment.shared.syncManager.syncAllGroups()
let _ = SSKEnvironment.shared.syncManager.syncAllOpenGroups()
thread.friendRequestStatus = .friends
thread.save()
DispatchQueue.main.async {
self?.dismiss(animated: true, completion: nil)
self?.delegate?.handleDeviceLinkAuthorized(signedDeviceLink)
}
}, failure: { error in
print("[Loki] Failed to send device link authorization message.")
let _ = LokiFileServerAPI.removeDeviceLink(signedDeviceLink) // Attempt to roll back
DispatchQueue.main.async {
self?.close()
let alert = UIAlertController(title: NSLocalizedString("Device Linking Failed", comment: ""), message: NSLocalizedString("Please check your internet connection and try again", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
self?.presentingViewController?.present(alert, animated: true, completion: nil)
}
})
}.catch { [weak self] error in
print("[Loki] Failed to add device link due to error: \(error).")
DispatchQueue.main.async {
self?.close() // TODO: Show a message to the user
let alert = UIAlertController(title: NSLocalizedString("Device Linking Failed", comment: ""), message: NSLocalizedString("Please check your internet connection and try again", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
self?.presentingViewController?.present(alert, animated: true, completion: nil)
}
}
}
@ -214,8 +243,9 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
session.markLinkingRequestAsProcessed() // Only relevant in master mode
delegate?.handleDeviceLinkingModalDismissed() // Only relevant in slave mode
if let deviceLink = deviceLink {
OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in
OWSPrimaryStorage.shared().removePreKeyBundle(forContact: deviceLink.slave.hexEncodedPublicKey, transaction: transaction)
let storage = OWSPrimaryStorage.shared()
storage.dbReadWriteConnection.readWrite { transaction in
storage.removePreKeyBundle(forContact: deviceLink.slave.hexEncodedPublicKey, transaction: transaction)
}
}
dismiss(animated: true, completion: nil)

@ -142,7 +142,7 @@ final class LandingVC : BaseVC, LinkDeviceVCDelegate, DeviceLinkingModalDelegate
// MARK: Device Linking
func requestDeviceLink(with hexEncodedPublicKey: String) {
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) else {
let alert = UIAlertController(title: NSLocalizedString("Invalid Public Key", comment: ""), message: NSLocalizedString("Please make sure the public key you entered is correct and try again.", comment: ""), preferredStyle: .alert)
let alert = UIAlertController(title: NSLocalizedString("Invalid Session ID", comment: ""), message: NSLocalizedString("Please make sure the Session ID you entered is correct and try again.", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), accessibilityIdentifier: nil, style: .default, handler: nil))
return present(alert, animated: true, completion: nil)
}

@ -68,7 +68,8 @@ final class PNModeVC : BaseVC, OptionViewDelegate {
// Set up top stack view
let topStackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, optionsStackView ])
topStackView.axis = .vertical
topStackView.spacing = isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing
let isMediumScreen = (UIScreen.main.bounds.height - 667) < 1
topStackView.spacing = isSmallScreen ? Values.smallSpacing : (isMediumScreen ? Values.mediumSpacing : Values.veryLargeSpacing)
topStackView.alignment = .fill
// Set up top stack view container
let topStackViewContainer = UIView(wrapping: topStackView, withInsets: UIEdgeInsets(top: 0, leading: Values.veryLargeSpacing, bottom: 0, trailing: Values.veryLargeSpacing))

@ -2823,3 +2823,9 @@
"Confirm" = "Confirm";
"Skip" = "Skip";
"Link Previews" = "Link Previews";
"Invalid Session ID" = "Invalid Session ID";
"Please make sure the Session ID you entered is correct and try again." = "Please make sure the Session ID you entered is correct and try again.";
"Device Linking Failed" = "Device Linking Failed";
"Please check your internet connection and try again" = "Please check your internet connection and try again";
"Authorization Device Link" = "Authorization Device Link";
"Please wait while the device link is created. This can take up to a minute." = "Please wait while the device link is created. This can take up to a minute.";

@ -11,7 +11,7 @@ public final class DeviceLink : NSObject, NSCoding {
return (userHexEncodedPublicKey == master.hexEncodedPublicKey) ? slave : master
}
// MARK: Nested Types
// MARK: Device
@objc(LKDevice)
public final class Device : NSObject, NSCoding {
@objc public let hexEncodedPublicKey: String

@ -135,7 +135,7 @@ public final class MultiDeviceProtocol : NSObject {
return OWSMessageSend(message: message, thread: thread, recipient: recipient, senderCertificate: senderCertificate,
udAccess: recipientUDAccess, localNumber: getUserHexEncodedPublicKey(), success: {
}, failure: { error in
}, failure: { _ in
})
}

@ -1,5 +1,5 @@
@objc public final class GroupParser : NSObject {
@objc public final class ClosedGroupParser : NSObject {
private let data: Data
@objc public init(data: Data) {

@ -20,15 +20,15 @@ public final class SyncMessagesProtocol : NSObject {
// MARK: - Sending
@objc(shouldSkipConfigurationSyncMessage)
public static func shouldSkipConfigurationSyncMessage() -> Bool {
// FIXME: We added this check to avoid a crash, but we should really figure out why that crash was happening in the first place
return !UserDefaults.standard[.hasLaunchedOnce]
}
@objc(syncContactWithHexEncodedPublicKey:in:)
public static func syncContact(_ hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) {
if let thread = TSContactThread.getWithContactId(hexEncodedPublicKey, transaction: transaction) { // TODO: Should this be getOrCreate?
let syncManager = SSKEnvironment.shared.syncManager
syncManager.syncContacts(for: [ SignalAccount(recipientId: hexEncodedPublicKey) ])
}
guard TSContactThread.getWithContactId(hexEncodedPublicKey, transaction: transaction) != nil else { return }
let syncManager = SSKEnvironment.shared.syncManager
syncManager.syncContacts(for: [ SignalAccount(recipientId: hexEncodedPublicKey) ])
}
@objc(syncAllContacts)
@ -46,7 +46,7 @@ public final class SyncMessagesProtocol : NSObject {
}
friends.append(SignalAccount(recipientId: getUserHexEncodedPublicKey())) // TODO: We sure about this?
let syncManager = SSKEnvironment.shared.syncManager
let promises = friends.chunked(by: 3).map { friends -> Promise<Void> in
let promises = friends.chunked(by: 3).map { friends -> Promise<Void> in // TODO: Does this always fit?
return Promise(syncManager.syncContacts(for: friends)).map { _ in }
}
return when(fulfilled: promises)
@ -60,7 +60,8 @@ public final class SyncMessagesProtocol : NSObject {
public static func syncAllClosedGroups() -> Promise<Void> {
var groups: [TSGroupThread] = []
TSGroupThread.enumerateCollectionObjects { object, _ in
guard let group = object as? TSGroupThread, group.groupModel.groupType == .closedGroup, group.shouldThreadBeVisible, !group.isForceHidden else { return }
guard let group = object as? TSGroupThread, group.groupModel.groupType == .closedGroup,
group.shouldThreadBeVisible, !group.isForceHidden else { return }
groups.append(group)
}
let syncManager = SSKEnvironment.shared.syncManager
@ -81,9 +82,9 @@ public final class SyncMessagesProtocol : NSObject {
let messageSender = SSKEnvironment.shared.messageSender
messageSender.send(openGroupSyncMessage, success: {
seal.fulfill(())
}) { error in
}, failure: { error in
seal.reject(error)
}
})
return promise
}
@ -158,7 +159,11 @@ public final class SyncMessagesProtocol : NSObject {
let wasSentByMasterDevice = (masterHexEncodedPublicKey == hexEncodedPublicKey)
guard wasSentByMasterDevice, let contacts = syncMessage.contacts, let contactsAsData = contacts.data, contactsAsData.count > 0 else { return }
print("[Loki] Contact sync message received.")
let parser = ContactParser(data: contactsAsData)
handleContactSyncMessageData(contactsAsData, using: transaction)
}
public static func handleContactSyncMessageData(_ data: Data, using transaction: YapDatabaseReadWriteTransaction) {
let parser = ContactParser(data: data)
let hexEncodedPublicKeys = parser.parseHexEncodedPublicKeys()
// Try to establish sessions
for hexEncodedPublicKey in hexEncodedPublicKeys {
@ -170,20 +175,18 @@ public final class SyncMessagesProtocol : NSObject {
let autoGeneratedFRMessage = MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessage(for: hexEncodedPublicKey, in: transaction)
thread.isForceHidden = true
thread.save(with: transaction)
// This takes into account multi device
messageSender.send(autoGeneratedFRMessage, success: {
storage.dbReadWriteConnection.readWrite { transaction in
autoGeneratedFRMessage.remove()
thread.isForceHidden = false
}
autoGeneratedFRMessage.remove(with: transaction)
thread.isForceHidden = false
}, failure: { error in
storage.dbReadWriteConnection.readWrite { transaction in
autoGeneratedFRMessage.remove()
thread.isForceHidden = false
}
autoGeneratedFRMessage.remove(with: transaction)
thread.isForceHidden = false
})
case .requestReceived:
thread.saveFriendRequestStatus(.friends, with: transaction)
FriendRequestProtocol.sendFriendRequestAcceptanceMessage(to: hexEncodedPublicKey, in: thread, using: transaction) // TODO: Shouldn't this be acceptFriendRequest so it takes into account multi device?
// Not sendFriendRequestAcceptanceMessage(to:in:using:) to take into account multi device
FriendRequestProtocol.acceptFriendRequest(from: hexEncodedPublicKey, in: thread, using: transaction)
default: break
}
}
@ -197,17 +200,17 @@ public final class SyncMessagesProtocol : NSObject {
let wasSentByMasterDevice = (masterHexEncodedPublicKey == hexEncodedPublicKey)
guard wasSentByMasterDevice, let groups = syncMessage.groups, let groupsAsData = groups.data, groupsAsData.count > 0 else { return }
print("[Loki] Closed group sync message received.")
let parser = GroupParser(data: groupsAsData)
let parser = ClosedGroupParser(data: groupsAsData)
let groupModels = parser.parseGroupModels()
for groupModel in groupModels {
var thread: TSGroupThread! = TSGroupThread(groupId: groupModel.groupId, transaction: transaction)
if thread == nil {
thread = TSGroupThread.getOrCreateThread(with: groupModel, transaction: transaction)
thread.save(with: transaction)
ClosedGroupsProtocol.establishSessionsIfNeeded(with: groupModel.groupMemberIds, in: thread, using: transaction)
let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate, customMessage: "You have joined the group.")
infoMessage.save(with: transaction)
}
ClosedGroupsProtocol.establishSessionsIfNeeded(with: groupModel.groupMemberIds, in: thread, using: transaction)
let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate, customMessage: "You have joined the group.")
infoMessage.save(with: transaction)
}
}

@ -0,0 +1,49 @@
import PromiseKit
@testable import SignalServiceKit
import XCTest
class SyncMessagesProtocolTests : XCTestCase {
private var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
override func setUp() {
super.setUp()
// Activate the mock environment
ClearCurrentAppContextForTests()
SetCurrentAppContext(TestAppContext())
MockSSKEnvironment.activate()
// Register a mock user
let identityManager = OWSIdentityManager.shared()
let seed = Randomness.generateRandomBytes(16)!
let keyPair = Curve25519.generateKeyPair(fromSeed: seed + seed)
let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection
databaseConnection.setObject(keyPair, forKey: OWSPrimaryStorageIdentityKeyStoreIdentityKey, inCollection: OWSPrimaryStorageIdentityKeyStoreCollection)
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = keyPair.hexEncodedPublicKey
TSAccountManager.sharedInstance().didRegister()
}
func testContactSyncMessageHandling() {
// Let's say Alice and Bob have an ongoing conversation. Alice now links a device. Let's call Alice's master device A1
// and her slave device A2, and let's call Bob's device B. When Alice links A2 to A1, A2 needs to somehow establish a
// session with B (it already established a session with A1 when the devices were linked). How does it do this?
//
// As part of the linking process, A2 should've received a contact sync from A1. Upon receiving this contact sync,
// A2 should send out AFRs to the subset of the contacts it received from A1 for which it doesn't yet have a session (in
// theory this should be all of them).
let base64EncodedContactData = "AAAA7QpCMDU0ZmI2M2IxYTU4YjU1YTcwNjMxODkyOWRjNmQxMWM4ZWY3OTAxMTZhNzRjOWFmNTVmYTZhMzZlNjhmMTYzYTMyEhBZMyAoLi4uOGYxNjNhMzIpIgZvcmFuZ2UqaQpCMDU0ZmI2M2IxYTU4YjU1YTcwNjMxODkyOWRjNmQxMWM4ZWY3OTAxMTZhNzRjOWFmNTVmYTZhMzZlNjhmMTYzYTMyEiEFT7Y7Gli1WnBjGJKdxtEcjveQEWp0ya9V+mo25o8WOjIYADIgXAgtAlrJr81tnuWyk8TgJhdsKzz+yIui5mXnbcMyPk1AAAAAAOwKQjA1Nzg4MmQzM2E4OTI1NDdiOTI2NjIyYjk0ZDZjMWNmYjI1ZmY2YTczZmQ4OTZlMWIxNmY1ODI0NzRjZjQ3MDE2YhIQWTQgKC4uLmNmNDcwMTZiKSIFYnJvd24qaQpCMDU3ODgyZDMzYTg5MjU0N2I5MjY2MjJiOTRkNmMxY2ZiMjVmZjZhNzNmZDg5NmUxYjE2ZjU4MjQ3NGNmNDcwMTZiEiEFeILTOoklR7kmYiuU1sHPsl/2pz/YluGxb1gkdM9HAWsYADIgD1QA1ofVIccRhbx8AnbygQYo5iOiyGUMG/sGNP1ENRJAAAAAAPAKQjA1OTUyYTRiNTFjNDJkZWE2OWEwYWNhNWU2OTgxYTQ2MDk0NGI2Yjc0NjdkOWQ5OTliOWU3NjExNzdkYWI1NzIxMxIQWTEgKC4uLmRhYjU3MjEzKSIJYmx1ZV9ncmV5KmkKQjA1OTUyYTRiNTFjNDJkZWE2OWEwYWNhNWU2OTgxYTQ2MDk0NGI2Yjc0NjdkOWQ5OTliOWU3NjExNzdkYWI1NzIxMxIhBZUqS1HELeppoKyl5pgaRglEtrdGfZ2Zm552EXfatXITGAAyIBkyX0S08IAuov6faUvaxYsfJtdpww1G4LF6bG5vG7L+QAA="
let contactData = Data(base64Encoded: base64EncodedContactData)!
let parser = ContactParser(data: contactData)
let hexEncodedPublicKeys = parser.parseHexEncodedPublicKeys()
storage.dbReadWriteConnection.readWrite { transaction in
SyncMessagesProtocol.handleContactSyncMessageData(contactData, using: transaction)
}
hexEncodedPublicKeys.forEach { hexEncodedPublicKey in
var thread: TSContactThread!
storage.dbReadWriteConnection.readWrite { transaction in
thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
}
XCTAssert(thread.friendRequestStatus == .requestSent)
}
// TODO: Test the case where Bob has multiple devices
}
}
Loading…
Cancel
Save