From 3a79e1f2159891df3d6816ccb69bf4c23b0d93b3 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 21 Nov 2019 12:43:33 +1100 Subject: [PATCH] Handle unpair request flag. --- .../securesms/jobs/PushDecryptJob.java | 49 ++++++++------ .../securesms/loki/LinkedDevicesActivity.kt | 8 ++- .../securesms/loki/MultiDeviceUtilities.kt | 18 +++++ .../loki/PushBackgroundMessageSendJob.kt | 67 ++++++++++++------- .../securesms/sms/MessageSender.java | 14 +++- 5 files changed, 108 insertions(+), 48 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 4c94ae0594..8888927b0b 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -331,27 +331,38 @@ public class PushDecryptJob extends BaseJob implements InjectableType { SignalServiceDataMessage message = content.getDataMessage().get(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent(); - if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId); - else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId); - else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId); - else if (isMediaMessage) handleMediaMessage(content, message, smsMessageId, Optional.absent()); - else if (message.getBody().isPresent()) handleTextMessage(content, message, smsMessageId, Optional.absent()); + if (message.isUnpairingRequest()) { + // Make sure we got the request from our primary device + String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); + if (ourPrimaryDevice != null && ourPrimaryDevice.equals(content.getSender())) { + MultiDeviceUtilities.checkForRevocation(context); + } + } else { + if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId); + else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId); + else if (message.isExpirationUpdate()) + handleExpirationUpdate(content, message, smsMessageId); + else if (isMediaMessage) + handleMediaMessage(content, message, smsMessageId, Optional.absent()); + else if (message.getBody().isPresent()) + handleTextMessage(content, message, smsMessageId, Optional.absent()); - if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) { - handleUnknownGroupMessage(content, message.getGroupInfo().get()); + if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) { + handleUnknownGroupMessage(content, message.getGroupInfo().get()); + } + + if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { + handleProfileKey(content, message); + } + + // Loki - This doesn't get invoked for group chats + if (content.isNeedsReceipt()) { + handleNeedsDeliveryReceipt(content, message); + } + + // Loki - Handle friend request logic if needed + updateFriendRequestStatusIfNeeded(envelope, content, message); } - - if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { - handleProfileKey(content, message); - } - - // Loki - This doesn't get invoked for group chats - if (content.isNeedsReceipt()) { - handleNeedsDeliveryReceipt(content, message); - } - - // Loki - Handle friend request logic if needed - updateFriendRequestStatusIfNeeded(envelope, content, message); } else if (content.getSyncMessage().isPresent()) { TextSecurePreferences.setMultiDevice(context, true); diff --git a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt index 28c8556fcd..0788095b2e 100644 --- a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.* import org.thoughtcrime.securesms.util.DynamicTheme import org.thoughtcrime.securesms.util.DynamicLanguage import network.loki.messenger.R +import nl.komponents.kovenant.then import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.util.TextSecurePreferences @@ -44,9 +45,10 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinki val database = DatabaseFactory.getLokiAPIDatabase(this) database.removePairingAuthorisation(ourPublicKey, devicePublicKey) // Update mapping on the file server - LokiStorageAPI.shared.updateUserDeviceMappings() - // Send a background message to let the device know that it has been revoked - MessageSender.sendBackgroundMessage(this, devicePublicKey) + LokiStorageAPI.shared.updateUserDeviceMappings().success { + // Send an unpair request to let the device know that it has been revoked + MessageSender.sendUnpairRequest(this, devicePublicKey) + } // Refresh the list this.deviceListFragment.refresh() Toast.makeText(this, R.string.DeviceListActivity_unlinked_device, Toast.LENGTH_LONG).show() diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 257336de05..aae9a72a15 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -6,6 +6,7 @@ import nl.komponents.kovenant.Promise import nl.komponents.kovenant.all import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map +import nl.komponents.kovenant.then import nl.komponents.kovenant.toFailVoid import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil @@ -27,6 +28,23 @@ import org.whispersystems.signalservice.loki.utilities.retryIfNeeded import java.util.* import kotlin.concurrent.schedule +fun checkForRevocation(context: Context) { + val primaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) ?: return + val ourDevice = TextSecurePreferences.getLocalNumber(context) + + LokiStorageAPI.shared.fetchDeviceMappings(primaryDevice).bind { mappings -> + val ourMapping = mappings.find { it.secondaryDevicePublicKey == ourDevice } + if (ourMapping != null) throw Error("Device has not been revoked") + // remove pairing auths for our device + DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(ourDevice) + LokiStorageAPI.shared.updateUserDeviceMappings() + }.success { + // TODO: Revoke here + }.fail { error -> + Log.d("Loki", "Revocation check failed: $error") + } +} + fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise, Exception> { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys -> diff --git a/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt b/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt index 0c7e055ff1..d1a81005b8 100644 --- a/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt +++ b/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt @@ -11,39 +11,58 @@ import org.whispersystems.libsignal.util.guava.Optional import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage import org.whispersystems.signalservice.api.push.SignalServiceAddress +import org.whispersystems.signalservice.internal.util.JsonUtil import java.io.IOException import java.util.concurrent.TimeUnit +data class BackgroundMessage private constructor(val recipient: String, val body: String?, val friendRequest: Boolean, val unpairingRequest: Boolean) { + companion object { + @JvmStatic + fun create(recipient: String) = BackgroundMessage(recipient, null, false, false) + @JvmStatic + fun createFriendRequest(recipient: String, messageBody: String) = BackgroundMessage(recipient, messageBody, true, false) + @JvmStatic + fun createUnpairingRequest(recipient: String) = BackgroundMessage(recipient, null, false, true) + + internal fun parse(serialized: String): BackgroundMessage { + val node = JsonUtil.fromJson(serialized) + val recipient = node.get("recipient").asText() + val body = if (node.hasNonNull("body")) node.get("body").asText() else null + val friendRequest = node.get("friendRequest").asBoolean(false) + val unpairingRequest = node.get("unpairingRequest").asBoolean(false) + return BackgroundMessage(recipient, body, friendRequest, unpairingRequest) + } + } + + fun serialize(): String { + val map = mapOf("recipient" to recipient, "body" to body, "friendRequest" to friendRequest, "unpairingRequest" to unpairingRequest) + return JsonUtil.toJson(map) + } +} + class PushBackgroundMessageSendJob private constructor( parameters: Parameters, - private val recipient: String, - private val messageBody: String?, - private val friendRequest: Boolean + private val message: BackgroundMessage ) : BaseJob(parameters) { companion object { const val KEY = "PushBackgroundMessageSendJob" private val TAG = PushBackgroundMessageSendJob::class.java.simpleName - private val KEY_RECIPIENT = "recipient" - private val KEY_MESSAGE_BODY = "message_body" - private val KEY_FRIEND_REQUEST = "asFriendRequest" + private val KEY_MESSAGE = "message" } - constructor(recipient: String): this(recipient, null, false) - constructor(recipient: String, messageBody: String?, friendRequest: Boolean) : this(Parameters.Builder() + constructor(message: BackgroundMessage) : this(Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setQueue(KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(1) .build(), - recipient, messageBody, friendRequest) + message) override fun serialize(): Data { return Data.Builder() - .putString(KEY_RECIPIENT, recipient) - .putString(KEY_MESSAGE_BODY, messageBody) - .putBoolean(KEY_FRIEND_REQUEST, friendRequest) + .putString(KEY_MESSAGE, message.serialize()) .build() } @@ -52,22 +71,24 @@ class PushBackgroundMessageSendJob private constructor( } public override fun onRun() { - val message = SignalServiceDataMessage.newBuilder() + val dataMessage = SignalServiceDataMessage.newBuilder() .withTimestamp(System.currentTimeMillis()) - .withBody(messageBody) + .withBody(message.body) - if (friendRequest) { - val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(recipient) - message.withPreKeyBundle(bundle) + if (message.friendRequest) { + val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(message.recipient) + dataMessage.withPreKeyBundle(bundle) .asFriendRequest(true) + } else if (message.unpairingRequest) { + dataMessage.asUnpairingRequest(true) } val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() - val address = SignalServiceAddress(recipient) + val address = SignalServiceAddress(message.recipient) try { - messageSender.sendMessage(-1, address, Optional.absent(), message.build()) // The message ID doesn't matter + messageSender.sendMessage(-1, address, Optional.absent(), dataMessage.build()) // The message ID doesn't matter } catch (e: Exception) { - Log.d("Loki", "Failed to send background message to: $recipient.") + Log.d("Loki", "Failed to send background message to: ${message.recipient}.") throw e } } @@ -82,10 +103,8 @@ class PushBackgroundMessageSendJob private constructor( class Factory : Job.Factory { override fun create(parameters: Parameters, data: Data): PushBackgroundMessageSendJob { try { - val recipient = data.getString(KEY_RECIPIENT) - val messageBody = if (data.hasString(KEY_MESSAGE_BODY)) data.getString(KEY_MESSAGE_BODY) else null - val friendRequest = data.getBooleanOrDefault(KEY_FRIEND_REQUEST, false) - return PushBackgroundMessageSendJob(parameters, recipient, messageBody, friendRequest) + val messageJSON = data.getString(KEY_MESSAGE) + return PushBackgroundMessageSendJob(parameters, BackgroundMessage.parse(messageJSON)) } catch (e: IOException) { throw AssertionError(e) } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index de240c81e4..c60bb40231 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -17,6 +17,7 @@ package org.thoughtcrime.securesms.sms; import android.content.Context; +import android.os.AsyncTask; import android.support.annotation.NonNull; import org.thoughtcrime.securesms.ApplicationContext; @@ -44,6 +45,7 @@ import org.thoughtcrime.securesms.jobs.SmsSendJob; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.loki.BackgroundMessage; import org.thoughtcrime.securesms.loki.FriendRequestHandler; import org.thoughtcrime.securesms.loki.GeneralUtilitiesKt; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; @@ -58,7 +60,11 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.push.ContactTokenDetails; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.utilities.PromiseUtil; @@ -123,11 +129,15 @@ public class MessageSender { // We don't call the message sender here directly and instead we just opt to create a specific job for the send // This is because calling message sender directly would cause the application to freeze in some cases as it was blocking the thread when waiting for a response from the send public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) { - ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(contactHexEncodedPublicKey)); + ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.create(contactHexEncodedPublicKey))); } public static void sendBackgroundFriendRequest(Context context, String contactHexEncodedPublicKey, String messageBody) { - ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(contactHexEncodedPublicKey, messageBody, true)); + ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createFriendRequest(contactHexEncodedPublicKey, messageBody))); + } + + public static void sendUnpairRequest(Context context, String contactHexEncodedPublicKey) { + ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createUnpairingRequest(contactHexEncodedPublicKey))); } // endregion