Fix moderator race condition (#975)

* fix: race condition in clear all / add new members

* fix: max width emoji text view on smaller devices

* fix: add extra widths and reduce the original again

* fix: quoted messages for blinded servers, remove unused add group member function

* fix: quote sending and resending using blinded IDs in quotes

* build: update build number
pull/982/head
Harris 2 years ago committed by GitHub
parent 3fcd972c2a
commit d2dc86de88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -156,8 +156,8 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.4'
}
def canonicalVersionCode = 299
def canonicalVersionName = "1.15.3"
def canonicalVersionCode = 302
def canonicalVersionName = "1.15.4"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,

@ -1397,7 +1397,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} else it.individualRecipient.address
QuoteModel(it.dateSent, sender, it.body, false, quotedAttachments)
}
val outgoingTextMessage = OutgoingMediaMessage.from(message, recipient, attachments, quote, linkPreview)
val localQuote = quotedMessage?.let {
val sender =
if (it.isOutgoing) fromSerialized(textSecurePreferences.getLocalNumber()!!)
else it.individualRecipient.address
quote?.copy(author = sender)
}
val outgoingTextMessage = OutgoingMediaMessage.from(message, recipient, attachments, localQuote, linkPreview)
// Clear the input bar
binding?.inputBar?.text = ""
binding?.inputBar?.cancelQuoteDraft()
@ -1713,7 +1719,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun resendMessage(messages: Set<MessageRecord>) {
messages.iterator().forEach { messageRecord ->
ResendMessageUtilities.resend(messageRecord)
ResendMessageUtilities.resend(this, messageRecord, viewModel.blindedPublicKey)
}
endActionMode()
}

@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.utilities.recipients.Recipient
@ -41,7 +42,7 @@ class ConversationViewModel(
get() = openGroup?.let { storage.getServerCapabilities(it.server) } ?: listOf()
val blindedPublicKey: String?
get() = if (openGroup == null || edKeyPair == null) null else {
get() = if (openGroup == null || edKeyPair == null || !serverCapabilities.contains(OpenGroupApi.Capability.BLIND.name.lowercase())) null else {
SodiumUtilities.blindedKeyPair(openGroup!!.publicKey, edKeyPair)?.publicKey?.asBytes
?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString
}

@ -2,24 +2,36 @@ package org.thoughtcrime.securesms.conversation.v2
import android.os.Bundle
import android.view.View
import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityMessageDetailBinding
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.ExpirationUtil
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.IdPrefix
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities
import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.util.DateUtils
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import javax.inject.Inject
@AndroidEntryPoint
class MessageDetailActivity: PassphraseRequiredActionBarActivity() {
private lateinit var binding: ActivityMessageDetailBinding
var messageRecord: MessageRecord? = null
@Inject
lateinit var storage: Storage
// region Settings
companion object {
// Extras
@ -37,9 +49,19 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() {
// so the author of the messages must be the current user.
val author = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!)
messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author)
val threadId = messageRecord!!.threadId
val openGroup = storage.getOpenGroup(threadId)
val blindedKey = openGroup?.let { group ->
val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return@let null
val blindingEnabled = storage.getServerCapabilities(group.server).contains(OpenGroupApi.Capability.BLIND.name.lowercase())
if (blindingEnabled) {
SodiumUtilities.blindedKeyPair(group.publicKey, userEdKeyPair)?.publicKey?.asBytes
?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString
} else null
}
updateContent()
binding.resendButton.setOnClickListener {
ResendMessageUtilities.resend(messageRecord!!)
ResendMessageUtilities.resend(this, messageRecord!!, blindedKey)
finish()
}
}

@ -13,6 +13,7 @@ import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewQuoteBinding
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
import org.thoughtcrime.securesms.database.SessionContactDatabase
@ -69,7 +70,12 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
isOriginalMissing: Boolean, glide: GlideRequests) {
// Author
val author = contactDb.getContactWithSessionID(authorPublicKey)
val authorDisplayName = author?.displayName(Contact.contextForRecipient(thread)) ?: "${authorPublicKey.take(4)}...${authorPublicKey.takeLast(4)}"
val localNumber = TextSecurePreferences.getLocalNumber(context)
val quoteIsLocalUser = localNumber != null && localNumber == author?.sessionID
val authorDisplayName =
if (quoteIsLocalUser) context.getString(R.string.QuoteView_you)
else author?.displayName(Contact.contextForRecipient(thread)) ?: "${authorPublicKey.take(4)}...${authorPublicKey.takeLast(4)}"
binding.quoteViewAuthorTextView.text = authorDisplayName
binding.quoteViewAuthorTextView.setTextColor(getTextColor(isOutgoingMessage))
// Body

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.conversation.v2.utilities
import android.content.Context
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.visible.LinkPreview
import org.session.libsession.messaging.messages.visible.OpenGroupInvitation
@ -7,13 +8,14 @@ import org.session.libsession.messaging.messages.visible.Quote
import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.UpdateMessageData
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
object ResendMessageUtilities {
fun resend(messageRecord: MessageRecord) {
fun resend(context: Context, messageRecord: MessageRecord, userBlindedKey: String?) {
val recipient: Recipient = messageRecord.recipient
val message = VisibleMessage()
message.id = messageRecord.getId()
@ -44,6 +46,9 @@ object ResendMessageUtilities {
}
if (mmsMessageRecord.quote != null) {
message.quote = Quote.from(mmsMessageRecord.quote!!.quoteModel)
if (userBlindedKey != null && messageRecord.quote!!.author.serialize() == TextSecurePreferences.getLocalNumber(context)) {
message.quote!!.publicKey = userBlindedKey
}
}
message.addSignalAttachments(mmsMessageRecord.slideDeck.asAttachments())
}

@ -51,31 +51,31 @@ class GroupMemberDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
return mappings.map { it.role }
}
fun addGroupMember(member: GroupMember) {
fun setGroupMembers(members: List<GroupMember>) {
writableDatabase.beginTransaction()
try {
val values = ContentValues().apply {
put(GROUP_ID, member.groupId)
put(PROFILE_ID, member.profileId)
put(ROLE, member.role.name)
}
val query = "$GROUP_ID = ? AND $PROFILE_ID = ?"
val args = arrayOf(member.groupId, member.profileId)
val grouped = members.groupBy { it.role }
grouped.forEach { (role, members) ->
if (members.isEmpty()) return@forEach
writableDatabase.insertOrUpdate(TABLE_NAME, values, query, args)
writableDatabase.setTransactionSuccessful()
} finally {
writableDatabase.endTransaction()
}
}
val toDeleteQuery = "$GROUP_ID = ? AND $ROLE = ?"
val toDeleteArgs = arrayOf(members.first().groupId, role.name)
fun clearGroupMemberRoles(groupId: String) {
writableDatabase.beginTransaction()
try {
val query = "$GROUP_ID = ?"
val args = arrayOf(groupId)
writableDatabase.delete(TABLE_NAME, query, args)
writableDatabase.setTransactionSuccessful()
writableDatabase.delete(TABLE_NAME, toDeleteQuery, toDeleteArgs)
members.forEach { member ->
val values = ContentValues().apply {
put(GROUP_ID, member.groupId)
put(PROFILE_ID, member.profileId)
put(ROLE, member.role.name)
}
val query = "$GROUP_ID = ? AND $PROFILE_ID = ?"
val args = arrayOf(member.groupId, member.profileId)
writableDatabase.insertOrUpdate(TABLE_NAME, values, query, args)
}
writableDatabase.setTransactionSuccessful()
}
} finally {
writableDatabase.endTransaction()
}

@ -319,12 +319,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return getAllOpenGroups().values.firstOrNull { it.server == server && it.room == room }
}
override fun clearGroupMemberRoles(groupId: String) {
DatabaseComponent.get(context).groupMemberDatabase().clearGroupMemberRoles(groupId)
}
override fun addGroupMemberRole(member: GroupMember) {
DatabaseComponent.get(context).groupMemberDatabase().addGroupMember(member)
override fun setGroupMemberRoles(members: List<GroupMember>) {
DatabaseComponent.get(context).groupMemberDatabase().setGroupMembers(members)
}
override fun isDuplicateMessage(timestamp: Long): Boolean {

@ -104,7 +104,7 @@
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/bodyTopBarrier"
app:layout_constraintStart_toStartOf="parent"
android:maxWidth="300dp"
android:maxWidth="@dimen/max_text_width"
android:paddingHorizontal="12dp"
android:paddingVertical="@dimen/small_spacing"
android:id="@+id/bodyTextView"

@ -11,4 +11,5 @@
<dimen name="album_5_total_height">210dp</dimen>
<dimen name="album_5_cell_size_big">125dp</dimen>
<dimen name="album_5_cell_size_small">83dp</dimen>
<dimen name="max_text_width">240dp</dimen>
</resources>

@ -19,4 +19,5 @@
<dimen name="album_5_total_height">250dp</dimen>
<dimen name="album_5_cell_size_big">149dp</dimen>
<dimen name="album_5_cell_size_small">99dp</dimen>
<dimen name="max_text_width">300dp</dimen>
</resources>

@ -145,5 +145,6 @@
<dimen name="react_with_any_emoji_bottom_sheet_dialog_fragment_min_height">390dp</dimen>
<dimen name="react_with_any_emoji_bottom_sheet_dialog_fragment_tabs_height">40dp</dimen>
<dimen name="max_text_width">200dp</dimen>
</resources>

@ -71,8 +71,7 @@ interface StorageProtocol {
fun hasBackgroundGroupAddJob(groupJoinUrl: String): Boolean
fun setOpenGroupServerMessageID(messageID: Long, serverID: Long, threadID: Long, isSms: Boolean)
fun getOpenGroup(room: String, server: String): OpenGroup?
fun addGroupMemberRole(member: GroupMember)
fun clearGroupMemberRoles(groupId: String)
fun setGroupMemberRoles(members: List<GroupMember>)
// Open Group Public Keys
fun getOpenGroupPublicKey(server: String): String?

@ -231,12 +231,18 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage,
throw MessageReceiver.Error.NoThread
}
val threadRecipient = storage.getRecipientForThread(threadID)
val userBlindedKey = openGroupID?.let {
val openGroup = storage.getOpenGroup(threadID) ?: return@let null
val blindedKey = SodiumUtilities.blindedKeyPair(openGroup.publicKey, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) ?: return@let null
SessionId(
IdPrefix.BLINDED, blindedKey.publicKey.asBytes
).hexString
}
// Update profile if needed
val recipient = Recipient.from(context, Address.fromSerialized(messageSender!!), false)
if (runProfileUpdate) {
val profile = message.profile
val isUserBlindedSender = messageSender == storage.getOpenGroup(threadID)?.publicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId(
IdPrefix.BLINDED, it.publicKey.asBytes).hexString }
val isUserBlindedSender = messageSender == userBlindedKey
if (profile != null && userPublicKey != messageSender && !isUserBlindedSender) {
val profileManager = SSKEnvironment.shared.profileManager
val name = profile.displayName!!
@ -260,7 +266,13 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage,
var quoteModel: QuoteModel? = null
if (message.quote != null && proto.dataMessage.hasQuote()) {
val quote = proto.dataMessage.quote
val author = Address.fromSerialized(quote.author)
val author = if (quote.author == userBlindedKey) {
Address.fromSerialized(userPublicKey!!)
} else {
Address.fromSerialized(quote.author)
}
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
val messageInfo = messageDataProvider.getMessageForQuote(quote.id, author)
quoteModel = if (messageInfo != null) {

@ -134,20 +134,26 @@ class OpenGroupPoller(private val server: String, private val executorService: S
storage.setUserCount(roomToken, server, pollInfo.activeUsers)
// - Moderators
storage.clearGroupMemberRoles(groupId)
pollInfo.details?.moderators?.forEach {
storage.addGroupMemberRole(GroupMember(groupId, it, GroupMemberRole.MODERATOR))
pollInfo.details?.moderators?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.MODERATOR)
})
}
pollInfo.details?.hiddenModerators?.forEach {
storage.addGroupMemberRole(GroupMember(groupId, it, GroupMemberRole.HIDDEN_MODERATOR))
pollInfo.details?.hiddenModerators?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.HIDDEN_MODERATOR)
})
}
// - Admins
pollInfo.details?.admins?.forEach {
storage.addGroupMemberRole(GroupMember(groupId, it, GroupMemberRole.ADMIN))
pollInfo.details?.admins?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.ADMIN)
})
}
pollInfo.details?.hiddenAdmins?.forEach {
storage.addGroupMemberRole(GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN))
pollInfo.details?.hiddenAdmins?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN)
})
}
}

@ -3,7 +3,7 @@ package org.session.libsession.messaging.sending_receiving.quotes
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.utilities.Address
class QuoteModel(val id: Long,
data class QuoteModel(val id: Long,
val author: Address,
val text: String?,
val missing: Boolean,

Loading…
Cancel
Save