From 7eb59c1400b7760abfdee6b3e990263e213a4cbe Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Fri, 7 Aug 2020 13:47:40 +1000 Subject: [PATCH] Implement SSK group member adding Also finish group creation --- .../loki/protocol/ClosedGroupsProtocol.kt | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 522bbce2f5..3618ebebfb 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.loki.protocol import android.content.Context +import android.util.Log import nl.komponents.kovenant.Promise import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.Address @@ -34,7 +35,7 @@ object ClosedGroupsProtocol { ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(publicKey)) } // Create the group - val groupID = GroupUtil.getEncodedId(Hex.fromStringCondensed(groupPublicKey), false); + val groupID = GroupUtil.getEncodedId(Hex.fromStringCondensed(groupPublicKey), false) val admins = setOf( Address.fromSerialized(userPublicKey) ) DatabaseFactory.getGroupDatabase(context).create(groupID, name, LinkedList
(members.map { Address.fromSerialized(it) }), null, null, LinkedList
(admins)) @@ -42,15 +43,70 @@ object ClosedGroupsProtocol { // Establish sessions if needed establishSessionsWithMembersIfNeeded(context, members) // Send a closed group update message to all members using established channels - // TODO + val adminsAsData = admins.map { Hex.fromStringCondensed(it.serialize()) } + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, groupKeyPair.privateKey.serialize(), + senderKeys, membersAsData, adminsAsData) + for (member in members) { + val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) + ApplicationContext.getInstance(context).jobManager.add(job) + } + // TODO: Wait for the messages to finish sending // Add the group to the user's set of public keys to poll for DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey) // Notify the user - // TODO + // TODO: Implement // Return return Promise.of(Unit) } + public fun addMembers(context: Context, newMembers: Collection, groupPublicKey: String) { + // Prepare + val userPublicKey = TextSecurePreferences.getLocalNumber(context) + val groupDB = DatabaseFactory.getGroupDatabase(context) + val groupID = GroupUtil.getEncodedId(Hex.fromStringCondensed(groupPublicKey), false) + 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 { Hex.fromStringCondensed(it.serialize()) } + val privateKey = DatabaseFactory.getSSKDatabase(context).getClosedGroupPrivateKey(groupPublicKey) + if (privateKey == null) { + 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, admins) + 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 + for (member in members) { + @Suppress("NAME_SHADOWING") + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), name, + senderKeys, membersAsData, admins) + @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 + // TODO: Implement + } + @JvmStatic fun shouldIgnoreContentMessage(context: Context, address: Address, groupID: String?, senderPublicKey: String): Boolean { if (!address.isClosedGroup || groupID == null) { return false }