diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 66b4fd5004..a09866b265 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -428,6 +428,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getLokiAPIDatabase(context).removeAllClosedGroupEncryptionKeyPairs(groupPublicKey) } + override fun setExpirationTimer(groupID: String, duration: Int) { + val recipient = Recipient.from(context, fromSerialized(groupID), false) + DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, duration); + } + override fun getAllV2OpenGroups(): Map { return DatabaseFactory.getLokiThreadDatabase(context).getAllV2OpenGroups() } diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index 9d42a2a2f8..dc557d337d 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -115,6 +115,7 @@ interface StorageProtocol { fun isClosedGroup(publicKey: String): Boolean fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair? + fun setExpirationTimer(groupID: String, duration: Int) // Groups fun getAllGroups(): List diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt index 6503582f89..e7e5196671 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt @@ -34,6 +34,7 @@ class ClosedGroupControlMessage() : ControlMessage() { is Kind.New -> { !kind.publicKey.isEmpty && kind.name.isNotEmpty() && kind.encryptionKeyPair?.publicKey != null && kind.encryptionKeyPair?.privateKey != null && kind.members.isNotEmpty() && kind.admins.isNotEmpty() + && kind.expireTimer >= 0 } is Kind.EncryptionKeyPair -> true is Kind.NameChange -> kind.name.isNotEmpty() @@ -44,8 +45,8 @@ class ClosedGroupControlMessage() : ControlMessage() { } sealed class Kind { - class New(var publicKey: ByteString, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List) : Kind() { - internal constructor() : this(ByteString.EMPTY, "", null, listOf(), listOf()) + class New(var publicKey: ByteString, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List, var expireTimer: Int) : Kind() { + internal constructor() : this(ByteString.EMPTY, "", null, listOf(), listOf(), 0) } /** An encryption key pair encrypted for each member individually. * @@ -88,10 +89,11 @@ class ClosedGroupControlMessage() : ControlMessage() { val publicKey = closedGroupControlMessageProto.publicKey ?: return null val name = closedGroupControlMessageProto.name ?: return null val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null + val expireTimer = closedGroupControlMessageProto.expireTimer try { val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) - kind = Kind.New(publicKey, name, encryptionKeyPair, closedGroupControlMessageProto.membersList, closedGroupControlMessageProto.adminsList) + kind = Kind.New(publicKey, name, encryptionKeyPair, closedGroupControlMessageProto.membersList, closedGroupControlMessageProto.adminsList, expireTimer) } catch (e: Exception) { Log.w(TAG, "Couldn't parse key pair from proto: $encryptionKeyPairAsProto.") return null @@ -140,9 +142,10 @@ class ClosedGroupControlMessage() : ControlMessage() { val encryptionKeyPair = SignalServiceProtos.KeyPair.newBuilder() encryptionKeyPair.publicKey = ByteString.copyFrom(kind.encryptionKeyPair!!.publicKey.serialize().removing05PrefixIfNeeded()) encryptionKeyPair.privateKey = ByteString.copyFrom(kind.encryptionKeyPair!!.privateKey.serialize()) - closedGroupControlMessage.encryptionKeyPair = encryptionKeyPair.build() + closedGroupControlMessage.encryptionKeyPair = encryptionKeyPair.build() closedGroupControlMessage.addAllMembers(kind.members) closedGroupControlMessage.addAllAdmins(kind.admins) + closedGroupControlMessage.expireTimer = kind.expireTimer } is Kind.EncryptionKeyPair -> { closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index 6f8b0a6647..c7dec83d19 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -11,8 +11,10 @@ import org.session.libsession.messaging.sending_receiving.MessageSender.Error import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 import org.session.libsession.utilities.Address +import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.Curve import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.utilities.guava.Optional @@ -50,7 +52,7 @@ fun MessageSender.create(name: String, members: Collection): Promise) Log.d("Loki", "Can't add members to nonexistent closed group.") throw Error.NoThread } + // Get expiration timer value for the group + val recipient = Recipient.from(context, fromSerialized(groupID), false) + val expireTimer = recipient.expireMessages if (membersToAdd.isEmpty()) { Log.d("Loki", "Invalid closed group update.") throw Error.InvalidClosedGroupUpdate @@ -155,7 +160,7 @@ fun MessageSender.addMembers(groupPublicKey: String, membersToAdd: List) send(closedGroupControlMessage, Address.fromSerialized(groupID)) // Send closed group update messages to any new members individually for (member in membersToAdd) { - val closedGroupNewKind = ClosedGroupControlMessage.Kind.New(ByteString.copyFrom(Hex.fromStringCondensed(groupPublicKey)), name, encryptionKeyPair, membersAsData, adminsAsData) + val closedGroupNewKind = ClosedGroupControlMessage.Kind.New(ByteString.copyFrom(Hex.fromStringCondensed(groupPublicKey)), name, encryptionKeyPair, membersAsData, adminsAsData, expireTimer) val closedGroupControlMessage = ClosedGroupControlMessage(closedGroupNewKind) closedGroupControlMessage.sentTimestamp = sentTime send(closedGroupControlMessage, Address.fromSerialized(member)) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 653492138e..bb6b5ada67 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -121,7 +121,7 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) { for (closedGroup in message.closedGroups) { if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) continue handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, closedGroup.publicKey, closedGroup.name, - closedGroup.encryptionKeyPair!!, closedGroup.members, closedGroup.admins, message.sentTimestamp!!) + closedGroup.encryptionKeyPair!!, closedGroup.members, closedGroup.admins, message.sentTimestamp!!, 0) } val allV2OpenGroups = storage.getAllV2OpenGroups().map { it.value.joinURL } for (openGroup in message.openGroups) { @@ -256,10 +256,11 @@ private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMess val groupPublicKey = kind.publicKey.toByteArray().toHexString() val members = kind.members.map { it.toByteArray().toHexString() } val admins = kind.admins.map { it.toByteArray().toHexString() } - handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, groupPublicKey, kind.name, kind.encryptionKeyPair!!, members, admins, message.sentTimestamp!!) + val expireTimer = kind.expireTimer + handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, groupPublicKey, kind.name, kind.encryptionKeyPair!!, members, admins, message.sentTimestamp!!, expireTimer) } -private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: List, admins: List, formationTimestamp: Long) { +private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: List, admins: List, formationTimestamp: Long, expireTimer: Int) { val context = MessagingModuleConfiguration.shared.context val storage = MessagingModuleConfiguration.shared.storage // Create the group @@ -289,6 +290,8 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli storage.addClosedGroupPublicKey(groupPublicKey) // Store the encryption key pair storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey) + // Set expiration timer + storage.setExpirationTimer(groupID, expireTimer) // Notify the PN server PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, storage.getUserPublicKey()!!) // Start polling