|
|
|
@ -4,9 +4,9 @@ import PromiseKit
|
|
|
|
|
//
|
|
|
|
|
// • Don't use a database transaction if you can avoid it.
|
|
|
|
|
// • If you do need to use a database transaction, use a read transaction if possible.
|
|
|
|
|
// • Consider making it the caller's responsibility to manage the database transaction (this helps avoid nested or unnecessary transactions).
|
|
|
|
|
// • Consider making it the caller's responsibility to manage the database transaction (this helps avoid unnecessary transactions).
|
|
|
|
|
// • Think carefully about adding a function; there might already be one for what you need.
|
|
|
|
|
// • Document the expected cases for everything.
|
|
|
|
|
// • Document the expected cases in which a function will be used
|
|
|
|
|
// • Express those cases in tests.
|
|
|
|
|
|
|
|
|
|
/// See [The Session Friend Request Protocol](https://github.com/loki-project/session-protocol-docs/wiki/Friend-Requests) for more information.
|
|
|
|
@ -23,10 +23,10 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
|
|
|
|
|
// MARK: - General
|
|
|
|
|
@objc(isFriendsWithAnyLinkedDeviceOfHexEncodedPublicKey:)
|
|
|
|
|
public static func isFriendsWithAnyLinkedDevice(of hexEncodedPublicKey: String) -> Bool {
|
|
|
|
|
public static func isFriendsWithAnyLinkedDevice(of publicKey: String) -> Bool {
|
|
|
|
|
var result = false
|
|
|
|
|
storage.dbReadConnection.read { transaction in
|
|
|
|
|
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction)
|
|
|
|
|
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: publicKey, in: transaction)
|
|
|
|
|
let friendRequestStatuses = linkedDevices.map {
|
|
|
|
|
storage.getFriendRequestStatus(for: $0, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
@ -103,30 +103,29 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
|
|
|
|
|
// MARK: - Sending
|
|
|
|
|
@objc(acceptFriendRequestFromHexEncodedPublicKey:using:)
|
|
|
|
|
public static func acceptFriendRequest(from hexEncodedPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) else {
|
|
|
|
|
print("[Loki] Invalid Session ID: \(hexEncodedPublicKey).")
|
|
|
|
|
public static func acceptFriendRequest(from publicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: publicKey) else {
|
|
|
|
|
print("[Loki] Invalid Session ID: \(publicKey).")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let userHexEncodedPublicKey = getUserHexEncodedPublicKey()
|
|
|
|
|
let userLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: userHexEncodedPublicKey, in: transaction)
|
|
|
|
|
let userPublicKey = getUserHexEncodedPublicKey()
|
|
|
|
|
let userLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: userPublicKey, in: transaction)
|
|
|
|
|
// Accept all outstanding friend requests associated with this user and try to establish sessions with the
|
|
|
|
|
// subset of their devices that haven't sent a friend request.
|
|
|
|
|
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction)
|
|
|
|
|
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: publicKey, in: transaction)
|
|
|
|
|
for device in linkedDevices {
|
|
|
|
|
let friendRequestStatus = storage.getFriendRequestStatus(for: device, transaction: transaction)
|
|
|
|
|
if friendRequestStatus == .requestReceived {
|
|
|
|
|
storage.setFriendRequestStatus(.friends, for: device, transaction: transaction)
|
|
|
|
|
sendFriendRequestAcceptanceMessage(to: device, using: transaction)
|
|
|
|
|
sendFriendRequestAcceptedMessage(to: device, using: transaction)
|
|
|
|
|
// Send a contact sync message if needed
|
|
|
|
|
guard !userLinkedDevices.contains(hexEncodedPublicKey) else { return }
|
|
|
|
|
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
|
|
|
|
|
guard !userLinkedDevices.contains(publicKey) else { return }
|
|
|
|
|
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: publicKey, in: transaction) ?? publicKey
|
|
|
|
|
let syncManager = SSKEnvironment.shared.syncManager
|
|
|
|
|
syncManager.syncContact(masterHexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
} else if friendRequestStatus == .requestSent {
|
|
|
|
|
// We sent a friend request to this device before, how can we be sure that it hasn't expired?
|
|
|
|
|
} else if !userLinkedDevices.contains(device) && (friendRequestStatus == .none || friendRequestStatus == .requestExpired) {
|
|
|
|
|
// TODO: We should track these so that we can expire them and resend if needed
|
|
|
|
|
MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: device, in: transaction)
|
|
|
|
|
.done(on: OWSDispatch.sendingQueue()) { autoGeneratedFRMessageSend in
|
|
|
|
|
let messageSender = SSKEnvironment.shared.messageSender
|
|
|
|
@ -136,25 +135,25 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(sendFriendRequestAcceptanceMessageToHexEncodedPublicKey:using:)
|
|
|
|
|
public static func sendFriendRequestAcceptanceMessage(to hexEncodedPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) else {
|
|
|
|
|
print("[Loki] Invalid Session ID: \(hexEncodedPublicKey).")
|
|
|
|
|
@objc(sendFriendRequestAcceptedMessageToHexEncodedPublicKey:using:)
|
|
|
|
|
public static func sendFriendRequestAcceptedMessage(to publicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: publicKey) else {
|
|
|
|
|
print("[Loki] Invalid Session ID: \(publicKey).")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
let thread = TSContactThread.getOrCreateThread(withContactId: publicKey, transaction: transaction)
|
|
|
|
|
let ephemeralMessage = EphemeralMessage(thread: thread)
|
|
|
|
|
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
|
|
|
|
|
messageSenderJobQueue.add(message: ephemeralMessage, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(declineFriendRequestFromHexEncodedPublicKey:using:)
|
|
|
|
|
public static func declineFriendRequest(from hexEncodedPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) else {
|
|
|
|
|
print("[Loki] Invalid Session ID: \(hexEncodedPublicKey).")
|
|
|
|
|
public static func declineFriendRequest(from publicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: publicKey) else {
|
|
|
|
|
print("[Loki] Invalid Session ID: \(publicKey).")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction)
|
|
|
|
|
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: publicKey, in: transaction)
|
|
|
|
|
for device in linkedDevices {
|
|
|
|
|
let friendRequestStatus = storage.getFriendRequestStatus(for: device, transaction: transaction)
|
|
|
|
|
// We only want to decline incoming requests
|
|
|
|
@ -201,14 +200,13 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
// MARK: - Receiving
|
|
|
|
|
@objc(isFriendRequestFromBeforeRestoration:)
|
|
|
|
|
public static func isFriendRequestFromBeforeRestoration(_ envelope: SSKProtoEnvelope) -> Bool {
|
|
|
|
|
// The envelope type is set during UD decryption
|
|
|
|
|
let restorationTimeInMs = UInt64(storage.getRestorationTime() * 1000)
|
|
|
|
|
return (envelope.type == .friendRequest && envelope.timestamp < restorationTimeInMs)
|
|
|
|
|
return (envelope.type == .friendRequest && envelope.timestamp < restorationTimeInMs) // The envelope type is set during UD decryption
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(canFriendRequestBeAutoAcceptedForHexEncodedPublicKey:using:)
|
|
|
|
|
public static func canFriendRequestBeAutoAccepted(for hexEncodedPublicKey: String, using transaction: YapDatabaseReadTransaction) -> Bool {
|
|
|
|
|
if storage.getFriendRequestStatus(for: hexEncodedPublicKey, transaction: transaction) == .requestSent {
|
|
|
|
|
public static func canFriendRequestBeAutoAccepted(for publicKey: String, using transaction: YapDatabaseReadTransaction) -> Bool {
|
|
|
|
|
if storage.getFriendRequestStatus(for: publicKey, transaction: transaction) == .requestSent {
|
|
|
|
|
// This can happen if Alice sent Bob a friend request, Bob declined, but then Bob changed his
|
|
|
|
|
// mind and sent a friend request to Alice. In this case we want Alice to auto-accept the request
|
|
|
|
|
// and send a friend request accepted message back to Bob. We don't check that sending the
|
|
|
|
@ -222,10 +220,10 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// Auto-accept any friend requests from the user's own linked devices
|
|
|
|
|
let userLinkedDeviceHexEncodedPublicKeys = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction)
|
|
|
|
|
if userLinkedDeviceHexEncodedPublicKeys.contains(hexEncodedPublicKey) { return true }
|
|
|
|
|
let userLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction)
|
|
|
|
|
if userLinkedDevices.contains(publicKey) { return true }
|
|
|
|
|
// Auto-accept if the user is friends with any of the sender's linked devices.
|
|
|
|
|
let senderLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction)
|
|
|
|
|
let senderLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: publicKey, in: transaction)
|
|
|
|
|
if senderLinkedDevices.contains(where: {
|
|
|
|
|
storage.getFriendRequestStatus(for: $0, transaction: transaction) == .friends
|
|
|
|
|
}) {
|
|
|
|
@ -237,23 +235,21 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
|
|
|
|
|
@objc(handleFriendRequestAcceptanceIfNeeded:in:)
|
|
|
|
|
public static func handleFriendRequestAcceptanceIfNeeded(_ envelope: SSKProtoEnvelope, in transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
// The envelope source is set during UD decryption
|
|
|
|
|
let hexEncodedPublicKey = envelope.source!
|
|
|
|
|
// The envelope type is set during UD decryption.
|
|
|
|
|
guard !envelope.isGroupChatMessage && envelope.type != .friendRequest else { return }
|
|
|
|
|
let sender = envelope.source! // Set during UD decryption
|
|
|
|
|
guard !envelope.isGroupChatMessage && envelope.type != .friendRequest else { return } // The envelope type is set during UD decryption
|
|
|
|
|
// If we get an envelope that isn't a friend request, then we can infer that we had to use
|
|
|
|
|
// Signal cipher decryption and thus that we have a session with the other person.
|
|
|
|
|
let friendRequestStatus = storage.getFriendRequestStatus(for: hexEncodedPublicKey, transaction: transaction);
|
|
|
|
|
let friendRequestStatus = storage.getFriendRequestStatus(for: sender, transaction: transaction);
|
|
|
|
|
// We shouldn't be able to skip from none to friends
|
|
|
|
|
guard friendRequestStatus == .requestSending || friendRequestStatus == .requestSent
|
|
|
|
|
|| friendRequestStatus == .requestReceived else { return }
|
|
|
|
|
// Become friends
|
|
|
|
|
storage.setFriendRequestStatus(.friends, for: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
storage.setFriendRequestStatus(.friends, for: sender, transaction: transaction)
|
|
|
|
|
// Send a contact sync message if needed
|
|
|
|
|
guard !LokiDatabaseUtilities.isUserLinkedDevice(hexEncodedPublicKey, transaction: transaction) else { return }
|
|
|
|
|
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
|
|
|
|
|
guard !LokiDatabaseUtilities.isUserLinkedDevice(sender, transaction: transaction) else { return }
|
|
|
|
|
let masterPublicKey = storage.getMasterHexEncodedPublicKey(for: sender, in: transaction) ?? sender
|
|
|
|
|
let syncManager = SSKEnvironment.shared.syncManager
|
|
|
|
|
syncManager.syncContact(masterHexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
syncManager.syncContact(masterPublicKey, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(handleFriendRequestMessageIfNeededFromEnvelope:using:)
|
|
|
|
@ -262,16 +258,14 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
print("[Loki] Ignoring friend request in group chat.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// The envelope source is set during UD decryption
|
|
|
|
|
let hexEncodedPublicKey = envelope.source!
|
|
|
|
|
// The envelope type is set during UD decryption.
|
|
|
|
|
guard envelope.type == .friendRequest else {
|
|
|
|
|
let hexEncodedPublicKey = envelope.source! // Set during UD decryption
|
|
|
|
|
guard envelope.type == .friendRequest else { // Set during UD decryption
|
|
|
|
|
print("[Loki] Ignoring friend request logic for non friend request type envelope.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if canFriendRequestBeAutoAccepted(for: hexEncodedPublicKey, using: transaction) {
|
|
|
|
|
storage.setFriendRequestStatus(.friends, for: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
sendFriendRequestAcceptanceMessage(to: hexEncodedPublicKey, using: transaction)
|
|
|
|
|
sendFriendRequestAcceptedMessage(to: hexEncodedPublicKey, using: transaction)
|
|
|
|
|
} else if storage.getFriendRequestStatus(for: hexEncodedPublicKey, transaction: transaction) != .friends {
|
|
|
|
|
// Checking that the sender of the message isn't already a friend is necessary because otherwise
|
|
|
|
|
// the following situation can occur: Alice and Bob are friends. Bob loses his database and his
|
|
|
|
|