From 5957afa1479eb739b5ba4ff342695b57b8e8b286 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 31 Aug 2020 14:54:03 +1000 Subject: [PATCH] Debug SSK based closed group editing --- .../activities/EditClosedGroupActivity.kt | 3 +- .../loki/protocol/ClosedGroupsProtocol.kt | 177 ++++++++---------- 2 files changed, 76 insertions(+), 104 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index 2326562a48..455587c8af 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -234,8 +234,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { } if (isSSKBasedClosedGroup) { - ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() }, - name, admins.map { it.address.serialize() }) + ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name) } else { GroupManager.updateGroup(this, groupID, members, null, name, admins) } diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 3df6187a7b..1c347de7ab 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -73,17 +73,34 @@ object ClosedGroupsProtocol { return groupID } - public fun addMembers(context: Context, newMembers: Collection, groupPublicKey: String) { - // Prepare + @JvmStatic + public fun leave(context: Context, groupPublicKey: String) { + val userPublicKey = TextSecurePreferences.getLocalNumber(context) + val groupDB = DatabaseFactory.getGroupDatabase(context) + val groupID = doubleEncodeGroupID(groupPublicKey) + val group = groupDB.getGroup(groupID).orNull() + if (group == null) { + Log.d("Loki", "Can't leave nonexistent closed group.") + return + } + val name = group.title + val oldMembers = group.members.map { it.serialize() }.toSet() + val newMembers = oldMembers.minus(userPublicKey) + update(context, groupPublicKey, newMembers, name) + } + + public fun update(context: Context, groupPublicKey: String, members: Collection, name: String) { + val userPublicKey = TextSecurePreferences.getLocalNumber(context) val sskDatabase = DatabaseFactory.getSSKDatabase(context) val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = doubleEncodeGroupID(groupPublicKey) val group = groupDB.getGroup(groupID).orNull() if (group == null) { - Log.d("Loki", "Can't add users to nonexistent closed group.") + Log.d("Loki", "Can't update nonexistent closed group.") return } - val name = group.title + val oldMembers = group.members.map { it.serialize() }.toSet() + val membersAsData = members.map { Hex.fromStringCondensed(it) } val admins = group.admins.map { it.serialize() } val adminsAsData = admins.map { Hex.fromStringCondensed(it) } val groupPrivateKey = DatabaseFactory.getSSKDatabase(context).getClosedGroupPrivateKey(groupPublicKey) @@ -91,113 +108,69 @@ object ClosedGroupsProtocol { Log.d("Loki", "Couldn't get private key for closed group.") return } - // Add the members to the member list - val members = group.members.map { it.serialize() }.toMutableSet() - members.addAll(newMembers) - val membersAsData = members.map { Hex.fromStringCondensed(it) } - // Generate ratchets for the new members - val senderKeys: List = newMembers.map { publicKey -> - val ratchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, publicKey) - ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(publicKey)) - } - // Send a closed group update message to the existing members with the new members' ratchets (this message is aimed at the group) - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), name, - senderKeys, membersAsData, adminsAsData) - val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) - ApplicationContext.getInstance(context).jobManager.add(job) - // Establish sessions if needed - establishSessionsWithMembersIfNeeded(context, newMembers) - // Send closed group update messages to the new members using established channels - val allSenderKeys = sskDatabase.getAllClosedGroupSenderKeys(groupPublicKey) + senderKeys - for (member in members) { - @Suppress("NAME_SHADOWING") - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, - Hex.fromStringCondensed(groupPrivateKey), allSenderKeys, membersAsData, adminsAsData) - @Suppress("NAME_SHADOWING") - val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) - ApplicationContext.getInstance(context).jobManager.add(job) - } - // Update the group - groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) - // Notify the user - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID) - } - - @JvmStatic - public fun leave(context: Context, groupPublicKey: String) { - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - removeMembers(context, setOf( userPublicKey ), groupPublicKey) - } - - public fun removeMembers(context: Context, membersToRemove: Collection, groupPublicKey: String) { - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - val sskDatabase = DatabaseFactory.getSSKDatabase(context) - val isUserLeaving = membersToRemove.contains(userPublicKey) - if (isUserLeaving && membersToRemove.count() != 1) { - Log.d("Loki", "Can't remove self and others simultaneously.") - return - } - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupID = doubleEncodeGroupID(groupPublicKey) - val group = groupDB.getGroup(groupID).orNull() - if (group == null) { - Log.d("Loki", "Can't add users to nonexistent closed group.") - return - } - val name = group.title - val admins = group.admins.map { it.serialize() } - val adminsAsData = admins.map { Hex.fromStringCondensed(it) } - // Remove the members from the member list - val members = group.members.map { it.serialize() }.toSet().minus(membersToRemove) - val membersAsData = members.map { Hex.fromStringCondensed(it) } - // Send the update to the group (don't include new ratchets as everyone should regenerate new ratchets individually) - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), - name, setOf(), membersAsData, adminsAsData) - val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) - job.setContext(context) - job.onRun() // Run the job immediately - // Delete all ratchets (it's important that this happens after sending out the update) - sskDatabase.removeAllClosedGroupRatchets(groupPublicKey) - // Remove the group from the user's set of public keys to poll for if the user is leaving. Otherwise generate a new ratchet and - // send it out to all members (minus the removed ones) using established channels. - if (isUserLeaving) { - sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) - groupDB.setActive(groupID, false) + val wasAnyUserRemoved = members.toSet().intersect(oldMembers) != oldMembers.toSet() + if (wasAnyUserRemoved) { + val removedMembers = oldMembers.minus(members) + val isUserLeaving = removedMembers.contains(userPublicKey) + if (isUserLeaving && removedMembers.count() != 1) { + Log.d("Loki", "Can't remove self and others simultaneously.") + return + } + // Send the update to the group (don't include new ratchets as everyone should regenerate new ratchets individually) + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), + name, setOf(), membersAsData, adminsAsData) + val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) + job.setContext(context) + job.onRun() // Run the job immediately + // Delete all ratchets (it's important that this happens * after * sending out the update) + sskDatabase.removeAllClosedGroupRatchets(groupPublicKey) + // Remove the group from the user's set of public keys to poll for if the user is leaving. Otherwise generate a new ratchet and + // send it out to all members (minus the removed ones) using established channels. + if (isUserLeaving) { + sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) + groupDB.setActive(groupID, false) + } else { + // Establish sessions if needed + establishSessionsWithMembersIfNeeded(context, members) + // Send out the user's new ratchet to all members (minus the removed ones) using established channels + val userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, userPublicKey) + val userSenderKey = ClosedGroupSenderKey(Hex.fromStringCondensed(userRatchet.chainKey), userRatchet.keyIndex, Hex.fromStringCondensed(userPublicKey)) + for (member in members) { + if (member == userPublicKey) { continue } + @Suppress("NAME_SHADOWING") + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.SenderKey(Hex.fromStringCondensed(groupPublicKey), userSenderKey) + @Suppress("NAME_SHADOWING") + val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) + ApplicationContext.getInstance(context).jobManager.add(job) + } + } } else { + // Generate ratchets for any new members + val newMembers = members.minus(oldMembers) + val senderKeys: List = newMembers.map { publicKey -> + val ratchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, publicKey) + ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(publicKey)) + } + // Send a closed group update message to the existing members with the new members' ratchets (this message is aimed at the group) + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), name, + senderKeys, membersAsData, adminsAsData) + val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) + ApplicationContext.getInstance(context).jobManager.add(job) // Establish sessions if needed - establishSessionsWithMembersIfNeeded(context, members) - // Send out the user's new ratchet to all members (minus the removed ones) using established channels - val userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, userPublicKey) - val userSenderKey = ClosedGroupSenderKey(Hex.fromStringCondensed(userRatchet.chainKey), userRatchet.keyIndex, Hex.fromStringCondensed(userPublicKey)) - for (member in members) { + establishSessionsWithMembersIfNeeded(context, newMembers) + // Send closed group update messages to the new members using established channels + val allSenderKeys = sskDatabase.getAllClosedGroupSenderKeys(groupPublicKey) + senderKeys + for (member in newMembers) { @Suppress("NAME_SHADOWING") - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.SenderKey(Hex.fromStringCondensed(groupPublicKey), userSenderKey) + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, + Hex.fromStringCondensed(groupPrivateKey), allSenderKeys, membersAsData, adminsAsData) @Suppress("NAME_SHADOWING") val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) ApplicationContext.getInstance(context).jobManager.add(job) } } // Update the group - groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) - // Notify the user - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, GroupContext.Type.QUIT, name, members, admins, threadID) - } - - public fun update(context: Context, groupPublicKey: String, members: Collection, name: String, admins: Collection) { - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupID = doubleEncodeGroupID(groupPublicKey) - if (groupDB.getGroup(groupID).orNull() == null) { - Log.d("Loki", "Can't update nonexistent closed group.") - return - } - // Send the update to the group - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), - name, setOf(), members.map { Hex.fromStringCondensed(it) }, admins.map { Hex.fromStringCondensed(it) }) - val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) - ApplicationContext.getInstance(context).jobManager.add(job) - // Update the group + groupDB.updateTitle(groupID, name) groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) // Notify the user val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))