mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-30 13:35:18 +00:00
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
This commit is contained in:
parent
3fcd972c2a
commit
d2dc86de88
@ -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 grouped = members.groupBy { it.role }
|
||||
grouped.forEach { (role, members) ->
|
||||
if (members.isEmpty()) return@forEach
|
||||
|
||||
val toDeleteQuery = "$GROUP_ID = ? AND $ROLE = ?"
|
||||
val toDeleteArgs = arrayOf(members.first().groupId, role.name)
|
||||
|
||||
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()
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
fun clearGroupMemberRoles(groupId: String) {
|
||||
writableDatabase.beginTransaction()
|
||||
try {
|
||||
val query = "$GROUP_ID = ?"
|
||||
val args = arrayOf(groupId)
|
||||
writableDatabase.delete(TABLE_NAME, 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…
Reference in New Issue
Block a user