2020-05-11 16:19:26 +10:00
|
|
|
package org.thoughtcrime.securesms.loki.protocol
|
|
|
|
|
|
|
|
import android.content.Context
|
2020-05-22 11:14:36 +10:00
|
|
|
import nl.komponents.kovenant.Promise
|
2020-05-13 10:24:20 +10:00
|
|
|
import org.thoughtcrime.securesms.ApplicationContext
|
2020-05-11 16:19:26 +10:00
|
|
|
import org.thoughtcrime.securesms.database.Address
|
|
|
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
2020-05-13 12:29:31 +10:00
|
|
|
import org.thoughtcrime.securesms.loki.utilities.recipient
|
2020-05-11 16:19:26 +10:00
|
|
|
import org.thoughtcrime.securesms.recipients.Recipient
|
|
|
|
import org.thoughtcrime.securesms.sms.MessageSender
|
2020-08-07 10:17:35 +10:00
|
|
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
|
2020-05-11 16:19:26 +10:00
|
|
|
import org.thoughtcrime.securesms.util.GroupUtil
|
2020-08-07 10:17:35 +10:00
|
|
|
import org.thoughtcrime.securesms.util.Hex
|
2020-05-11 16:19:26 +10:00
|
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
2020-08-07 10:17:35 +10:00
|
|
|
import org.whispersystems.libsignal.ecc.Curve
|
|
|
|
import org.whispersystems.signalservice.loki.protocol.closedgroups.ClosedGroupSenderKey
|
|
|
|
import org.whispersystems.signalservice.loki.protocol.closedgroups.SharedSenderKeysImplementation
|
|
|
|
import org.whispersystems.signalservice.loki.utilities.hexEncodedPrivateKey
|
|
|
|
import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey
|
2020-05-22 10:41:31 +10:00
|
|
|
import java.util.*
|
2020-05-11 16:19:26 +10:00
|
|
|
|
|
|
|
object ClosedGroupsProtocol {
|
|
|
|
|
2020-08-07 10:17:35 +10:00
|
|
|
public fun createClosedGroup(context: Context, name: String, members: Collection<String>): Promise<Unit, Exception> {
|
|
|
|
// 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<ClosedGroupSenderKey> = 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 = GroupUtil.getEncodedId(Hex.fromStringCondensed(groupPublicKey), false);
|
|
|
|
val admins = setOf( Address.fromSerialized(userPublicKey) )
|
|
|
|
DatabaseFactory.getGroupDatabase(context).create(groupID, name, LinkedList<Address>(members.map { Address.fromSerialized(it) }),
|
|
|
|
null, null, LinkedList<Address>(admins))
|
|
|
|
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
|
|
|
|
// TODO
|
|
|
|
// 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
|
|
|
|
// Return
|
|
|
|
return Promise.of(Unit)
|
|
|
|
}
|
|
|
|
|
2020-05-12 16:28:35 +10:00
|
|
|
@JvmStatic
|
2020-08-07 09:15:36 +10:00
|
|
|
fun shouldIgnoreContentMessage(context: Context, address: Address, groupID: String?, senderPublicKey: String): Boolean {
|
|
|
|
if (!address.isClosedGroup || groupID == null) { return false }
|
|
|
|
/*
|
2020-07-15 12:24:43 +10:00
|
|
|
FileServerAPI.shared.getDeviceLinks(senderPublicKey).timeout(6000).get()
|
2020-05-13 12:29:31 +10:00
|
|
|
val senderMasterPublicKey = MultiDeviceProtocol.shared.getMasterDevice(senderPublicKey)
|
|
|
|
val publicKeyToCheckFor = senderMasterPublicKey ?: senderPublicKey
|
2020-08-07 09:15:36 +10:00
|
|
|
*/
|
2020-05-13 12:29:31 +10:00
|
|
|
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, true)
|
2020-08-07 09:15:36 +10:00
|
|
|
return !members.contains(recipient(context, senderPublicKey))
|
2020-05-13 12:29:31 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
@JvmStatic
|
2020-08-07 09:15:36 +10:00
|
|
|
fun getMessageDestinations(context: Context, groupID: String): List<Address> {
|
|
|
|
if (GroupUtil.isRSSFeed(groupID)) { return listOf() }
|
2020-05-12 15:29:00 +10:00
|
|
|
if (GroupUtil.isOpenGroup(groupID)) {
|
2020-08-07 09:15:36 +10:00
|
|
|
return listOf( Address.fromSerialized(groupID) )
|
2020-05-12 15:29:00 +10:00
|
|
|
} else {
|
2020-08-07 09:15:36 +10:00
|
|
|
// TODO: Shared sender keys
|
|
|
|
return DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, false).map { it.address }
|
|
|
|
/*
|
2020-07-15 12:24:43 +10:00
|
|
|
return FileServerAPI.shared.getDeviceLinks(members.map { it.address.serialize() }.toSet()).map {
|
2020-05-22 11:14:36 +10:00
|
|
|
val result = members.flatMap { member ->
|
|
|
|
MultiDeviceProtocol.shared.getAllLinkedDevices(member.address.serialize()).map { Address.fromSerialized(it) }
|
|
|
|
}.toMutableSet()
|
|
|
|
val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
|
|
|
if (userMasterPublicKey != null && result.contains(Address.fromSerialized(userMasterPublicKey))) {
|
|
|
|
result.remove(Address.fromSerialized(userMasterPublicKey))
|
|
|
|
}
|
|
|
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
|
|
|
if (userPublicKey != null && result.contains(Address.fromSerialized(userPublicKey))) {
|
|
|
|
result.remove(Address.fromSerialized(userPublicKey))
|
|
|
|
}
|
|
|
|
result.toList()
|
2020-05-12 15:29:00 +10:00
|
|
|
}
|
2020-08-07 09:15:36 +10:00
|
|
|
*/
|
2020-05-12 15:29:00 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@JvmStatic
|
2020-08-07 09:15:36 +10:00
|
|
|
fun leaveLegacyGroup(context: Context, recipient: Recipient): Boolean {
|
2020-05-11 16:54:31 +10:00
|
|
|
if (!recipient.address.isClosedGroup) { return true }
|
2020-05-11 16:19:26 +10:00
|
|
|
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
2020-08-07 09:15:36 +10:00
|
|
|
val message = GroupUtil.createGroupLeaveMessage(context, recipient).orNull()
|
|
|
|
if (threadID < 0 || message == null) { return false }
|
|
|
|
MessageSender.send(context, message, threadID, false, null)
|
|
|
|
/*
|
2020-05-11 16:54:31 +10:00
|
|
|
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
2020-05-13 12:29:31 +10:00
|
|
|
val publicKeyToRemove = masterPublicKey ?: TextSecurePreferences.getLocalNumber(context)
|
2020-08-07 09:15:36 +10:00
|
|
|
*/
|
|
|
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
2020-05-11 16:19:26 +10:00
|
|
|
val groupDatabase = DatabaseFactory.getGroupDatabase(context)
|
|
|
|
val groupID = recipient.address.toGroupString()
|
|
|
|
groupDatabase.setActive(groupID, false)
|
2020-08-07 09:15:36 +10:00
|
|
|
groupDatabase.remove(groupID, Address.fromSerialized(userPublicKey))
|
2020-05-11 16:19:26 +10:00
|
|
|
return true
|
|
|
|
}
|
2020-05-12 16:28:35 +10:00
|
|
|
|
|
|
|
@JvmStatic
|
2020-08-07 10:17:35 +10:00
|
|
|
fun establishSessionsWithMembersIfNeeded(context: Context, members: Collection<String>) {
|
2020-08-07 09:15:36 +10:00
|
|
|
@Suppress("NAME_SHADOWING") val members = members.toMutableSet()
|
|
|
|
/*
|
2020-05-12 16:28:35 +10:00
|
|
|
val allDevices = members.flatMap { member ->
|
|
|
|
MultiDeviceProtocol.shared.getAllLinkedDevices(member)
|
|
|
|
}.toMutableSet()
|
2020-05-13 12:29:31 +10:00
|
|
|
val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
|
|
|
if (userMasterPublicKey != null && allDevices.contains(userMasterPublicKey)) {
|
|
|
|
allDevices.remove(userMasterPublicKey)
|
2020-05-12 16:28:35 +10:00
|
|
|
}
|
2020-08-07 09:15:36 +10:00
|
|
|
*/
|
2020-05-12 16:28:35 +10:00
|
|
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
2020-08-07 09:15:36 +10:00
|
|
|
if (userPublicKey != null && members.contains(userPublicKey)) {
|
|
|
|
members.remove(userPublicKey)
|
2020-05-12 16:28:35 +10:00
|
|
|
}
|
2020-08-07 09:15:36 +10:00
|
|
|
for (member in members) {
|
|
|
|
ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(member)
|
2020-05-12 16:28:35 +10:00
|
|
|
}
|
|
|
|
}
|
2020-05-11 16:19:26 +10:00
|
|
|
}
|