From fc44b9e63be49acdc234745acb91a9e043447fae Mon Sep 17 00:00:00 2001 From: Jubb Date: Thu, 21 Jan 2021 13:24:29 +1100 Subject: [PATCH] refactor!: increase closed group limit to 100 BREAKING CHANGE: if older client users are added to a >20 size closed group they probably won't be able to edit group members without receiving errors remove legacy references to ClosedGroupsProtocol.kt, update translations for CreateClosedGroupActivity.kt --- res/values-de/strings.xml | 2 +- res/values-es/strings.xml | 2 +- res/values-fr/strings.xml | 2 +- res/values-in/strings.xml | 2 +- res/values-it/strings.xml | 2 +- res/values-ja/strings.xml | 2 +- res/values-pl/strings.xml | 2 +- res/values-pt-rBR/strings.xml | 2 +- res/values-ru/strings.xml | 2 +- res/values-vi/strings.xml | 2 +- res/values-zh-rCN/strings.xml | 2 +- res/values/strings.xml | 2 +- .../activities/CreateClosedGroupActivity.kt | 65 +--- .../activities/EditClosedGroupActivity.kt | 2 +- .../loki/protocol/ClosedGroupsProtocol.kt | 284 +----------------- .../loki/protocol/ClosedGroupsProtocolV2.kt | 2 +- 16 files changed, 21 insertions(+), 356 deletions(-) diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 7b5831943b..f11cf764ed 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -1386,7 +1386,7 @@ Schlüsselaustausch-Nachricht für eine ungültige Protokollversion empfangenSession starten Bitte geben Sie einen Gruppennamen ein. Bitte geben Sie einen kürzeren Gruppennamen ein. - Eine geschlossene Gruppe kann maximal 20 Mitglieder haben. + Eine geschlossene Gruppe kann maximal 100 Mitglieder haben. Ein Mitglied Ihrer Gruppe hat eine ungültige Session ID. Offener Gruppe beitreten diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 9f097c82af..a44fb5d241 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -1398,7 +1398,7 @@ Se recibió un mensaje de intercambio de claves para una versión no válida del Empezar una Session Por favor, ingresa un nombre de grupo Por favor, ingresa un nombre de grupo más corto - Un grupo cerrado no puede tener más de 20 miembros + Un grupo cerrado no puede tener más de 100 miembros Uno de los miembros de tu grupo tiene un ID de Session no válido Únete al grupo abierto diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 8c66289c46..d049d367b7 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -1393,7 +1393,7 @@ Vous avez reçu un message d’échange de clés pour une version de protocole i Démarrer une session Veuillez saisir un nom de groupe Veuillez saisir un nom de groupe plus court - Un groupe privé ne peut pas avoir plus de 20 membres + Un groupe privé ne peut pas avoir plus de 100 membres Un des membres de votre groupe a un Session ID non valide Joindre un groupe public diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index f714ca2ae4..931ca9206c 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -1351,7 +1351,7 @@ Diterima pesan pertukaran kunci untuk versi protokol yang tidak valid. Masukkan nama grup Masukkan nama grup yang lebih pendek Pilih setidaknya 2 anggota grup - Grup tertutup maksimal berisi 20 anggota + Grup tertutup maksimal berisi 100 anggota Salah satu anggota di grup memiliki Session ID yang salah Gabung ke grup terbuka diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 91a5dc36ef..ddd4b93863 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -1394,7 +1394,7 @@ Ricevuto un messaggio di scambio chiavi per una versione di protocollo non valid Inizia una sessione Inserisci un nome per il gruppo Inserisci un nome gruppo più breve - Un gruppo chiuso non può avere più di 20 membri + Un gruppo chiuso non può avere più di 100 membri Uno dei membri del tuo gruppo ha una Sessione ID non valido Unisciti a un gruppo aperto diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 94b8fa06c5..10f817eb83 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -1357,7 +1357,7 @@ グループ名を入力してください 短いグループ名を入力してください グループメンバーを少なくとも 2 人選択してください - 閉じたグループは 20 人を超えるメンバーを抱えることはできません + 閉じたグループは 100 人を超えるメンバーを抱えることはできません グループのメンバーの 1 人の Session ID が無効です オープングループに参加する diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 158c3ba773..1018a9c0de 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -1449,7 +1449,7 @@ Otrzymano wiadomość wymiany klucz dla niepoprawnej wersji protokołu. Rozpocznij sesję Wpisz nazwę grupy Wprowadź krótszą nazwę grupy - Grupa zamknięta nie może mieć więcej niż 20 członków + Grupa zamknięta nie może mieć więcej niż 100 członków Jeden z członków Twojej grupy ma nieprawidłowy identyfikator Session Dołącz do otwartej grupy diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml index 4fe6924f42..f9da691c43 100644 --- a/res/values-pt-rBR/strings.xml +++ b/res/values-pt-rBR/strings.xml @@ -1397,7 +1397,7 @@ Iniciar uma sessão Digite um nome de grupo Digite um nome de grupo mais curto - Um grupo fechado não pode ter mais de 20 membros + Um grupo fechado não pode ter mais de 100 membros Um dos membros do seu grupo tem um ID Session inválido Participar em grupo aberto diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 87cbbf29dc..590352b8f4 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -1451,7 +1451,7 @@ Начать Сессию Пожалуйста, введите название группы Пожалуйста, введите более короткое имя группы - В закрытой группе не может быть больше 20 участников + В закрытой группе не может быть больше 100 участников Один из участников вашей группы имеет недопустимый Session ID Присоединиться к открытой группе diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index 97ed7d8a32..c17c4a1e9c 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -845,7 +845,7 @@ Các tin nhắn và cuộc gọi riêng tư miễn phí đến người dùng Si Vui lòng nhập tên nhóm Vui lòng nhập một tên nhóm ngắn hơn Vui lòng chọn ít nhất 2 thành viên trong nhóm - Một nhóm kín không thể có nhiều hơn 20 thành viên + Một nhóm kín không thể có nhiều hơn 100 thành viên Một trong các thành viên trong nhóm của bạn có Session ID không hợp lệ Tham gia nhóm mở diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index b380cbc5e5..7df97b0782 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -1364,7 +1364,7 @@ 开始对话 请输入群组名称 请输入较短的群组名称 - 私密群组成员不得超过20个 + 私密群组成员不得超过100个 您群组中的一位成员的Session ID无效 加入公开群组 diff --git a/res/values/strings.xml b/res/values/strings.xml index 24a69f92e8..2f89a2b6d7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1748,7 +1748,7 @@ Please enter a group name Please enter a shorter group name Please pick at least 1 group member - A closed group cannot have more than 20 members + A closed group cannot have more than 100 members One of the members of your group has an invalid Session ID Join Open Group diff --git a/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt index bc87de8cc1..fbad3d4561 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt @@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.groups.GroupManager -import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2 import org.thoughtcrime.securesms.loki.utilities.fadeIn import org.thoughtcrime.securesms.loki.utilities.fadeOut @@ -43,7 +42,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM } companion object { - val closedGroupCreatedResultCode = 100 + const val closedGroupCreatedResultCode = 100 } // region Lifecycle @@ -98,14 +97,6 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM } private fun createClosedGroup() { - if (ClosedGroupsProtocol.isSharedSenderKeysEnabled) { - createSSKBasedClosedGroup() - } else { - createLegacyClosedGroup() - } - } - - private fun createSSKBasedClosedGroup() { val name = nameEditText.text.trim() if (name.isEmpty()) { return Toast.makeText(this, R.string.activity_create_closed_group_group_name_missing_error, Toast.LENGTH_LONG).show() @@ -117,7 +108,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM if (selectedMembers.count() < 1) { return Toast.makeText(this, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show() } - if (selectedMembers.count() >= ClosedGroupsProtocol.groupSizeLimit) { // Minus one because we're going to include self later + if (selectedMembers.count() >= ClosedGroupsProtocolV2.groupSizeLimit) { // Minus one because we're going to include self later return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() } val userPublicKey = TextSecurePreferences.getLocalNumber(this) @@ -133,61 +124,9 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM } } } - - private fun createLegacyClosedGroup() { - val name = nameEditText.text.trim() - if (name.isEmpty()) { - return Toast.makeText(this, R.string.activity_create_closed_group_group_name_missing_error, Toast.LENGTH_LONG).show() - } - if (name.length >= 64) { - return Toast.makeText(this, R.string.activity_create_closed_group_group_name_too_long_error, Toast.LENGTH_LONG).show() - } - val selectedMembers = this.selectContactsAdapter.selectedMembers - if (selectedMembers.count() < 1) { - return Toast.makeText(this, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show() - } - if (selectedMembers.count() > 10) { - return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() - } - val recipients = selectedMembers.map { - Recipient.from(this, Address.fromSerialized(it), false) - }.toSet() - val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this) ?: TextSecurePreferences.getLocalNumber(this) - val admin = Recipient.from(this, Address.fromSerialized(masterHexEncodedPublicKey), false) - CreateClosedGroupTask(WeakReference(this), null, name.toString(), recipients, setOf( admin )) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) - } // endregion - // region Group Creation Task (Legacy) - internal class CreateClosedGroupTask( - private val activity: WeakReference, - private val profilePicture: Bitmap?, - private val name: String?, - private val members: Set, - private val admins: Set - ) : AsyncTask>() { - - override fun doInBackground(vararg params: Void?): Optional { - val activity = activity.get() ?: return Optional.absent() - return Optional.of(GroupManager.createGroup(activity, members, profilePicture, name, false, admins)) - } - - override fun onPostExecute(result: Optional) { - val activity = activity.get() ?: return super.onPostExecute(result) - if (result.isPresent && result.get().threadId > -1) { - if (!activity.isFinishing) { - openConversationActivity(activity, result.get().threadId, result.get().groupRecipient) - activity.finish() - } - } else { - super.onPostExecute(result) - Toast.makeText(activity.applicationContext, R.string.activity_create_closed_group_invalid_session_id_error, Toast.LENGTH_LONG).show() - } - } - } } -// endregion // region Convenience private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) { diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index 8565e6e097..e3c449cd41 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -243,7 +243,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show() } - val maxGroupMembers = if (isSSKBasedClosedGroup) ClosedGroupsProtocol.groupSizeLimit else legacyGroupSizeLimit + val maxGroupMembers = if (isSSKBasedClosedGroup) ClosedGroupsProtocolV2.groupSizeLimit else legacyGroupSizeLimit if (members.size >= maxGroupMembers) { // TODO: Update copy for SSK based closed groups return Toast.makeText(this, R.string.activity_edit_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 73b071bc3c..550901d6f6 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -37,8 +37,6 @@ import java.util.* import kotlin.jvm.Throws object ClosedGroupsProtocol { - val isSharedSenderKeysEnabled = true - val groupSizeLimit = 20 sealed class Error(val description: String) : Exception() { object NoThread : Error("Couldn't find a thread associated with the given group public key") @@ -46,54 +44,8 @@ object ClosedGroupsProtocol { object InvalidUpdate : Error("Invalid group update.") } - public fun createClosedGroup(context: Context, name: String, members: Collection): Promise { - val deferred = deferred() - Thread { - // Prepare - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - // Generate a key pair for the group - val groupKeyPair = Curve.generateKeyPair() - val groupPublicKey = groupKeyPair.hexEncodedPublicKey // Includes the "05" prefix - val membersAsData = members.map { Hex.fromStringCondensed(it) } - // Create ratchets for all members - val senderKeys: List = members.map { publicKey -> - val ratchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, publicKey) - ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(publicKey)) - } - // Create the group - val groupID = doubleEncodeGroupID(groupPublicKey) - val admins = setOf( userPublicKey ) - DatabaseFactory.getGroupDatabase(context).create(groupID, name, LinkedList
(members.map { Address.fromSerialized(it) }), - null, null, LinkedList
(admins.map { Address.fromSerialized(it) })) - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.from(context, Address.fromSerialized(groupID), false), true) - // Establish sessions if needed - establishSessionsWithMembersIfNeeded(context, members) - // Send a closed group update message to all members using established channels - val adminsAsData = admins.map { Hex.fromStringCondensed(it) } - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, groupKeyPair.privateKey.serialize(), - senderKeys, membersAsData, adminsAsData) - for (member in members) { - if (member == userPublicKey) { continue } - val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) - job.setContext(context) - job.onRun() // Run the job immediately to make all of this sync - } - // Add the group to the user's set of public keys to poll for - DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey) - // Notify the user - val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID) - // Notify the PN server - LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) - // Fulfill the promise - deferred.resolve(groupID) - }.start() - // Return - return deferred.promise - } - @JvmStatic - public fun leave(context: Context, groupPublicKey: String) { + fun leave(context: Context, groupPublicKey: String) { val userPublicKey = TextSecurePreferences.getLocalNumber(context) val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = doubleEncodeGroupID(groupPublicKey) @@ -108,7 +60,7 @@ object ClosedGroupsProtocol { return update(context, groupPublicKey, newMembers, name).get() } - public fun update(context: Context, groupPublicKey: String, members: Collection, name: String): Promise { + fun update(context: Context, groupPublicKey: String, members: Collection, name: String): Promise { val deferred = deferred() Thread { val userPublicKey = TextSecurePreferences.getLocalNumber(context) @@ -237,7 +189,7 @@ object ClosedGroupsProtocol { } @JvmStatic - public fun requestSenderKey(context: Context, groupPublicKey: String, senderPublicKey: String) { + fun requestSenderKey(context: Context, groupPublicKey: String, senderPublicKey: String) { Log.d("Loki", "Requesting sender key for group public key: $groupPublicKey, sender public key: $senderPublicKey.") // Establish session if needed ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(senderPublicKey) @@ -247,217 +199,6 @@ object ClosedGroupsProtocol { ApplicationContext.getInstance(context).jobManager.add(job) } - @JvmStatic - public fun handleSharedSenderKeysUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) { - if (!isValid(closedGroupUpdate)) { return; } - when (closedGroupUpdate.type) { - SignalServiceProtos.ClosedGroupUpdate.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey) - SignalServiceProtos.ClosedGroupUpdate.Type.INFO -> handleClosedGroupUpdate(context, closedGroupUpdate, senderPublicKey) - SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY_REQUEST -> handleSenderKeyRequest(context, closedGroupUpdate, senderPublicKey) - SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY -> handleSenderKey(context, closedGroupUpdate, senderPublicKey) - else -> { - // Do nothing - } - } - } - - private fun isValid(closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate): Boolean { - if (closedGroupUpdate.groupPublicKey.isEmpty) { return false } - when (closedGroupUpdate.type) { - SignalServiceProtos.ClosedGroupUpdate.Type.NEW -> { - return !closedGroupUpdate.name.isNullOrEmpty() && !(closedGroupUpdate.groupPrivateKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty - && closedGroupUpdate.membersCount > 0 && closedGroupUpdate.adminsCount > 0 // senderKeys may be empty - } - SignalServiceProtos.ClosedGroupUpdate.Type.INFO -> { - return !closedGroupUpdate.name.isNullOrEmpty() && closedGroupUpdate.membersCount > 0 && closedGroupUpdate.adminsCount > 0 // senderKeys may be empty - } - SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY_REQUEST -> return true - SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY -> return closedGroupUpdate.senderKeysCount > 0 - else -> return false - } - } - - public fun handleNewClosedGroup(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) { - // Prepare - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - val sskDatabase = DatabaseFactory.getSSKDatabase(context) - // Unwrap the message - val groupPublicKey = closedGroupUpdate.groupPublicKey.toByteArray().toHexString() - val name = closedGroupUpdate.name - val groupPrivateKey = closedGroupUpdate.groupPrivateKey.toByteArray() - val senderKeys = closedGroupUpdate.senderKeysList.map { - ClosedGroupSenderKey(it.chainKey.toByteArray(), it.keyIndex, it.publicKey.toByteArray()) - } - val members = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() } - val admins = closedGroupUpdate.adminsList.map { it.toByteArray().toHexString() } - // Persist the ratchets - senderKeys.forEach { senderKey -> - if (!members.contains(senderKey.publicKey.toHexString())) { return@forEach } - val ratchet = ClosedGroupRatchet(senderKey.chainKey.toHexString(), senderKey.keyIndex, listOf()) - sskDatabase.setClosedGroupRatchet(groupPublicKey, senderKey.publicKey.toHexString(), ratchet, ClosedGroupRatchetCollectionType.Current) - } - // Sort out any discrepancies between the provided sender keys and what's required - val missingSenderKeys = members.toSet().subtract(senderKeys.map { Hex.toStringCondensed(it.publicKey) }) - if (missingSenderKeys.contains(userPublicKey)) { - establishSessionsWithMembersIfNeeded(context, members) - 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) - } - } - for (publicKey in missingSenderKeys.minus(userPublicKey)) { - requestSenderKey(context, groupPublicKey, publicKey) - } - // Create the group - val groupID = doubleEncodeGroupID(groupPublicKey) - val groupDB = DatabaseFactory.getGroupDatabase(context) - if (groupDB.getGroup(groupID).orNull() != 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) })) - } - 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 - sskDatabase.setClosedGroupPrivateKey(groupPublicKey, groupPrivateKey.toHexString()) - // Notify the user - insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) - // Establish sessions if needed - establishSessionsWithMembersIfNeeded(context, members) - // Notify the PN server - LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) - } - - public fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) { - // Prepare - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - val sskDatabase = DatabaseFactory.getSSKDatabase(context) - // Unwrap the message - val groupPublicKey = closedGroupUpdate.groupPublicKey.toByteArray().toHexString() - val name = closedGroupUpdate.name - val senderKeys = closedGroupUpdate.senderKeysList.map { - ClosedGroupSenderKey(it.chainKey.toByteArray(), it.keyIndex, it.publicKey.toByteArray()) - } - val members = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() } - val admins = closedGroupUpdate.adminsList.map { it.toByteArray().toHexString() } - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupID = doubleEncodeGroupID(groupPublicKey) - val group = groupDB.getGroup(groupID).orNull() - if (group == null) { - Log.d("Loki", "Ignoring closed group info message for nonexistent group.") - return - } - val oldMembers = group.members.map { it.serialize() } - // Check that the sender is a member of the group (before the update) - if (!oldMembers.contains(senderPublicKey)) { - Log.d("Loki", "Ignoring closed group info message from non-member.") - return - } - // Store the ratchets for any new members (it's important that this happens before the code below) - senderKeys.forEach { senderKey -> - val ratchet = ClosedGroupRatchet(senderKey.chainKey.toHexString(), senderKey.keyIndex, listOf()) - sskDatabase.setClosedGroupRatchet(groupPublicKey, senderKey.publicKey.toHexString(), ratchet, ClosedGroupRatchetCollectionType.Current) - } - // Delete all ratchets and either: - // • Send out the user's new ratchet using established channels if other members of the group left or were removed - // • Remove the group from the user's set of public keys to poll for if the current user was among the members that were removed - val wasCurrentUserRemoved = !members.contains(userPublicKey) - val wasAnyUserRemoved = members.toSet().intersect(oldMembers) != oldMembers.toSet() - val wasSenderRemoved = !members.contains(senderPublicKey) - if (wasAnyUserRemoved) { - val allOldRatchets = sskDatabase.getAllClosedGroupRatchets(groupPublicKey, ClosedGroupRatchetCollectionType.Current) - for (pair in allOldRatchets) { - @Suppress("NAME_SHADOWING") val senderPublicKey = pair.first - val ratchet = pair.second - val collection = ClosedGroupRatchetCollectionType.Old - sskDatabase.setClosedGroupRatchet(groupPublicKey, senderPublicKey, ratchet, collection) - } - sskDatabase.removeAllClosedGroupRatchets(groupPublicKey, ClosedGroupRatchetCollectionType.Current) - if (wasCurrentUserRemoved) { - sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) - groupDB.setActive(groupID, false) - groupDB.removeMember(groupID, Address.fromSerialized(userPublicKey)) - // Notify the PN server - LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey) - } else { - establishSessionsWithMembersIfNeeded(context, members) - 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 } - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.SenderKey(Hex.fromStringCondensed(groupPublicKey), userSenderKey) - val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) - ApplicationContext.getInstance(context).jobManager.add(job) - } - } - } - // 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 type0 = if (wasSenderRemoved) GroupContext.Type.QUIT else GroupContext.Type.UPDATE - val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE - insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, admins) - } - - public fun handleSenderKeyRequest(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) { - // Prepare - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - val groupPublicKey = closedGroupUpdate.groupPublicKey.toByteArray().toHexString() - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupID = doubleEncodeGroupID(groupPublicKey) - val group = groupDB.getGroup(groupID).orNull() - if (group == null) { - Log.d("Loki", "Ignoring closed group sender key request for nonexistent group.") - return - } - // Check that the requesting user is a member of the group - if (!group.members.map { it.serialize() }.contains(senderPublicKey)) { - Log.d("Loki", "Ignoring closed group sender key request from non-member.") - return - } - // Respond to the request - Log.d("Loki", "Responding to sender key request from: $senderPublicKey.") - ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(senderPublicKey) - val userRatchet = DatabaseFactory.getSSKDatabase(context).getClosedGroupRatchet(groupPublicKey, userPublicKey, ClosedGroupRatchetCollectionType.Current) - ?: SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, userPublicKey) - val userSenderKey = ClosedGroupSenderKey(Hex.fromStringCondensed(userRatchet.chainKey), userRatchet.keyIndex, Hex.fromStringCondensed(userPublicKey)) - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.SenderKey(Hex.fromStringCondensed(groupPublicKey), userSenderKey) - val job = ClosedGroupUpdateMessageSendJob(senderPublicKey, closedGroupUpdateKind) - ApplicationContext.getInstance(context).jobManager.add(job) - } - - public fun handleSenderKey(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) { - // Prepare - val sskDatabase = DatabaseFactory.getSSKDatabase(context) - val groupPublicKey = closedGroupUpdate.groupPublicKey.toByteArray().toHexString() - val senderKeyProto = closedGroupUpdate.senderKeysList.firstOrNull() - if (senderKeyProto == null) { - Log.d("Loki", "Ignoring invalid closed group sender key.") - return - } - val senderKey = ClosedGroupSenderKey(senderKeyProto.chainKey.toByteArray(), senderKeyProto.keyIndex, senderKeyProto.publicKey.toByteArray()) - if (senderKeyProto.publicKey.toByteArray().toHexString() != senderPublicKey) { - Log.d("Loki", "Ignoring invalid closed group sender key.") - return - } - // Store the sender key - Log.d("Loki", "Received a sender key from: $senderPublicKey.") - val ratchet = ClosedGroupRatchet(senderKey.chainKey.toHexString(), senderKey.keyIndex, listOf()) - sskDatabase.setClosedGroupRatchet(groupPublicKey, senderPublicKey, ratchet, ClosedGroupRatchetCollectionType.Current) - } - @JvmStatic fun shouldIgnoreContentMessage(context: Context, address: Address, groupID: String?, senderPublicKey: String): Boolean { if (!address.isClosedGroup || groupID == null) { return false } @@ -546,21 +287,6 @@ object ClosedGroupsProtocol { } } - private fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: GroupContext.Type, type1: SignalServiceGroup.Type, - name: String, members: Collection, admins: Collection) { - val groupContextBuilder = GroupContext.newBuilder() - .setId(ByteString.copyFrom(GroupUtil.getDecodedId(groupID))) - .setType(type0) - .setName(name) - .addAllMembers(members) - .addAllAdmins(admins) - val group = SignalServiceGroup(type1, GroupUtil.getDecodedId(groupID), GroupType.SIGNAL, name, members.toList(), null, admins.toList()) - val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, System.currentTimeMillis(), "", Optional.of(group), 0, true) - val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), "") - val smsDB = DatabaseFactory.getSmsDatabase(context) - smsDB.insertMessageInbox(infoMessage) - } - private fun insertOutgoingInfoMessage(context: Context, groupID: String, type: GroupContext.Type, name: String, members: Collection, admins: Collection, threadID: Long) { val recipient = Recipient.from(context, Address.fromSerialized(groupID), false) @@ -580,13 +306,13 @@ object ClosedGroupsProtocol { @JvmStatic @Throws(IOException::class) - public fun doubleEncodeGroupID(groupPublicKey: String): String { + fun doubleEncodeGroupID(groupPublicKey: String): String { return GroupUtil.getEncodedId(GroupUtil.getEncodedId(Hex.fromStringCondensed(groupPublicKey), false).toByteArray(), false) } @JvmStatic @Throws(IOException::class) - public fun doubleDecodeGroupID(groupID: String): ByteArray { + fun doubleDecodeGroupID(groupID: String): ByteArray { return GroupUtil.getDecodedId(GroupUtil.getDecodedStringId(groupID)) } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index 014d7fad62..c62d86d92d 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -42,7 +42,7 @@ import java.util.* import kotlin.jvm.Throws object ClosedGroupsProtocolV2 { - val groupSizeLimit = 20 + const val groupSizeLimit = 100 sealed class Error(val description: String) : Exception() { object NoThread : Error("Couldn't find a thread associated with the given group public key")