diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index d69c998e73..67da3867d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -107,12 +107,16 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.TreeSet; +import kotlin.Unit; import network.loki.messenger.R; +import nl.komponents.kovenant.Kovenant; @SuppressLint("StaticFieldLeak") public class ConversationFragment extends Fragment @@ -402,18 +406,24 @@ public class ConversationFragment extends Fragment boolean isPublicChat = (publicChat != null); int selectedMessageCount = messageRecords.size(); boolean areAllSentByUser = true; + Set uniqueUserSet = new HashSet<>(); for (MessageRecord message : messageRecords) { if (!message.isOutgoing()) { areAllSentByUser = false; } + uniqueUserSet.add(message.getRecipient().getAddress().toString()); } menu.findItem(R.id.menu_context_copy_public_key).setVisible(selectedMessageCount == 1 && !areAllSentByUser); menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext()); boolean userCanModerate = isPublicChat && PublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer()); boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate); + // allow banning if moderating a public chat and only one user's messages are selected + boolean isBanOptionVisible = isPublicChat && userCanModerate && !areAllSentByUser && uniqueUserSet.size() == 1; menu.findItem(R.id.menu_context_delete_message).setVisible(isDeleteOptionVisible); + menu.findItem(R.id.menu_context_ban_user).setVisible(isBanOptionVisible); } else { menu.findItem(R.id.menu_context_copy_public_key).setVisible(false); menu.findItem(R.id.menu_context_delete_message).setVisible(true); + menu.findItem(R.id.menu_context_ban_user).setVisible(false); } } @@ -572,6 +582,59 @@ public class ConversationFragment extends Fragment builder.show(); } + private void handleBanUser(Set messageRecords) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + + String userPublicKey = null; + for (MessageRecord record: messageRecords) { + String currentPublicKey = record.getRecipient().getAddress().toString(); + if (userPublicKey == null) { + userPublicKey = currentPublicKey; + } + } + final String finalPublicKey = userPublicKey; + + builder.setIconAttribute(R.attr.dialog_alert_icon); + builder.setTitle(R.string.ConversationFragment_ban_selected_user); + builder.setCancelable(true); + + PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId); + + builder.setPositiveButton(R.string.ban, (dialog, which) -> { + ConversationAdapter chatAdapter = getListAdapter(); + chatAdapter.clearSelection(); + chatAdapter.notifyDataSetChanged(); + new ProgressDialogAsyncTask(getActivity(), + R.string.ConversationFragment_banning, + R.string.ConversationFragment_banning_user) { + @Override + protected Void doInBackground(String... userPublicKeyParam) { + String userPublicKey = userPublicKeyParam[0]; + if (publicChat != null) { + PublicChatAPI publicChatAPI = ApplicationContext.getInstance(getContext()).getPublicChatAPI(); + if (publicChat != null && publicChatAPI != null) { + publicChatAPI + .ban(userPublicKey, publicChat.getServer()) + .success(l -> { + Log.d("Loki", "User banned"); + return Unit.INSTANCE; + }).fail(e -> { + Log.d("Loki", "Couldn't ban user due to error: " + e.toString() + "."); + return null; + }); + } + } else { + Log.d("Loki", "Tried to ban user from a non-public chat"); + } + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, finalPublicKey); + }); + + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + } + private void handleDisplayDetails(MessageRecord message) { Intent intent = new Intent(getActivity(), MessageDetailsActivity.class); intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, message.getId()); @@ -1086,6 +1149,9 @@ public class ConversationFragment extends Fragment handleDeleteMessages(getListAdapter().getSelectedItems()); actionMode.finish(); return true; + case R.id.menu_context_ban_user: + handleBanUser(getListAdapter().getSelectedItems()); + return true; case R.id.menu_context_details: handleDisplayDetails(getSelectedMessageRecord()); actionMode.finish(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt index 4f137733f5..ffab859f12 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt @@ -21,7 +21,6 @@ import org.session.libsession.messaging.threads.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.utilities.fadeIn import org.thoughtcrime.securesms.loki.utilities.fadeOut import org.thoughtcrime.securesms.mms.GlideApp @@ -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,8 @@ 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, 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/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index 81169f93d3..0d197ffbc7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -252,14 +252,13 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { isSSKBasedClosedGroup = false } - if (members.size < 1) { + if (members.isEmpty()) { 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() + return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() } val userPublicKey = TextSecurePreferences.getLocalNumber(this)!! diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 60f453362b..9f5120e71e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -37,8 +37,6 @@ import java.io.IOException import java.util.* 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,218 +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 +286,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.getDecodedGroupIDAsData(groupID))) - .setType(type0) - .setName(name) - .addAllMembers(members) - .addAllAdmins(admins) - val group = SignalServiceGroup(type1, GroupUtil.getDecodedGroupIDAsData(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 +305,13 @@ object ClosedGroupsProtocol { @JvmStatic @Throws(IOException::class) - public fun doubleEncodeGroupID(groupPublicKey: String): String { + fun doubleEncodeGroupID(groupPublicKey: String): String { return GroupUtil.getEncodedClosedGroupID(GroupUtil.getEncodedClosedGroupID(groupPublicKey)) } @JvmStatic @Throws(IOException::class) - public fun doubleDecodeGroupID(groupID: String): ByteArray { + fun doubleDecodeGroupID(groupID: String): ByteArray { return GroupUtil.getDecodedGroupIDAsData(GroupUtil.getDecodedGroupID(groupID)) } 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 index a4f0a8e4b7..28031a1def 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -35,7 +35,7 @@ import java.util.* import kotlin.jvm.Throws object ClosedGroupsProtocolV2 { - val groupSizeLimit = 20 + 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") diff --git a/app/src/main/res/menu/conversation_context.xml b/app/src/main/res/menu/conversation_context.xml index 4224c2bc5c..1204fedb31 100644 --- a/app/src/main/res/menu/conversation_context.xml +++ b/app/src/main/res/menu/conversation_context.xml @@ -11,6 +11,12 @@ android:icon="?menu_trash_icon" app:showAsAction="always" /> + + Session 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/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 9f097c82af..a44fb5d241 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/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/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 303805240d..b5cc992274 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -1312,7 +1312,7 @@ شروع Session لطفا یک نام گروه وارد کنید لطفا نام گروه کوتاه‌تری وارد کنید - یک گروه خصوصی نمی‌تواند بیش از بیست عضو داشته باشد + یک گروه خصوصی نمی‌تواند بیش از یکصد عضو داشته باشد یکی از اعضای گروه شما دارای شناسه نامعتبر است به گروه باز بپیوندید diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8c66289c46..d049d367b7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/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/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index f714ca2ae4..931ca9206c 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/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/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 91a5dc36ef..ddd4b93863 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/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/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 94b8fa06c5..10f817eb83 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1357,7 +1357,7 @@ グループ名を入力してください 短いグループ名を入力してください グループメンバーを少なくとも 2 人選択してください - 閉じたグループは 20 人を超えるメンバーを抱えることはできません + 閉じたグループは 100 人を超えるメンバーを抱えることはできません グループのメンバーの 1 人の Session ID が無効です オープングループに参加する diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 158c3ba773..1018a9c0de 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/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/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4fe6924f42..f9da691c43 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/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/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 87cbbf29dc..590352b8f4 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1451,7 +1451,7 @@ Начать Сессию Пожалуйста, введите название группы Пожалуйста, введите более короткое имя группы - В закрытой группе не может быть больше 20 участников + В закрытой группе не может быть больше 100 участников Один из участников вашей группы имеет недопустимый Session ID Присоединиться к открытой группе diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 97ed7d8a32..b53bcad53a 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/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/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index b380cbc5e5..7df97b0782 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1364,7 +1364,7 @@ 开始对话 请输入群组名称 请输入较短的群组名称 - 私密群组成员不得超过20个 + 私密群组成员不得超过100个 您群组中的一位成员的Session ID无效 加入公开群组 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index df0d4b07f3..e831ac6d8e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ Yes No Delete + Ban Please wait... Save Note to Self @@ -206,6 +207,7 @@ This will permanently delete the selected message. This will permanently delete all %1$d selected messages. + Ban this user? Save to storage? Saving this media to storage will allow any other apps on your device to access it.\n\nContinue? @@ -230,6 +232,8 @@ SMS Deleting Deleting messages... + Banning + Banning user… Original message not found Original message no longer available @@ -1340,6 +1344,7 @@ Message details Copy text Delete message + Ban user Forward message Resend message Reply to message @@ -1750,7 +1755,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 @@ -1844,7 +1849,6 @@ Group name can\'t be empty Please enter a shorter group name Groups must have at least 1 group member - A closed group cannot have more than 10 members One of the members of your group has an invalid Session ID Are you sure you want to remove this user? User removed from group diff --git a/libsession/src/main/res/values/strings.xml b/libsession/src/main/res/values/strings.xml index df0d4b07f3..70b424bd22 100644 --- a/libsession/src/main/res/values/strings.xml +++ b/libsession/src/main/res/values/strings.xml @@ -1750,7 +1750,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