From c6be42788394ac54837aa183782c585bb5bacbec Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 11 Aug 2022 13:37:37 -0400 Subject: [PATCH] Add support for resending badly-encrypted stories. --- .../database/DistributionListDatabase.kt | 24 ++++- .../securesms/jobmanager/JobManager.java | 2 +- ...DistributionSendJobRecipientMigration.java | 67 +++++++++++++ .../securesms/jobs/JobManagerFactories.java | 4 +- .../securesms/jobs/ResendMessageJob.java | 29 ++++-- .../jobs/SenderKeyDistributionSendJob.java | 94 ++++++++++++------- .../securesms/messages/GroupSendUtil.java | 2 +- .../messages/MessageContentProcessor.java | 41 ++++---- ...ributionSendJobRecipientMigrationTest.java | 85 +++++++++++++++++ .../api/SignalServiceMessageSender.java | 2 +- .../api/messages/SignalServiceContent.java | 9 +- 11 files changed, 288 insertions(+), 71 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/migrations/SenderKeyDistributionSendJobRecipientMigration.java create mode 100644 app/src/test/java/org/thoughtcrime/securesms/jobmanager/migrations/SenderKeyDistributionSendJobRecipientMigrationTest.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListDatabase.kt index de2309b3c0..daf547d10e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListDatabase.kt @@ -320,7 +320,19 @@ class DistributionListDatabase constructor(context: Context?, databaseHelper: Si } fun getList(listId: DistributionListId): DistributionListRecord? { - readableDatabase.query(ListTable.TABLE_NAME, null, "${ListTable.ID} = ? AND ${ListTable.IS_NOT_DELETED}", SqlUtil.buildArgs(listId), null, null, null).use { cursor -> + return getListByQuery("${ListTable.ID} = ? AND ${ListTable.IS_NOT_DELETED}", SqlUtil.buildArgs(listId)) + } + + fun getList(recipientId: RecipientId): DistributionListRecord? { + return getListByQuery("${ListTable.RECIPIENT_ID} = ? AND ${ListTable.IS_NOT_DELETED}", SqlUtil.buildArgs(recipientId)) + } + + fun getListByDistributionId(distributionId: DistributionId): DistributionListRecord? { + return getListByQuery("${ListTable.DISTRIBUTION_ID} = ? AND ${ListTable.IS_NOT_DELETED}", SqlUtil.buildArgs(distributionId)) + } + + private fun getListByQuery(query: String, args: Array): DistributionListRecord? { + readableDatabase.query(ListTable.TABLE_NAME, null, query, args, null, null, null).use { cursor -> return if (cursor.moveToFirst()) { val id: DistributionListId = DistributionListId.from(cursor.requireLong(ListTable.ID)) val privacyMode: DistributionListPrivacyMode = cursor.requireObject(ListTable.PRIVACY_MODE, DistributionListPrivacyMode.Serializer) @@ -393,6 +405,16 @@ class DistributionListDatabase constructor(context: Context?, databaseHelper: Si } } + fun getDistributionId(recipientId: RecipientId): DistributionId? { + readableDatabase.query(ListTable.TABLE_NAME, arrayOf(ListTable.DISTRIBUTION_ID), "${ListTable.RECIPIENT_ID} = ? AND ${ListTable.IS_NOT_DELETED}", SqlUtil.buildArgs(recipientId), null, null, null).use { cursor -> + return if (cursor.moveToFirst()) { + DistributionId.from(cursor.requireString(ListTable.DISTRIBUTION_ID)) + } else { + null + } + } + } + fun getMembers(listId: DistributionListId): List { lateinit var privacyMode: DistributionListPrivacyMode lateinit var rawMembers: List diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java index c77e7164fc..28994a00f4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java @@ -44,7 +44,7 @@ public class JobManager implements ConstraintObserver.Notifier { private static final String TAG = Log.tag(JobManager.class); - public static final int CURRENT_VERSION = 8; + public static final int CURRENT_VERSION = 9; private final Application application; private final Configuration configuration; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/migrations/SenderKeyDistributionSendJobRecipientMigration.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/migrations/SenderKeyDistributionSendJobRecipientMigration.java new file mode 100644 index 0000000000..fb56d0e71f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/migrations/SenderKeyDistributionSendJobRecipientMigration.java @@ -0,0 +1,67 @@ +package org.thoughtcrime.securesms.jobmanager.migrations; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; +import org.thoughtcrime.securesms.database.SignalDatabase; +import org.thoughtcrime.securesms.groups.GroupId; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.JobMigration; +import org.thoughtcrime.securesms.jobs.FailingJob; + +import java.util.Optional; + +/** + * We removed the messageId property from the job data and replaced it with a serialized envelope, + * so we need to take jobs that referenced an ID and replace it with the envelope instead. + */ +public class SenderKeyDistributionSendJobRecipientMigration extends JobMigration { + + private static final String TAG = Log.tag(SenderKeyDistributionSendJobRecipientMigration.class); + + private final GroupDatabase groupDatabase; + + public SenderKeyDistributionSendJobRecipientMigration() { + this(SignalDatabase.groups()); + } + + @VisibleForTesting + SenderKeyDistributionSendJobRecipientMigration(GroupDatabase groupDatabase) { + super(9); + this.groupDatabase = groupDatabase; + } + + @Override + protected @NonNull JobData migrate(@NonNull JobData jobData) { + if ("SenderKeyDistributionSendJob".equals(jobData.getFactoryKey())) { + return migrateJob(jobData, groupDatabase); + } else { + return jobData; + } + } + + private static @NonNull JobData migrateJob(@NonNull JobData jobData, @NonNull GroupDatabase groupDatabase) { + Data data = jobData.getData(); + + if (data.hasString("group_id")) { + GroupId groupId = GroupId.pushOrThrow(data.getStringAsBlob("group_id")); + Optional group = groupDatabase.getGroup(groupId); + + if (group.isPresent()) { + return jobData.withData(data.buildUpon() + .putString("thread_recipient_id", group.get().getRecipientId().serialize()) + .build()); + + } else { + return jobData.withFactoryKey(FailingJob.KEY); + } + } else if (!data.hasString("thread_recipient_id")) { + return jobData.withFactoryKey(FailingJob.KEY); + } else { + return jobData; + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index c5898f2156..76a27773af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMi import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdJobMigration; import org.thoughtcrime.securesms.jobmanager.migrations.RetrieveProfileJobMigration; import org.thoughtcrime.securesms.jobmanager.migrations.SendReadReceiptsJobMigration; +import org.thoughtcrime.securesms.jobmanager.migrations.SenderKeyDistributionSendJobRecipientMigration; import org.thoughtcrime.securesms.migrations.AccountRecordMigrationJob; import org.thoughtcrime.securesms.migrations.ApplyUnknownFieldsToSelfMigrationJob; import org.thoughtcrime.securesms.migrations.AttachmentCleanupMigrationJob; @@ -272,6 +273,7 @@ public final class JobManagerFactories { new SendReadReceiptsJobMigration(SignalDatabase.mmsSms()), new PushProcessMessageQueueJobMigration(application), new RetrieveProfileJobMigration(), - new PushDecryptMessageJobEnvelopeMigration(application)); + new PushDecryptMessageJobEnvelopeMigration(application), + new SenderKeyDistributionSendJobRecipientMigration()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java index d52a2de6fb..a8bf883f78 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java @@ -13,6 +13,7 @@ import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.SignalDatabase; +import org.thoughtcrime.securesms.database.model.DistributionListRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; @@ -143,14 +144,28 @@ public class ResendMessageJob extends BaseJob { Content contentToSend = content; if (distributionId != null) { - Optional groupRecord = SignalDatabase.groups().getGroupByDistributionId(distributionId); + if (groupId != null) { + Log.d(TAG, "GroupId is present. Assuming this is a group message."); + Optional groupRecord = SignalDatabase.groups().getGroupByDistributionId(distributionId); - if (!groupRecord.isPresent()) { - Log.w(TAG, "Could not find a matching group for the distributionId! Skipping message send."); - return; - } else if (!groupRecord.get().getMembers().contains(recipientId)) { - Log.w(TAG, "The target user is no longer in the group! Skipping message send."); - return; + if (!groupRecord.isPresent()) { + Log.w(TAG, "Could not find a matching group for the distributionId! Skipping message send."); + return; + } else if (!groupRecord.get().getMembers().contains(recipientId)) { + Log.w(TAG, "The target user is no longer in the group! Skipping message send."); + return; + } + } else { + Log.d(TAG, "GroupId is not present. Assuming this is a message for a distribution list."); + DistributionListRecord listRecord = SignalDatabase.distributionLists().getListByDistributionId(distributionId); + + if (listRecord == null) { + Log.w(TAG, "Could not find a matching distribution list for the distributionId! Skipping message send."); + return; + } else if (!listRecord.getMembers().contains(recipientId)) { + Log.w(TAG, "The target user is no longer in the distribution list! Skipping message send."); + return; + } } SenderKeyDistributionMessage senderKeyDistributionMessage = messageSender.getOrCreateNewGroupSession(distributionId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java index 35893d3f8d..55a3291003 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java @@ -6,8 +6,8 @@ import org.signal.core.util.logging.Log; import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; -import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.SignalDatabase; +import org.thoughtcrime.securesms.database.model.DistributionListRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; @@ -39,33 +39,33 @@ public final class SenderKeyDistributionSendJob extends BaseJob { public static final String KEY = "SenderKeyDistributionSendJob"; - private static final String KEY_RECIPIENT_ID = "recipient_id"; - private static final String KEY_GROUP_ID = "group_id"; + private static final String KEY_TARGET_RECIPIENT_ID = "recipient_id"; + private static final String KEY_THREAD_RECIPIENT_ID = "thread_recipient_id"; - private final RecipientId recipientId; - private final GroupId.V2 groupId; + private final RecipientId targetRecipientId; + private final RecipientId threadRecipientId; - public SenderKeyDistributionSendJob(@NonNull RecipientId recipientId, @NonNull GroupId.V2 groupId) { - this(recipientId, groupId, new Parameters.Builder() - .setQueue(recipientId.toQueueKey()) - .addConstraint(NetworkConstraint.KEY) - .setLifespan(TimeUnit.DAYS.toMillis(1)) - .setMaxAttempts(Parameters.UNLIMITED) - .setMaxInstancesForQueue(1) - .build()); + public SenderKeyDistributionSendJob(@NonNull RecipientId targetRecipientId, RecipientId threadRecipientId) { + this(targetRecipientId, threadRecipientId, new Parameters.Builder() + .setQueue(targetRecipientId.toQueueKey()) + .addConstraint(NetworkConstraint.KEY) + .setLifespan(TimeUnit.DAYS.toMillis(1)) + .setMaxAttempts(Parameters.UNLIMITED) + .setMaxInstancesForQueue(1) + .build()); } - private SenderKeyDistributionSendJob(@NonNull RecipientId recipientId, @NonNull GroupId.V2 groupId, @NonNull Parameters parameters) { + private SenderKeyDistributionSendJob(@NonNull RecipientId targetRecipientId, @NonNull RecipientId threadRecipientId, @NonNull Parameters parameters) { super(parameters); - this.recipientId = recipientId; - this.groupId = groupId; + this.targetRecipientId = targetRecipientId; + this.threadRecipientId = threadRecipientId; } @Override public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_RECIPIENT_ID, recipientId.serialize()) - .putBlobAsString(KEY_GROUP_ID, groupId.getDecodedId()) + return new Data.Builder().putString(KEY_TARGET_RECIPIENT_ID, targetRecipientId.serialize()) + .putString(KEY_THREAD_RECIPIENT_ID, threadRecipientId.serialize()) .build(); } @@ -76,38 +76,62 @@ public final class SenderKeyDistributionSendJob extends BaseJob { @Override protected void onRun() throws Exception { - GroupDatabase groupDatabase = SignalDatabase.groups(); + Recipient targetRecipient = Recipient.resolved(targetRecipientId); + Recipient threadRecipient = Recipient.resolved(threadRecipientId); - if (!groupDatabase.isCurrentMember(groupId, recipientId)) { - Log.w(TAG, recipientId + " is no longer a member of " + groupId + "! Not sending."); + if (targetRecipient.getSenderKeyCapability() != Recipient.Capability.SUPPORTED) { + Log.w(TAG, targetRecipientId + " does not support sender key! Not sending."); return; } - Recipient recipient = Recipient.resolved(recipientId); - - if (recipient.getSenderKeyCapability() != Recipient.Capability.SUPPORTED) { - Log.w(TAG, recipientId + " does not support sender key! Not sending."); + if (targetRecipient.isUnregistered()) { + Log.w(TAG, threadRecipient.getId() + " not registered!"); return; } - if (recipient.isUnregistered()) { - Log.w(TAG, recipient.getId() + " not registered!"); + GroupId.V2 groupId; + DistributionId distributionId; + + if (threadRecipient.isPushV2Group()) { + groupId = threadRecipient.requireGroupId().requireV2(); + distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId); + } else if (threadRecipient.isDistributionList()) { + groupId = null; + distributionId = SignalDatabase.distributionLists().getDistributionId(threadRecipientId); + } else { + warn(TAG, "Recipient is not a group or distribution list! Skipping."); return; } + if (distributionId == null) { + warn(TAG, "Failed to find a distributionId! Skipping."); + return; + } + + if (groupId != null && !SignalDatabase.groups().isCurrentMember(groupId, targetRecipientId)) { + Log.w(TAG, targetRecipientId + " is no longer a member of " + groupId + "! Not sending."); + return; + } else if (groupId == null) { + DistributionListRecord listRecord = SignalDatabase.distributionLists().getList(threadRecipientId); + + if (listRecord == null || !listRecord.getMembers().contains(targetRecipientId)) { + Log.w(TAG, targetRecipientId + " is no longer a member of the distribution list! Not sending."); + return; + } + } + SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - List address = Collections.singletonList(RecipientUtil.toSignalServiceAddress(context, recipient)); - DistributionId distributionId = groupDatabase.getOrCreateDistributionId(groupId); - SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId); - List> access = UnidentifiedAccessUtil.getAccessFor(context, Collections.singletonList(recipient)); + List address = Collections.singletonList(RecipientUtil.toSignalServiceAddress(context, targetRecipient)); + SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId); + List> access = UnidentifiedAccessUtil.getAccessFor(context, Collections.singletonList(targetRecipient)); - SendMessageResult result = messageSender.sendSenderKeyDistributionMessage(distributionId, address, access, message, Optional.of(groupId.getDecodedId()), false).get(0); + SendMessageResult result = messageSender.sendSenderKeyDistributionMessage(distributionId, address, access, message, Optional.ofNullable(groupId).map(GroupId::getDecodedId), false).get(0); if (result.isSuccess()) { List addresses = result.getSuccess() .getDevices() .stream() - .map(device -> recipient.requireServiceId().toProtocolAddress(device)) + .map(device -> targetRecipient.requireServiceId().toProtocolAddress(device)) .collect(Collectors.toList()); ApplicationDependencies.getProtocolStore().aci().markSenderKeySharedWith(distributionId, addresses); @@ -128,8 +152,8 @@ public final class SenderKeyDistributionSendJob extends BaseJob { @Override public @NonNull SenderKeyDistributionSendJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new SenderKeyDistributionSendJob(RecipientId.from(data.getString(KEY_RECIPIENT_ID)), - GroupId.pushOrThrow(data.getStringAsBlob(KEY_GROUP_ID)).requireV2(), + return new SenderKeyDistributionSendJob(RecipientId.from(data.getString(KEY_TARGET_RECIPIENT_ID)), + RecipientId.from(data.getString(KEY_THREAD_RECIPIENT_ID)), parameters); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java index a1f09acf39..a072475445 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java @@ -673,7 +673,7 @@ public final class GroupSendUtil { @Override public @NonNull ContentHint getContentHint() { - return ContentHint.RESENDABLE; + return ContentHint.IMPLICIT; } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index db84a6473d..05ebe2ae54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.database.SentStorySyncManifest; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.StickerDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; +import org.thoughtcrime.securesms.database.model.DistributionListRecord; import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageLogEntry; @@ -2683,13 +2684,27 @@ public final class MessageContentProcessor { return; } - if (!threadRecipient.isPushV2Group()) { - warn(content.getTimestamp(), "[RetryReceipt-SK] Thread recipient is not a v2 group! Skipping."); + if (!threadRecipient.isPushV2Group() && !threadRecipient.isDistributionList()) { + warn(content.getTimestamp(), "[RetryReceipt-SK] Thread recipient is not a V2 group or distribution list! Skipping."); + return; + } + + DistributionId distributionId; + GroupId.V2 groupId; + + if (threadRecipient.isGroup()) { + groupId = threadRecipient.requireGroupId().requireV2(); + distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId); + } else { + groupId = null; + distributionId = SignalDatabase.distributionLists().getDistributionId(threadRecipient.getId()); + } + + if (distributionId == null) { + Log.w(TAG, "[RetryReceipt-SK] Failed to find a distributionId! Skipping."); return; } - GroupId.V2 groupId = threadRecipient.requireGroupId().requireV2(); - DistributionId distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId); SignalProtocolAddress requesterAddress = new SignalProtocolAddress(requester.requireServiceId().toString(), content.getSenderDevice()); SignalDatabase.senderKeyShared().delete(distributionId, Collections.singleton(requesterAddress)); @@ -2705,22 +2720,8 @@ public final class MessageContentProcessor { groupId, distributionId)); } else { - warn(content.getTimestamp(), "[RetryReceipt-SK] Unable to find MSL entry for " + requester.getId() + " (" + requesterAddress + ") with timestamp " + sentTimestamp + "."); - - Optional groupRecord = SignalDatabase.groups().getGroup(groupId); - - if (!groupRecord.isPresent()) { - warn(content.getTimestamp(), "[RetryReceipt-SK] Could not find a record for the group!"); - return; - } - - if (!groupRecord.get().getMembers().contains(requester.getId())) { - warn(content.getTimestamp(), "[RetryReceipt-SK] The requester is not in the group, so we cannot send them a SenderKeyDistributionMessage."); - return; - } - - warn(content.getTimestamp(), "[RetryReceipt-SK] The requester is in the group, so we'll send them a SenderKeyDistributionMessage."); - ApplicationDependencies.getJobManager().add(new SenderKeyDistributionSendJob(requester.getId(), groupRecord.get().getId().requireV2())); + warn(content.getTimestamp(), "[RetryReceipt-SK] Unable to find MSL entry for " + requester.getId() + " (" + requesterAddress + ") with timestamp " + sentTimestamp + " for " + (groupId != null ? "group " + groupId : "distribution list") + ". Scheduling a job to send them the SenderKeyDistributionMessage. Membership will be checked there."); + ApplicationDependencies.getJobManager().add(new SenderKeyDistributionSendJob(requester.getId(), threadRecipient.getId())); } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobmanager/migrations/SenderKeyDistributionSendJobRecipientMigrationTest.java b/app/src/test/java/org/thoughtcrime/securesms/jobmanager/migrations/SenderKeyDistributionSendJobRecipientMigrationTest.java new file mode 100644 index 0000000000..bfe0738358 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/jobmanager/migrations/SenderKeyDistributionSendJobRecipientMigrationTest.java @@ -0,0 +1,85 @@ +package org.thoughtcrime.securesms.jobmanager.migrations; + +import org.junit.Test; +import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.database.MmsSmsDatabase; +import org.thoughtcrime.securesms.groups.GroupId; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.JobMigration; +import org.thoughtcrime.securesms.jobs.FailingJob; +import org.thoughtcrime.securesms.jobs.SendReadReceiptJob; +import org.thoughtcrime.securesms.jobs.SenderKeyDistributionSendJob; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.util.Util; + +import java.util.ArrayList; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SenderKeyDistributionSendJobRecipientMigrationTest { + + private final GroupDatabase mockDatabase = mock(GroupDatabase.class); + private final SenderKeyDistributionSendJobRecipientMigration testSubject = new SenderKeyDistributionSendJobRecipientMigration(mockDatabase); + + private static final GroupId GROUP_ID = GroupId.pushOrThrow(Util.getSecretBytes(32)); + + @Test + public void normalMigration() { + // GIVEN + JobMigration.JobData jobData = new JobMigration.JobData(SenderKeyDistributionSendJob.KEY, + "asdf", + new Data.Builder() + .putString("recipient_id", RecipientId.from(1).serialize()) + .putBlobAsString("group_id", GROUP_ID.getDecodedId()) + .build()); + + GroupDatabase.GroupRecord mockGroup = mock(GroupDatabase.GroupRecord.class); + when(mockGroup.getRecipientId()).thenReturn(RecipientId.from(2)); + when(mockDatabase.getGroup(GROUP_ID)).thenReturn(Optional.of(mockGroup)); + + // WHEN + JobMigration.JobData result = testSubject.migrate(jobData); + + // THEN + assertEquals(RecipientId.from(1).serialize(), result.getData().getString("recipient_id")); + assertEquals(RecipientId.from(2).serialize(), result.getData().getString("thread_recipient_id")); + } + + @Test + public void cannotFindGroup() { + // GIVEN + JobMigration.JobData jobData = new JobMigration.JobData(SenderKeyDistributionSendJob.KEY, + "asdf", + new Data.Builder() + .putString("recipient_id", RecipientId.from(1).serialize()) + .putBlobAsString("group_id", GROUP_ID.getDecodedId()) + .build()); + + // WHEN + JobMigration.JobData result = testSubject.migrate(jobData); + + // THEN + assertEquals(FailingJob.KEY, result.getFactoryKey()); + } + + @Test + public void missingGroupId() { + // GIVEN + JobMigration.JobData jobData = new JobMigration.JobData(SenderKeyDistributionSendJob.KEY, + "asdf", + new Data.Builder() + .putString("recipient_id", RecipientId.from(1).serialize()) + .build()); + + // WHEN + JobMigration.JobData result = testSubject.migrate(jobData); + + // THEN + assertEquals(FailingJob.KEY, result.getFactoryKey()); + } +} \ No newline at end of file diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index 9289166adf..e992423b92 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -1745,7 +1745,7 @@ public class SignalServiceMessageSender { if (content.getContent().isPresent() && content.getContent().get().getSyncMessage() != null && content.getContent().get().getSyncMessage().hasSent()) { Log.d(TAG, "[sendMessage][" + timestamp + "] Sending a sent sync message to devices: " + messages.getDevices()); } else if (content.getContent().isPresent() && content.getContent().get().hasSenderKeyDistributionMessage()) { - Log.d(TAG, "[sendMessage][" + timestamp + "] Sending a SKDM to " + messages.getDestination() + " for devices: " + messages.getDevices()); + Log.d(TAG, "[sendMessage][" + timestamp + "] Sending a SKDM to " + messages.getDestination() + " for devices: " + messages.getDevices() + (content.getContent().get().getDataMessage() != null ? " (it's piggy-backing on a DataMessage)" : "")); } if (cancelationSignal != null && cancelationSignal.isCanceled()) { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java index 13de8ab5a9..215143cf3d 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java @@ -560,8 +560,8 @@ public final class SignalServiceContent { metadata.getGroupId(), metadata.getDestinationUuid(), serviceContentProto); - } else if (senderKeyDistributionMessage.isPresent()) { - return new SignalServiceContent(senderKeyDistributionMessage.get(), + } else if (message.hasStoryMessage()) { + return new SignalServiceContent(createStoryMessage(message.getStoryMessage()), metadata.getSender(), metadata.getSenderDevice(), metadata.getTimestamp(), @@ -572,8 +572,9 @@ public final class SignalServiceContent { metadata.getGroupId(), metadata.getDestinationUuid(), serviceContentProto); - } else if (message.hasStoryMessage()) { - return new SignalServiceContent(createStoryMessage(message.getStoryMessage()), + } else if (senderKeyDistributionMessage.isPresent()) { + // IMPORTANT: This block should always be last, since you can pair SKDM's with other content + return new SignalServiceContent(senderKeyDistributionMessage.get(), metadata.getSender(), metadata.getSenderDevice(), metadata.getTimestamp(),