Clean up any invalid group V1 ids in database.

This commit is contained in:
Alan Evans
2020-11-25 14:21:33 -04:00
parent e1bf23251f
commit 6cbd68fe9f
6 changed files with 71 additions and 20 deletions

View File

@@ -1107,7 +1107,7 @@ public class ThreadDatabase extends Database {
pinnedRecipient = Recipient.externalPush(context, pinned.getContact().get()); pinnedRecipient = Recipient.externalPush(context, pinned.getContact().get());
} else if (pinned.getGroupV1Id().isPresent()) { } else if (pinned.getGroupV1Id().isPresent()) {
try { try {
pinnedRecipient = Recipient.externalGroupExact(context, GroupId.v1Exact(pinned.getGroupV1Id().get())); pinnedRecipient = Recipient.externalGroupExact(context, GroupId.v1(pinned.getGroupV1Id().get()));
} catch (BadGroupIdException e) { } catch (BadGroupIdException e) {
Log.w(TAG, "Failed to parse pinned groupV1 ID!", e); Log.w(TAG, "Failed to parse pinned groupV1 ID!", e);
pinnedRecipient = null; pinnedRecipient = null;

View File

@@ -22,12 +22,6 @@ import net.sqlcipher.database.SQLiteDatabaseHook;
import net.sqlcipher.database.SQLiteOpenHelper; import net.sqlcipher.database.SQLiteOpenHelper;
import org.thoughtcrime.securesms.contacts.avatars.ContactColorsLegacy; import org.thoughtcrime.securesms.contacts.avatars.ContactColorsLegacy;
import org.thoughtcrime.securesms.database.MentionDatabase;
import org.thoughtcrime.securesms.database.RemappedRecordsDatabase;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.crypto.DatabaseSecret; import org.thoughtcrime.securesms.crypto.DatabaseSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase;
@@ -38,10 +32,12 @@ import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.JobDatabase; import org.thoughtcrime.securesms.database.JobDatabase;
import org.thoughtcrime.securesms.database.KeyValueDatabase; import org.thoughtcrime.securesms.database.KeyValueDatabase;
import org.thoughtcrime.securesms.database.MegaphoneDatabase; import org.thoughtcrime.securesms.database.MegaphoneDatabase;
import org.thoughtcrime.securesms.database.MentionDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase; import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RemappedRecordsDatabase;
import org.thoughtcrime.securesms.database.SearchDatabase; import org.thoughtcrime.securesms.database.SearchDatabase;
import org.thoughtcrime.securesms.database.SessionDatabase; import org.thoughtcrime.securesms.database.SessionDatabase;
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase; import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
@@ -55,10 +51,15 @@ import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.CursorUtil; import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.FileUtils; import org.thoughtcrime.securesms.util.FileUtils;
import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SqlUtil; import org.thoughtcrime.securesms.util.SqlUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -72,6 +73,7 @@ import java.io.IOException;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set; import java.util.Set;
public class SQLCipherOpenHelper extends SQLiteOpenHelper { public class SQLCipherOpenHelper extends SQLiteOpenHelper {
@@ -161,8 +163,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int NOTIFIED_TIMESTAMP = 81; private static final int NOTIFIED_TIMESTAMP = 81;
private static final int GV1_MIGRATION_LAST_SEEN = 82; private static final int GV1_MIGRATION_LAST_SEEN = 82;
private static final int VIEWED_RECEIPTS = 83; private static final int VIEWED_RECEIPTS = 83;
private static final int CLEAN_UP_GV1_IDS = 84;
private static final int DATABASE_VERSION = 83; private static final int DATABASE_VERSION = 84;
private static final String DATABASE_NAME = "signal.db"; private static final String DATABASE_NAME = "signal.db";
private final Context context; private final Context context;
@@ -1175,6 +1178,62 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE mms ADD COLUMN viewed_receipt_count INTEGER DEFAULT 0"); db.execSQL("ALTER TABLE mms ADD COLUMN viewed_receipt_count INTEGER DEFAULT 0");
} }
if (oldVersion < CLEAN_UP_GV1_IDS) {
List<String> deletableRecipients = new LinkedList<>();
try (Cursor cursor = db.rawQuery("SELECT _id, group_id FROM recipient\n" +
"WHERE group_id NOT IN (SELECT group_id FROM groups)\n" +
"AND group_id LIKE '__textsecure_group__!%' AND length(group_id) <> 53\n" +
"AND (_id NOT IN (SELECT recipient_ids FROM thread) OR _id IN (SELECT recipient_ids FROM thread WHERE message_count = 0))", null))
{
while (cursor.moveToNext()) {
String recipientId = cursor.getString(cursor.getColumnIndexOrThrow("_id"));
String groupIdV1 = cursor.getString(cursor.getColumnIndexOrThrow("group_id"));
deletableRecipients.add(recipientId);
Log.d(TAG, String.format(Locale.US, "Found invalid GV1 on %s with no or empty thread %s length %d", recipientId, groupIdV1, groupIdV1.length()));
}
}
for (String recipientId : deletableRecipients) {
db.delete("recipient", "_id = ?", new String[]{recipientId});
Log.d(TAG, "Deleted recipient " + recipientId);
}
List<String> orphanedThreads = new LinkedList<>();
try (Cursor cursor = db.rawQuery("SELECT _id FROM thread WHERE message_count = 0 AND recipient_ids NOT IN (SELECT _id FROM recipient)", null)) {
while (cursor.moveToNext()) {
orphanedThreads.add(cursor.getString(cursor.getColumnIndexOrThrow("_id")));
}
}
for (String orphanedThreadId : orphanedThreads) {
db.delete("thread", "_id = ?", new String[]{orphanedThreadId});
Log.d(TAG, "Deleted orphaned thread " + orphanedThreadId);
}
List<String> remainingInvalidGV1Recipients = new LinkedList<>();
try (Cursor cursor = db.rawQuery("SELECT _id, group_id FROM recipient\n" +
"WHERE group_id NOT IN (SELECT group_id FROM groups)\n" +
"AND group_id LIKE '__textsecure_group__!%' AND length(group_id) <> 53\n" +
"AND _id IN (SELECT recipient_ids FROM thread)", null))
{
while (cursor.moveToNext()) {
String recipientId = cursor.getString(cursor.getColumnIndexOrThrow("_id"));
String groupIdV1 = cursor.getString(cursor.getColumnIndexOrThrow("group_id"));
remainingInvalidGV1Recipients.add(recipientId);
Log.d(TAG, String.format(Locale.US, "Found invalid GV1 on %s with non-empty thread %s length %d", recipientId, groupIdV1, groupIdV1.length()));
}
}
for (String recipientId : remainingInvalidGV1Recipients) {
String newId = "__textsecure_group__!" + Hex.toStringCondensed(Util.getSecretBytes(16));
ContentValues values = new ContentValues(1);
values.put("group_id", newId);
db.update("recipient", values, "_id = ?", new String[] { String.valueOf(recipientId) });
Log.d(TAG, String.format("Replaced group id on recipient %s now %s", recipientId, newId));
}
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

View File

@@ -43,13 +43,6 @@ public abstract class GroupId {
} }
public static @NonNull GroupId.V1 v1(byte[] gv1GroupIdBytes) throws BadGroupIdException { public static @NonNull GroupId.V1 v1(byte[] gv1GroupIdBytes) throws BadGroupIdException {
if (gv1GroupIdBytes.length == V2_BYTE_LENGTH) {
throw new BadGroupIdException();
}
return new GroupId.V1(gv1GroupIdBytes);
}
public static @NonNull GroupId.V1 v1Exact(byte[] gv1GroupIdBytes) throws BadGroupIdException {
if (gv1GroupIdBytes.length != V1_BYTE_LENGTH) { if (gv1GroupIdBytes.length != V1_BYTE_LENGTH) {
throw new BadGroupIdException(); throw new BadGroupIdException();
} }

View File

@@ -35,7 +35,7 @@ final class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger<Si
return Stream.of(remoteRecords) return Stream.of(remoteRecords)
.filter(record -> { .filter(record -> {
try { try {
GroupId.V1 id = GroupId.v1Exact(record.getGroupId()); GroupId.V1 id = GroupId.v1(record.getGroupId());
return groupExistenceChecker.exists(id.deriveV2MigrationGroupId()); return groupExistenceChecker.exists(id.deriveV2MigrationGroupId());
} catch (BadGroupIdException e) { } catch (BadGroupIdException e) {
return true; return true;

View File

@@ -25,7 +25,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext; import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
import java.io.IOException; import java.io.IOException;
@@ -46,7 +45,7 @@ public final class GroupUtil {
throws BadGroupIdException throws BadGroupIdException
{ {
if (groupContext.getGroupV1().isPresent()) { if (groupContext.getGroupV1().isPresent()) {
return GroupId.v1Exact(groupContext.getGroupV1().get().getGroupId()); return GroupId.v1(groupContext.getGroupV1().get().getGroupId());
} else if (groupContext.getGroupV2().isPresent()) { } else if (groupContext.getGroupV2().isPresent()) {
return GroupId.v2(groupContext.getGroupV2().get().getMasterKey()); return GroupId.v2(groupContext.getGroupV2().get().getMasterKey());
} else { } else {

View File

@@ -273,7 +273,7 @@ public final class GroupIdTest {
@Test(expected = BadGroupIdException.class) @Test(expected = BadGroupIdException.class)
public void cannot_create_v1_with_wrong_length() throws IOException, BadGroupIdException { public void cannot_create_v1_with_wrong_length() throws IOException, BadGroupIdException {
GroupId.v1Exact(Hex.fromStringCondensed("000102030405060708090a0b0c0d0e")); GroupId.v1(Hex.fromStringCondensed("000102030405060708090a0b0c0d0e"));
} }
@Test @Test
@@ -302,7 +302,7 @@ public final class GroupIdTest {
@Test @Test
public void v1Exact_static_factory() throws BadGroupIdException { public void v1Exact_static_factory() throws BadGroupIdException {
GroupId.V1 v1 = GroupId.v1Exact(new byte[]{ 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8 }); GroupId.V1 v1 = GroupId.v1(new byte[]{9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8});
assertEquals("__textsecure_group__!090a0b0c0d0e0f000102030405060708", v1.toString()); assertEquals("__textsecure_group__!090a0b0c0d0e0f000102030405060708", v1.toString());
assertTrue(v1.isV1()); assertTrue(v1.isV1());