Updated the code to ignore outdated legacy group control message changes

This commit is contained in:
Morgan Pretty
2023-06-02 15:35:08 +10:00
parent ff7b7cd0b9
commit cbdcb4ffa1
10 changed files with 131 additions and 49 deletions

View File

@@ -223,4 +223,5 @@ interface StorageProtocol {
// Shared configs
fun notifyConfigUpdates(forConfigObject: ConfigBase)
fun canPerformConfigChange(variant: String, publicKey: String, changeTimestampMs: Long): Boolean
}

View File

@@ -2,6 +2,7 @@ package org.session.libsession.messaging.jobs
import network.loki.messenger.libsession_util.ConfigBase
import network.loki.messenger.libsession_util.ConfigBase.Companion.protoKindFor
import network.loki.messenger.libsession_util.UserGroupsConfig
import nl.komponents.kovenant.functional.bind
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.Destination
@@ -60,6 +61,7 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
val toDeleteHashes = mutableListOf<String>()
// allow null results here so the list index matches configsRequiringPush
val sentTimestamp: Long = SnodeAPI.nowWithOffset
val batchObjects: List<Pair<SharedConfigurationMessage, SnodeAPI.SnodeBatchRequestInfo>?> = configsRequiringPush.map { config ->
val (data, seqNo, obsoleteHashes) = config.push()
toDeleteHashes += obsoleteHashes
@@ -140,7 +142,7 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
Log.d(TAG, "Successfully removed the deleted hashes from ${config.javaClass.simpleName}")
// dump and write config after successful
if (config.needsDump()) { // usually this will be true?
configFactory.persist(config)
configFactory.persist(config, toPushMessage.sentTimestamp ?: sentTimestamp)
}
}
} catch (e: Exception) {

View File

@@ -42,6 +42,7 @@ import org.session.libsignal.crypto.ecc.DjbECPublicKey
import org.session.libsignal.crypto.ecc.ECKeyPair
import org.session.libsignal.messages.SignalServiceGroup
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log
@@ -440,7 +441,14 @@ private fun MessageReceiver.handleClosedGroupControlMessage(message: ClosedGroup
is ClosedGroupControlMessage.Kind.MembersRemoved -> handleClosedGroupMembersRemoved(message)
is ClosedGroupControlMessage.Kind.MemberLeft -> handleClosedGroupMemberLeft(message)
}
if (message.kind !is ClosedGroupControlMessage.Kind.New) {
if (
message.kind !is ClosedGroupControlMessage.Kind.New &&
MessagingModuleConfiguration.shared.storage.canPerformConfigChange(
SharedConfigMessage.Kind.GROUPS.name,
MessagingModuleConfiguration.shared.storage.getUserPublicKey()!!,
message.sentTimestamp!!
)
) {
// update the config
val closedGroupPublicKey = message.getPublicKey()
val storage = MessagingModuleConfiguration.shared.storage
@@ -471,10 +479,24 @@ private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMess
private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: List<String>, admins: List<String>, formationTimestamp: Long, expireTimer: Int) {
val context = MessagingModuleConfiguration.shared.context
val storage = MessagingModuleConfiguration.shared.storage
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
// Create the group
val userPublicKey = storage.getUserPublicKey()!!
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
val groupExists = storage.getGroup(groupID) != null
if (!storage.canPerformConfigChange(SharedConfigMessage.Kind.GROUPS.name, userPublicKey, sentTimestamp)) {
// If the closed group already exists then store the encryption keys (since the config only stores
// the latest key we won't be able to decrypt older messages if we were added to the group within
// the last two weeks and the key has been rotated - unfortunately if the user was added more than
// two weeks ago and the keys were rotated within the last two weeks then we won't be able to decrypt
// messages received before the key rotation)
if (groupExists) {
storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey, sentTimestamp)
storage.updateGroupConfig(groupPublicKey)
}
return
}
// Create the group
if (groupExists) {
// Update the group
if (!storage.isGroupActive(groupPublicKey)) {
@@ -498,7 +520,7 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli
// Set expiration timer
storage.setExpirationTimer(groupID, expireTimer)
// Notify the PN server
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, storage.getUserPublicKey()!!)
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
// Create thread
storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
// Start polling
@@ -569,7 +591,12 @@ private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupCon
val members = group.members.map { it.serialize() }
val admins = group.admins.map { it.serialize() }
val name = kind.name
storage.updateTitle(groupID, name)
// Only update the group in storage if it isn't invalidated by the config state
if (storage.canPerformConfigChange(SharedConfigMessage.Kind.GROUPS.name, userPublicKey!!, message.sentTimestamp!!)) {
storage.updateTitle(groupID, name)
}
// Notify the user
if (userPublicKey == senderPublicKey) {
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
@@ -603,12 +630,16 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo
val updateMembers = kind.members.map { it.toByteArray().toHexString() }
val newMembers = members + updateMembers
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
// Update zombie members in case the added members are zombies
val zombies = storage.getZombieMembers(groupID)
if (zombies.intersect(updateMembers).isNotEmpty()) {
storage.setZombieMembers(groupID, zombies.minus(updateMembers).map { Address.fromSerialized(it) })
// Only update the group in storage if it isn't invalidated by the config state
if (storage.canPerformConfigChange(SharedConfigMessage.Kind.GROUPS.name, userPublicKey, message.sentTimestamp!!)) {
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
// Update zombie members in case the added members are zombies
val zombies = storage.getZombieMembers(groupID)
if (zombies.intersect(updateMembers).isNotEmpty()) {
storage.setZombieMembers(groupID, zombies.minus(updateMembers).map { Address.fromSerialized(it) })
}
}
// Notify the user
@@ -690,14 +721,18 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
Log.d("Loki", "Received a MEMBERS_REMOVED instead of a MEMBERS_LEFT from sender: $senderPublicKey.")
}
val wasCurrentUserRemoved = userPublicKey in removedMembers
// Admin should send a MEMBERS_LEFT message but handled here just in case
if (didAdminLeave || wasCurrentUserRemoved) {
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey, true)
return
} else {
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
// Update zombie members
storage.setZombieMembers(groupID, zombies.minus(removedMembers).map { Address.fromSerialized(it) })
// Only update the group in storage if it isn't invalidated by the config state
if (storage.canPerformConfigChange(SharedConfigMessage.Kind.GROUPS.name, userPublicKey, message.sentTimestamp!!)) {
// Admin should send a MEMBERS_LEFT message but handled here just in case
if (didAdminLeave || wasCurrentUserRemoved) {
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey, true)
return
} else {
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
// Update zombie members
storage.setZombieMembers(groupID, zombies.minus(removedMembers).map { Address.fromSerialized(it) })
}
}
// Notify the user
@@ -746,18 +781,23 @@ private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupCont
val didAdminLeave = admins.contains(senderPublicKey)
val updatedMemberList = members - senderPublicKey
val userLeft = (userPublicKey == senderPublicKey)
if (didAdminLeave || userLeft) {
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey, delete = userLeft)
if (userLeft) {
return
// Only update the group in storage if it isn't invalidated by the config state
if (storage.canPerformConfigChange(SharedConfigMessage.Kind.GROUPS.name, userPublicKey, message.sentTimestamp!!)) {
if (didAdminLeave || userLeft) {
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey, delete = userLeft)
if (userLeft) {
return
}
} else {
storage.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) })
// Update zombie members
val zombies = storage.getZombieMembers(groupID)
storage.setZombieMembers(groupID, zombies.plus(senderPublicKey).map { Address.fromSerialized(it) })
}
} else {
storage.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) })
// Update zombie members
val zombies = storage.getZombieMembers(groupID)
storage.setZombieMembers(groupID, zombies.plus(senderPublicKey).map { Address.fromSerialized(it) })
}
// Notify the user
if (!userLeft) {
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceGroup.Type.QUIT, name, members, admins, message.sentTimestamp!!)

View File

@@ -146,6 +146,7 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
return
}
var latestMessageTimestamp: Long? = null
messages.forEach { (envelope, hash) ->
try {
val (message, _) = MessageReceiver.parse(data = envelope.toByteArray(), openGroupServerID = null)
@@ -155,13 +156,14 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
return@forEach
}
forConfigObject.merge(hash!! to message.data)
latestMessageTimestamp = if ((message.sentTimestamp ?: 0L) > (latestMessageTimestamp ?: 0L)) { message.sentTimestamp } else { latestMessageTimestamp }
} catch (e: Exception) {
Log.e("Loki", e)
}
}
// process new results
if (forConfigObject.needsDump()) {
configFactory.persist(forConfigObject)
configFactory.persist(forConfigObject, latestMessageTimestamp ?: SnodeAPI.nowWithOffset)
}
}

View File

@@ -12,7 +12,8 @@ interface ConfigFactoryProtocol {
val convoVolatile: ConversationVolatileConfig?
val userGroups: UserGroupsConfig?
fun getUserConfigs(): List<ConfigBase>
fun persist(forConfigObject: ConfigBase)
fun persist(forConfigObject: ConfigBase, timestamp: Long)
fun canPerformChange(variant: String, publicKey: String, changeTimestampMs: Long): Boolean
}
interface ConfigFactoryUpdateListener {