Handle friend requests correctly.

This commit is contained in:
Mikunj 2019-10-24 12:17:58 +11:00
parent 1c1685ae9d
commit 24ae0c640b
4 changed files with 74 additions and 59 deletions

View File

@ -3015,6 +3015,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
messageSender.sendMessage(0, address, Optional.absent(), message); // The message ID doesn't matter messageSender.sendMessage(0, address, Optional.absent(), message); // The message ID doesn't matter
DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS); DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS);
lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
Util.runOnMain(this::updateInputPanel);
} catch (Exception e) { } catch (Exception e) {
Log.d("Loki", "Failed to send background message to: " + contactID + "."); Log.d("Loki", "Failed to send background message to: " + contactID + ".");
} }
@ -3030,6 +3031,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.NONE); DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.NONE);
String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress().toString(); String contactID = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress().toString();
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(contactID); DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(contactID);
updateInputPanel();
} }
public boolean isFriendsWithAnyLinkedDevice() { public boolean isFriendsWithAnyLinkedDevice() {

View File

@ -144,6 +144,7 @@ import javax.inject.Inject;
import kotlin.Unit; import kotlin.Unit;
import network.loki.messenger.R; import network.loki.messenger.R;
import nl.komponents.kovenant.Promise;
public class PushDecryptJob extends BaseJob implements InjectableType { public class PushDecryptJob extends BaseJob implements InjectableType {
@ -1097,57 +1098,55 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
if (!envelope.isFriendRequest()) { return; } if (!envelope.isFriendRequest()) { return; }
// This handles the case where another user sends us a regular message without authorisation // This handles the case where another user sends us a regular message without authorisation
MultiDeviceUtilitiesKt.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context).success(becomeFriends -> { boolean shouldBecomeFriends = MultiDeviceUtilitiesKt.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context);
if (becomeFriends) { if (shouldBecomeFriends) {
// Become friends AND update the message they sent // Become friends AND update the message they sent
becomeFriendsWithContact(content.getSender()); becomeFriendsWithContact(content.getSender());
// Send them an accept message back // Send them an accept message back
sendBackgroundMessage(content.getSender());
} else {
// Do regular friend request logic checks
Recipient originalRecipient = getMessageDestination(content, message);
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
SmsDatabase smsMessageDatabase = DatabaseFactory.getSmsDatabase(context);
MmsDatabase mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context);
LokiMessageDatabase lokiMessageDatabase= DatabaseFactory.getLokiMessageDatabase(context);
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient);
long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient);
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
int messageCount = smsMessageDatabase.getMessageCountForThread(primaryDeviceThreadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
// 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
// friend request accepted message succeeded. Even if it doesn't, the thread's current friend
// request status will be set to `FRIENDS` for Alice making it possible
// for Alice to send messages to Bob. When Bob receives a message, his thread's friend request status
// will then be set to `FRIENDS`. If we do check for a successful send
// before updating Alice's thread's friend request status to `FRIENDS`,
// we can end up in a deadlock where both users' threads' friend request statuses are
// `REQUEST_SENT`.
lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
// Since messages are forwarded to the primary device thread, we need to update it there
long messageID = smsMessageDatabase.getIDForMessageAtIndex(primaryDeviceThreadID, messageCount - 2); // The message before the one that was just received
lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
// Accept the friend request
sendBackgroundMessage(content.getSender()); sendBackgroundMessage(content.getSender());
} else { } else if (threadFriendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) {
// Do regular friend request logic checks // Checking that the sender of the message isn't already a friend is necessary because otherwise
Recipient contactID = getMessageDestination(content, message); // the following situation can occur: Alice and Bob are friends. Bob loses his database and his
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); // friend request status is reset to `NONE`. Bob now sends Alice a friend
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(contactID); // request. Alice's thread's friend request status is reset to
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID); // `REQUEST_RECEIVED`.
SmsDatabase smsMessageDatabase = DatabaseFactory.getSmsDatabase(context); lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED);
MmsDatabase mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context); // Since messages are forwarded to the primary device thread, we need to update it there
LokiMessageDatabase lokiMessageDatabase= DatabaseFactory.getLokiMessageDatabase(context); long smsMessageID = smsMessageDatabase.getIDForMessageAtIndex(primaryDeviceThreadID, messageCount - 1); // The message that was just received
int messageCount = smsMessageDatabase.getMessageCountForThread(threadID); long messageID = smsMessageID != -1 ? smsMessageID : mmsMessageDatabase.getIDForMessageAtIndex(primaryDeviceThreadID, 0);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) { lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING);
// 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
// friend request accepted message succeeded. Even if it doesn't, the thread's current friend
// request status will be set to `FRIENDS` for Alice making it possible
// for Alice to send messages to Bob. When Bob receives a message, his thread's friend request status
// will then be set to `FRIENDS`. If we do check for a successful send
// before updating Alice's thread's friend request status to `FRIENDS`,
// we can end up in a deadlock where both users' threads' friend request statuses are
// `REQUEST_SENT`.
lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
long messageID = smsMessageDatabase.getIDForMessageAtIndex(threadID, messageCount - 2); // The message before the one that was just received
// TODO: MMS
lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
// Accept the friend request
sendBackgroundMessage(content.getSender());
} else if (threadFriendRequestStatus != LokiThreadFriendRequestStatus.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
// friend request status is reset to `NONE`. Bob now sends Alice a friend
// request. Alice's thread's friend request status is reset to
// `REQUEST_RECEIVED`.
lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED);
long messageID = smsMessageDatabase.getIDForMessageAtIndex(threadID, messageCount - 1); // The message that was just received
if (messageID != -1) {
lokiMessageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING);
} else {
// TODO: The code below is ugly due to Java limitations
lokiMessageDatabase.setFriendRequestStatus(mmsMessageDatabase.getIDForMessageAtIndex(threadID, 0), LokiMessageFriendRequestStatus.REQUEST_PENDING);
}
}
} }
return Unit.INSTANCE; }
});
} }
private void sendBackgroundMessage(String contactHexEncodedPublicKey) { private void sendBackgroundMessage(String contactHexEncodedPublicKey) {

View File

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import net.sqlcipher.Cursor
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.PreKeyUtil import org.thoughtcrime.securesms.crypto.PreKeyUtil
import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.Database
@ -92,7 +93,14 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
fun hasPreKeyBundle(hexEncodedPublicKey: String): Boolean { fun hasPreKeyBundle(hexEncodedPublicKey: String): Boolean {
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
val cursor = database.query(tableName, null, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ), null, null, null) var cursor: Cursor? = null
return cursor != null && cursor.count > 0 return try {
cursor = database.query(tableName, null, "${Companion.hexEncodedPublicKey} = ?", arrayOf( hexEncodedPublicKey ), null, null, null)
cursor != null && cursor.count > 0
} catch (e: Exception) {
false
} finally {
cursor?.close()
}
} }
} }

View File

@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.logging.Log import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.concurrent.SettableFuture
import org.whispersystems.libsignal.util.guava.Optional import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
@ -51,33 +52,38 @@ fun getAllDevicePublicKeys(context: Context, hexEncodedPublicKey: String, storag
} }
} }
fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Promise<Boolean, Unit> { fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Boolean {
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
val storageAPI = LokiStorageAPI.shared val storageAPI = LokiStorageAPI.shared
val deferred = deferred<Boolean, Unit>() val future = SettableFuture<Boolean>()
storageAPI.getPrimaryDevicePublicKey(publicKey).success { primaryDevicePublicKey -> storageAPI.getPrimaryDevicePublicKey(publicKey).success { primaryDevicePublicKey ->
if (primaryDevicePublicKey == null) { if (primaryDevicePublicKey == null) {
deferred.resolve(false) future.set(false)
return@success return@success
} }
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
if (primaryDevicePublicKey == userHexEncodedPublicKey) { if (primaryDevicePublicKey == userHexEncodedPublicKey) {
storageAPI.getSecondaryDevicePublicKeys(userHexEncodedPublicKey).success { secondaryDevices -> storageAPI.getSecondaryDevicePublicKeys(userHexEncodedPublicKey).success { secondaryDevices ->
deferred.resolve(secondaryDevices.contains(publicKey)) future.set(secondaryDevices.contains(publicKey))
}.fail { }.fail {
deferred.resolve(false) future.set(false)
} }
return@success return@success
} }
val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false) val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false)
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice) val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice)
if (threadID < 0) { if (threadID < 0) {
deferred.resolve(false) future.set(false)
return@success return@success
} }
deferred.resolve(lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) future.set(lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS)
}
return try {
future.get()
} catch (e: Exception) {
false
} }
return deferred.promise
} }
fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> { fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> {