From 05cc6ea742e67499ef8ff8e327745dd0358f3412 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 9 Jul 2021 13:01:16 +1000 Subject: [PATCH] Re-enable copying Session IDs in closed groups & clean --- .../v2/dialogs/JoinOpenGroupDialog.kt | 5 +- .../menus/ConversationActionModeCallback.kt | 3 +- .../database/helpers/SQLCipherOpenHelper.java | 8 - .../groups/JoinPublicChatActivity.kt | 5 +- .../securesms/home/HomeActivity.kt | 4 +- .../loki/dialogs/ClearAllDataDialog.kt | 10 +- .../loki/protocol/ClosedGroupsMigration.kt | 73 ---- .../loki/protocol/ClosedGroupsProtocolV2.kt | 392 ------------------ .../securesms/preferences/SettingsActivity.kt | 4 +- .../ConfigurationMessageUtilities.kt} | 6 +- 10 files changed, 14 insertions(+), 496 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsMigration.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt rename app/src/main/java/org/thoughtcrime/securesms/{loki/protocol/MultiDeviceProtocol.kt => util/ConfigurationMessageUtilities.kt} (92%) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt index 51d85c3651..fba97ed406 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt @@ -9,12 +9,11 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.dialog_join_open_group.view.* import network.loki.messenger.R -import org.session.libsession.messaging.open_groups.OpenGroupV2 import org.session.libsession.utilities.OpenGroupUrlParser import org.session.libsignal.utilities.ThreadUtils import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog import org.thoughtcrime.securesms.loki.api.OpenGroupManager -import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol +import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities /** Shown upon tapping an open group invitation. */ class JoinOpenGroupDialog(private val name: String, private val url: String) : BaseDialog() { @@ -38,7 +37,7 @@ class JoinOpenGroupDialog(private val name: String, private val url: String) : B val activity = requireContext() as AppCompatActivity ThreadUtils.queue { OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, activity) - MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(activity) + ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(activity) } dismiss() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index 42e36e2c78..62f292e325 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -31,6 +31,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p if (selectedItems.isEmpty()) { return } val firstMessage = selectedItems.iterator().next() val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID) + val thread = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID)!! val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! fun userCanDeleteSelectedItems(): Boolean { if (openGroup == null) { return true } @@ -54,7 +55,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p menu.findItem(R.id.menu_context_copy).isVisible = !containsControlMessage && hasText // Copy Session ID menu.findItem(R.id.menu_context_copy_public_key).isVisible = - (openGroup != null && selectedItems.size == 1 && firstMessage.recipient.address.toString() != userPublicKey) + (thread.isGroupRecipient && selectedItems.size == 1 && firstMessage.recipient.address.toString() != userPublicKey) // Resend menu.findItem(R.id.menu_context_resend).isVisible = (selectedItems.size == 1 && firstMessage.isFailed) // Save media diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 349f7a082b..11f85cabf4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -30,7 +30,6 @@ import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; import org.thoughtcrime.securesms.loki.database.SessionContactDatabase; import org.thoughtcrime.securesms.loki.database.SessionJobDatabase; -import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsMigration; public class SQLCipherOpenHelper extends SQLiteOpenHelper { @@ -182,8 +181,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { if (oldVersion < lokiV12) { db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command()); - db.execSQL(ClosedGroupsMigration.getCreateCurrentClosedGroupRatchetTableCommand()); - db.execSQL(ClosedGroupsMigration.getCreateClosedGroupPrivateKeyTableCommand()); } if (oldVersion < lokiV13) { @@ -193,10 +190,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { if (oldVersion < lokiV14_BACKUP_FILES) { db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand()); } - - if (oldVersion < lokiV15) { - db.execSQL(ClosedGroupsMigration.getCreateOldClosedGroupRatchetTableCommand()); - } if (oldVersion < lokiV16) { db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand()); @@ -217,7 +210,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { if (oldVersion < lokiV19) { db.execSQL(LokiAPIDatabase.getCreateClosedGroupEncryptionKeyPairsTable()); db.execSQL(LokiAPIDatabase.getCreateClosedGroupPublicKeysTable()); - ClosedGroupsMigration.INSTANCE.perform(db); db.execSQL("DROP TABLE identities"); deleteJobRecords(db, "RetrieveProfileJob"); deleteJobRecords(db, diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinPublicChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinPublicChatActivity.kt index 4ce8bbde04..ab0bbaaf00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinPublicChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinPublicChatActivity.kt @@ -25,7 +25,6 @@ import network.loki.messenger.R import okhttp3.HttpUrl import org.session.libsession.messaging.open_groups.OpenGroupAPIV2.DefaultGroup import org.session.libsession.utilities.Address -import org.session.libsession.utilities.DistributionTypes import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.GroupUtil import org.session.libsignal.utilities.Log @@ -38,7 +37,7 @@ import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.loki.api.OpenGroupManager import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate -import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol +import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.loki.viewmodel.DefaultGroupsViewModel import org.thoughtcrime.securesms.loki.viewmodel.State import java.util.* @@ -108,7 +107,7 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode } else { throw Exception("No longer supported.") } - MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity) + ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity) withContext(Dispatchers.Main) { val recipient = Recipient.from(this@JoinPublicChatActivity, Address.fromSerialized(groupID), false) openConversationActivity(this@JoinPublicChatActivity, threadID, recipient) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 643f2b98e4..9633cbf73d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -43,7 +43,7 @@ import org.thoughtcrime.securesms.groups.CreateClosedGroupActivity import org.thoughtcrime.securesms.loki.activities.* import org.thoughtcrime.securesms.loki.api.OpenGroupManager import org.thoughtcrime.securesms.loki.dialogs.* -import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol +import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.loki.utilities.* import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate import org.thoughtcrime.securesms.mms.GlideApp @@ -167,7 +167,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis } if (TextSecurePreferences.getConfigurationMessageSynced(this)) { lifecycleScope.launch(Dispatchers.IO) { - MultiDeviceProtocol.syncConfigurationIfNeeded(this@HomeActivity) + ConfigurationMessageUtilities.syncConfigurationIfNeeded(this@HomeActivity) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt index a3037223b3..e1a5271acf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt @@ -1,19 +1,13 @@ package org.thoughtcrime.securesms.loki.dialogs -import android.app.Dialog -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.os.Bundle import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog -import androidx.fragment.app.DialogFragment import kotlinx.android.synthetic.main.dialog_clear_all_data.view.* import network.loki.messenger.R import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.KeyPairUtilities -import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol +import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog -import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities class ClearAllDataDialog : BaseDialog() { @@ -26,7 +20,7 @@ class ClearAllDataDialog : BaseDialog() { private fun clearAllData() { if (KeyPairUtilities.hasV2KeyPair(requireContext())) { - MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(requireContext()) + ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(requireContext()) ApplicationContext.getInstance(context).clearAllData(false) } else { val dialog = AlertDialog.Builder(requireContext()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsMigration.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsMigration.kt deleted file mode 100644 index d5a333d8d0..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsMigration.kt +++ /dev/null @@ -1,73 +0,0 @@ -package org.thoughtcrime.securesms.loki.protocol - -import android.content.ContentValues -import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase -import org.thoughtcrime.securesms.loki.utilities.get -import org.thoughtcrime.securesms.loki.utilities.getAll -import org.thoughtcrime.securesms.loki.utilities.getString -import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate -import org.session.libsignal.utilities.Hex -import org.session.libsignal.crypto.ecc.DjbECPrivateKey -import org.session.libsignal.crypto.ecc.DjbECPublicKey -import org.session.libsignal.crypto.ecc.ECKeyPair -import org.session.libsignal.utilities.PublicKeyValidation -import org.session.libsignal.utilities.removing05PrefixIfNeeded -import org.session.libsignal.utilities.toHexString -import java.util.* - -object ClosedGroupsMigration { - - public val closedGroupPublicKey = "closed_group_public_key" - // Ratchets - private val oldClosedGroupRatchetTable = "old_closed_group_ratchet_table" - private val currentClosedGroupRatchetTable = "closed_group_ratchet_table" - private val senderPublicKey = "sender_public_key" - private val chainKey = "chain_key" - private val keyIndex = "key_index" - private val messageKeys = "message_keys" - @JvmStatic val createOldClosedGroupRatchetTableCommand - = "CREATE TABLE $oldClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " + - "$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));" - // Private keys - @JvmStatic val createCurrentClosedGroupRatchetTableCommand - = "CREATE TABLE $currentClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " + - "$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));" - // Private keys - public val closedGroupPrivateKeyTable = "closed_group_private_key_table" - public val closedGroupPrivateKey = "closed_group_private_key" - @JvmStatic val createClosedGroupPrivateKeyTableCommand - = "CREATE TABLE $closedGroupPrivateKeyTable ($closedGroupPublicKey STRING PRIMARY KEY, $closedGroupPrivateKey STRING);" - - -fun perform(database: net.sqlcipher.database.SQLiteDatabase) { - val publicKeys = database.getAll(closedGroupPrivateKeyTable, null, null) { cursor -> - cursor.getString(closedGroupPublicKey) - }.filter { - PublicKeyValidation.isValid(it) - } - val keyPairs = mutableListOf() - for (publicKey in publicKeys) { - val query = "${closedGroupPublicKey} = ?" - val privateKey = database.get(closedGroupPrivateKeyTable, query, arrayOf( publicKey )) { cursor -> - cursor.getString(closedGroupPrivateKey) - } - val keyPair = ECKeyPair(DjbECPublicKey(Hex.fromStringCondensed(publicKey.removing05PrefixIfNeeded())), DjbECPrivateKey(Hex.fromStringCondensed(privateKey))) - keyPairs.add(keyPair) - val row = ContentValues(1) - row.put(LokiAPIDatabase.groupPublicKey, publicKey) - database.insertOrUpdate(LokiAPIDatabase.closedGroupPublicKeysTable, row, "${LokiAPIDatabase.groupPublicKey} = ?", arrayOf( publicKey )) - } - for (keyPair in keyPairs) { - // In this particular case keyPair.publicKey == groupPublicKey - val timestamp = Date().time.toString() - val index = "${keyPair.publicKey.serialize().toHexString()}-$timestamp" - val encryptionKeyPairPublicKey = keyPair.publicKey.serialize().toHexString().removing05PrefixIfNeeded() - val encryptionKeyPairPrivateKey = keyPair.privateKey.serialize().toHexString() - val row = ContentValues(3) - row.put(LokiAPIDatabase.closedGroupsEncryptionKeyPairIndex, index) - row.put(LokiAPIDatabase.encryptionKeyPairPublicKey, encryptionKeyPairPublicKey) - row.put(LokiAPIDatabase.encryptionKeyPairPrivateKey, encryptionKeyPairPrivateKey) - database.insertOrUpdate(LokiAPIDatabase.closedGroupEncryptionKeyPairsTable, row, "${LokiAPIDatabase.closedGroupsEncryptionKeyPairIndex} = ?", arrayOf( index )) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt deleted file mode 100644 index 1ac34bc1e7..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ /dev/null @@ -1,392 +0,0 @@ -package org.thoughtcrime.securesms.loki.protocol - -import android.content.Context -import android.util.Log -import com.google.protobuf.ByteString -import org.session.libsession.messaging.sending_receiving.* -import org.session.libsignal.crypto.ecc.DjbECPrivateKey -import org.session.libsignal.crypto.ecc.DjbECPublicKey -import org.session.libsignal.crypto.ecc.ECKeyPair -import org.session.libsignal.messages.SignalServiceGroup -import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.protos.SignalServiceProtos.DataMessage -import org.session.libsignal.utilities.removing05PrefixIfNeeded -import org.session.libsignal.utilities.toHexString -import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.database.GroupDatabase -import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager -import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation -import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase - -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.TextSecurePreferences - -import java.util.* - -object ClosedGroupsProtocolV2 { - - @JvmStatic - fun handleMessage(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { - if (!isValid(context, closedGroupUpdate, senderPublicKey, sentTimestamp)) { return } - when (closedGroupUpdate.type) { - DataMessage.ClosedGroupControlMessage.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey, sentTimestamp) - DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED -> handleClosedGroupMembersRemoved(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) - DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED -> handleClosedGroupMembersAdded(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) - DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> handleClosedGroupNameChange(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) - DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> handleClosedGroupMemberLeft(context, sentTimestamp, groupPublicKey, senderPublicKey) - DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> handleGroupEncryptionKeyPair(context, closedGroupUpdate, groupPublicKey, senderPublicKey) - else -> { - Log.d("Loki","Can't handle closed group update of unknown type: ${closedGroupUpdate.type}") - } - } - } - - private fun isValid(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, senderPublicKey: String, sentTimestamp: Long): Boolean { - val record = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(sentTimestamp, senderPublicKey) - if (record != null) return false - - return when (closedGroupUpdate.type) { - DataMessage.ClosedGroupControlMessage.Type.NEW -> { - (!closedGroupUpdate.publicKey.isEmpty && !closedGroupUpdate.name.isNullOrEmpty() && !(closedGroupUpdate.encryptionKeyPair.privateKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty - && !(closedGroupUpdate.encryptionKeyPair.publicKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty && closedGroupUpdate.membersCount > 0 && closedGroupUpdate.adminsCount > 0) - } - DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED, - DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED -> { - closedGroupUpdate.membersCount > 0 - } - DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> { - senderPublicKey.isNotEmpty() - } - DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> { - !closedGroupUpdate.name.isNullOrEmpty() - } - DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> true - else -> false - } - } - - public fun handleNewClosedGroup(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, senderPublicKey: String, sentTimestamp: Long) { - // Prepare - val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! - val apiDB = DatabaseFactory.getLokiAPIDatabase(context) - // Unwrap the message - val groupPublicKey = closedGroupUpdate.publicKey.toByteArray().toHexString() - val name = closedGroupUpdate.name - val encryptionKeyPairAsProto = closedGroupUpdate.encryptionKeyPair - val members = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() } - val admins = closedGroupUpdate.adminsList.map { it.toByteArray().toHexString() } - // Create the group - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val groupDB = DatabaseFactory.getGroupDatabase(context) - val prevGroup = groupDB.getGroup(groupID).orNull() - if (prevGroup != null) { - // Update the group - groupDB.updateTitle(groupID, name) - groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) - } else { - groupDB.create(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }), - null, null, LinkedList(admins.map { Address.fromSerialized(it) }), sentTimestamp) - } - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.from(context, Address.fromSerialized(groupID), false), true) - // Add the group to the user's set of public keys to poll for - apiDB.addClosedGroupPublicKey(groupPublicKey) - // Store the encryption key pair - val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) - apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey) - // Notify the user (if we didn't make the group) - if (userPublicKey != senderPublicKey) { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) - } else if (prevGroup == null) { - // only notify if we created this group - val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp) - } - // Notify the PN server - LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) - } - - fun handleClosedGroupMembersRemoved(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { - val apiDB = DatabaseFactory.getLokiAPIDatabase(context) - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = groupDB.getGroup(groupID).orNull() - if (group == null || !group.isActive) { - Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.") - return - } - val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! - val name = group.title - // Check common group update logic - val members = group.members.map { it.serialize() } - val admins = group.admins.map { it.toString() } - - // Users that are part of this remove update - val updateMembers = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() } - - if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) { - return - } - // If admin leaves the group is disbanded - val didAdminLeave = admins.any { it in updateMembers } - // newMembers to save is old members minus removed members - val newMembers = members - updateMembers - // user should be posting MEMBERS_LEFT so this should not be encountered - val senderLeft = senderPublicKey in updateMembers - if (senderLeft) { - Log.d("Loki", "Received a MEMBERS_REMOVED instead of a MEMBERS_LEFT from sender $senderPublicKey") - } - val wasCurrentUserRemoved = userPublicKey in updateMembers - - // admin should send a MEMBERS_LEFT message but handled here in case - if (didAdminLeave || wasCurrentUserRemoved) { - disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey) - } else { - val isCurrentUserAdmin = admins.contains(userPublicKey) - groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) }) - if (isCurrentUserAdmin) { - MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers) - } - } - val type = - if (senderLeft) SignalServiceGroup.Type.QUIT - else SignalServiceGroup.Type.UPDATE - if (userPublicKey == senderPublicKey) { - val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, type, name, members, admins, threadID, sentTimestamp) - } else { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, admins, sentTimestamp) - } - } - - fun handleClosedGroupMembersAdded(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - val apiDB = DatabaseFactory.getLokiAPIDatabase(context) - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = groupDB.getGroup(groupID).orNull() - if (group == null || !group.isActive) { - Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.") - return - } - // Check common group update logic - if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) { - return - } - val name = group.title - val members = group.members.map { it.serialize() } - val admins = group.admins.map { it.serialize() } - // Users that are part of this add update - val updateMembers = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() } - // newMembers to save is old members plus members included in this update - val newMembers = members + updateMembers - groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) }) - if (userPublicKey == senderPublicKey) { - val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp) - } else { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) - } - if (userPublicKey in admins) { - // send current encryption key to the latest added members - val encryptionKeyPair = pendingKeyPairs[groupPublicKey]?.orNull() - ?: apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) - if (encryptionKeyPair == null) { - Log.d("Loki", "Couldn't get encryption key pair for closed group.") - } else { - for (user in updateMembers) { - MessageSender.sendEncryptionKeyPair(groupPublicKey, encryptionKeyPair, setOf(user), targetUser = user, force = false) - } - } - } - } - - fun handleClosedGroupNameChange(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { - // Check that the sender is a member of the group (before the update) - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = groupDB.getGroup(groupID).orNull() - if (group == null || !group.isActive) { - Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.") - return - } - // Check common group update logic - if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) { - return - } - val members = group.members.map { it.serialize() } - val admins = group.admins.map { it.serialize() } - val name = closedGroupUpdate.name - groupDB.updateTitle(groupID, name) - // Notify the user - if (userPublicKey == senderPublicKey) { - val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, threadID, sentTimestamp) - } else { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) - } - } - - private fun handleClosedGroupMemberLeft(context: Context, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { - // Check the user leaving isn't us, will already be handled - val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! - val apiDB = DatabaseFactory.getLokiAPIDatabase(context) - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = groupDB.getGroup(groupID).orNull() - if (group == null || !group.isActive) { - Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.") - return - } - val name = group.title - // Check common group update logic - val members = group.members.map { it.serialize() } - val admins = group.admins.map { it.toString() } - if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) { - return - } - // If the admin leaves the group is disbanded - val didAdminLeave = admins.contains(senderPublicKey) - val updatedMemberList = members - senderPublicKey - val userLeft = (userPublicKey == senderPublicKey) - - // if the admin left, we left, or we are the only remaining member: remove the group - if (didAdminLeave || userLeft) { - disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey) - } else { - val isCurrentUserAdmin = admins.contains(userPublicKey) - groupDB.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) }) - if (isCurrentUserAdmin) { - MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMemberList) - } - } - // Notify user - if (userLeft) { - val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, threadID, sentTimestamp) - } else { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, sentTimestamp) - } - } - - private fun disableLocalGroupAndUnsubscribe(context: Context, apiDB: LokiAPIDatabase, groupPublicKey: String, groupDB: GroupDatabase, groupID: String, userPublicKey: String) { - apiDB.removeClosedGroupPublicKey(groupPublicKey) - // Remove the key pairs - apiDB.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey) - // Mark the group as inactive - groupDB.setActive(groupID, false) - groupDB.removeMember(groupID, Address.fromSerialized(userPublicKey)) - // Notify the PN server - LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey) - } - - private fun isValidGroupUpdate(group: GroupRecord, - sentTimestamp: Long, - senderPublicKey: String): Boolean { - val oldMembers = group.members.map { it.serialize() } - // Check that the message isn't from before the group was created - // TODO: We should check that formationTimestamp is the sent timestamp of the closed group update that created the group - if (group.formationTimestamp > sentTimestamp) { - Log.d("Loki", "Ignoring closed group update from before thread was created.") - return false - } - // Check that the sender is a member of the group (before the update) - if (senderPublicKey !in oldMembers) { - Log.d("Loki", "Ignoring closed group info message from non-member.") - return false - } - return true - } - - private fun handleGroupEncryptionKeyPair(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, groupPublicKey: String, senderPublicKey: String) { - // Prepare - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - val apiDB = DatabaseFactory.getLokiAPIDatabase(context) - val userKeyPair = apiDB.getUserX25519KeyPair() - // Unwrap the message - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupPublicKeyToUse = when { - groupPublicKey.isNotEmpty() -> groupPublicKey - !closedGroupUpdate.publicKey.isEmpty -> closedGroupUpdate.publicKey.toByteArray().toHexString() - else -> "" - } - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKeyToUse) - val group = groupDB.getGroup(groupID).orNull() - if (group == null) { - Log.d("Loki", "Ignoring closed group encryption key pair message for nonexistent group.") - return - } - if (!group.admins.map { it.toString() }.contains(senderPublicKey)) { - Log.d("Loki", "Ignoring closed group encryption key pair from non-admin.") - return - } - // Find our wrapper and decrypt it if possible - val wrapper = closedGroupUpdate.wrappersList.firstOrNull { it.publicKey.toByteArray().toHexString() == userPublicKey } ?: return - val encryptedKeyPair = wrapper.encryptedKeyPair.toByteArray() - val plaintext = MessageDecrypter.decrypt(encryptedKeyPair, userKeyPair).first - // Parse it - val proto = SignalServiceProtos.KeyPair.parseFrom(plaintext) - val keyPair = ECKeyPair(DjbECPublicKey(proto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(proto.privateKey.toByteArray())) - // Store it - apiDB.addClosedGroupEncryptionKeyPair(keyPair, groupPublicKeyToUse) - Log.d("Loki", "Received a new closed group encryption key pair") - } - - // region Deprecated - private fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { - // Prepare - val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! - val apiDB = DatabaseFactory.getLokiAPIDatabase(context) - // Unwrap the message - val name = closedGroupUpdate.name - val members = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() } - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val group = groupDB.getGroup(groupID).orNull() - if (group == null || !group.isActive) { - Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.") - return - } - val oldMembers = group.members.map { it.serialize() } - // Check common group update logic - if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) { - return - } - // Check that the admin wasn't removed unless the group was destroyed entirely - if (!members.contains(group.admins.first().toString()) && members.isNotEmpty()) { - Log.d("Loki", "Ignoring invalid closed group update message.") - return - } - // Remove the group from the user's set of public keys to poll for if the current user was removed - val wasCurrentUserRemoved = !members.contains(userPublicKey) - if (wasCurrentUserRemoved) { - disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey) - } - // Generate and distribute a new encryption key pair if needed - val wasAnyUserRemoved = (members.toSet().intersect(oldMembers) != oldMembers.toSet()) - val isCurrentUserAdmin = group.admins.map { it.toString() }.contains(userPublicKey) - if (wasAnyUserRemoved && isCurrentUserAdmin) { - MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, members) - } - // Update the group - groupDB.updateTitle(groupID, name) - if (!wasCurrentUserRemoved) { - // The call below sets isActive to true, so if the user is leaving we have to use groupDB.remove(...) instead - groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) - } - // Notify the user - val wasSenderRemoved = !members.contains(senderPublicKey) - val type = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE - val admins = group.admins.map { it.toString() } - if (userPublicKey == senderPublicKey) { - val threadID = DatabaseFactory.getLokiThreadDatabase(context).getThreadID(groupID) - DatabaseFactory.getStorage(context).insertOutgoingInfoMessage(context, groupID, type, name, members, admins, threadID, sentTimestamp) - } else { - DatabaseFactory.getStorage(context).insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, admins, sentTimestamp) - } - } - // endregion -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index 5fbc23848a..734d31d817 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -36,7 +36,7 @@ import org.thoughtcrime.securesms.avatar.AvatarSelection import org.thoughtcrime.securesms.loki.dialogs.ChangeUiModeDialog import org.thoughtcrime.securesms.loki.dialogs.ClearAllDataDialog import org.thoughtcrime.securesms.loki.dialogs.SeedDialog -import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol +import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.mms.GlideApp @@ -194,7 +194,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey) } if (profilePicture != null || displayName != null) { - MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@SettingsActivity) + ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity) } } compoundPromise.alwaysUi { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt similarity index 92% rename from app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt rename to app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt index e9e88215ea..1efc23f8bf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt @@ -1,8 +1,6 @@ -package org.thoughtcrime.securesms.loki.protocol +package org.thoughtcrime.securesms.util import android.content.Context -import com.google.protobuf.ByteString -import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.sending_receiving.MessageSender @@ -10,7 +8,7 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.loki.utilities.ContactUtilities -object MultiDeviceProtocol { +object ConfigurationMessageUtilities { @JvmStatic fun syncConfigurationIfNeeded(context: Context) {