diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt index ec4d43f3e..7b7952fd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt @@ -1,16 +1,11 @@ package org.thoughtcrime.securesms.conversation.mutiselect.forward import android.content.Context -import androidx.core.util.Consumer import io.reactivex.rxjava3.core.Single import org.signal.core.util.concurrent.SignalExecutors import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey -import org.thoughtcrime.securesms.contacts.paged.RecipientSearchKey import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.database.identity.IdentityRecordList -import org.thoughtcrime.securesms.database.model.IdentityRecord -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.sharing.MultiShareArgs @@ -20,25 +15,12 @@ import org.whispersystems.libsignal.util.guava.Optional class MultiselectForwardRepository(context: Context) { - private val context = context.applicationContext - class MultiselectForwardResultHandlers( val onAllMessageSentSuccessfully: () -> Unit, val onSomeMessagesFailed: () -> Unit, val onAllMessagesFailed: () -> Unit ) - fun checkForBadIdentityRecords(contactSearchKeys: Set, consumer: Consumer>) { - SignalExecutors.BOUNDED.execute { - val recipients: List = contactSearchKeys - .filterIsInstance() - .map { Recipient.resolved(it.recipientId) } - val identityRecordList: IdentityRecordList = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecords(recipients) - - consumer.accept(identityRecordList.untrustedRecords) - } - } - fun canSelectRecipient(recipientId: Optional): Single { if (!recipientId.isPresent) { return Single.just(true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardViewModel.kt index 097f7ba73..ca818a4c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardViewModel.kt @@ -4,7 +4,9 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey +import org.thoughtcrime.securesms.contacts.paged.RecipientSearchKey import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.mediasend.v2.UntrustedRecords import org.thoughtcrime.securesms.sharing.MultiShareArgs import org.thoughtcrime.securesms.util.livedata.Store @@ -23,7 +25,7 @@ class MultiselectForwardViewModel( store.update { it.copy(stage = MultiselectForwardState.Stage.FirstConfirmation) } } else { store.update { it.copy(stage = MultiselectForwardState.Stage.LoadingIdentities) } - repository.checkForBadIdentityRecords(selectedContacts) { identityRecords -> + UntrustedRecords.checkForBadIdentityRecords(selectedContacts.filterIsInstance(RecipientSearchKey::class.java).toSet()) { identityRecords -> if (identityRecords.isEmpty()) { performSend(additionalMessage, selectedContacts) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt index 3e5c92cb4..5591507da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.components.emoji.EmojiEventListener import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration import org.thoughtcrime.securesms.contacts.paged.ContactSearchState import org.thoughtcrime.securesms.conversation.mutiselect.forward.SearchConfigurationProvider +import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment import org.thoughtcrime.securesms.mediasend.Media @@ -146,13 +147,18 @@ class MediaSelectionActivity : } override fun onSendError(error: Throwable) { - setResult(RESULT_CANCELED) + if (error is UntrustedRecords.UntrustedRecordsException) { + Log.w(TAG, "Send failed due to untrusted identities.") + SafetyNumberChangeDialog.show(supportFragmentManager, error.untrustedRecords) + } else { + setResult(RESULT_CANCELED) - // TODO [alex] - Toast - Log.w(TAG, "Failed to send message.", error) + // TODO [alex] - Toast + Log.w(TAG, "Failed to send message.", error) - finish() - overridePendingTransition(R.anim.stationary, R.anim.camera_slide_to_bottom) + finish() + overridePendingTransition(R.anim.stationary, R.anim.camera_slide_to_bottom) + } } override fun onNoMediaSelected() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt index b16b88281..9f3e17f81 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt @@ -280,17 +280,19 @@ class MediaSelectionViewModel( fun send( selectedContacts: List = emptyList() ): Maybe { - return repository.send( - store.state.selectedMedia, - store.state.editorStateMap, - store.state.quality, - store.state.message, - store.state.transportOption.isSms, - isViewOnceEnabled(), - destination.getRecipientSearchKey(), - if (selectedContacts.isNotEmpty()) selectedContacts else destination.getRecipientSearchKeyList(), - MentionAnnotation.getMentionsFromAnnotations(store.state.message), - store.state.transportOption + return UntrustedRecords.checkForBadIdentityRecords(selectedContacts.toSet()).andThen( + repository.send( + store.state.selectedMedia, + store.state.editorStateMap, + store.state.quality, + store.state.message, + store.state.transportOption.isSms, + isViewOnceEnabled(), + destination.getRecipientSearchKey(), + selectedContacts.ifEmpty { destination.getRecipientSearchKeyList() }, + MentionAnnotation.getMentionsFromAnnotations(store.state.message), + store.state.transportOption + ) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/UntrustedRecords.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/UntrustedRecords.kt new file mode 100644 index 000000000..44e4eeee2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/UntrustedRecords.kt @@ -0,0 +1,46 @@ +package org.thoughtcrime.securesms.mediasend.v2 + +import androidx.core.util.Consumer +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.schedulers.Schedulers +import org.signal.core.util.concurrent.SignalExecutors +import org.thoughtcrime.securesms.contacts.paged.RecipientSearchKey +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.model.IdentityRecord +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.recipients.Recipient + +object UntrustedRecords { + + fun checkForBadIdentityRecords(contactSearchKeys: Set): Completable { + return Completable.fromAction { + val untrustedRecords: List = checkForBadIdentityRecordsSync(contactSearchKeys) + if (untrustedRecords.isNotEmpty()) { + throw UntrustedRecordsException(untrustedRecords) + } + }.subscribeOn(Schedulers.io()) + } + + fun checkForBadIdentityRecords(contactSearchKeys: Set, consumer: Consumer>) { + SignalExecutors.BOUNDED.execute { + consumer.accept(checkForBadIdentityRecordsSync(contactSearchKeys)) + } + } + + private fun checkForBadIdentityRecordsSync(contactSearchKeys: Set): List { + val recipients: List = contactSearchKeys + .map { Recipient.resolved(it.recipientId) } + .map { recipient -> + when { + recipient.isGroup -> recipient.participants + recipient.isDistributionList -> Recipient.resolvedList(SignalDatabase.distributionLists.getMembers(recipient.distributionListId.get())) + else -> listOf(recipient) + } + } + .flatten() + + return ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecords(recipients).untrustedRecords + } + + class UntrustedRecordsException(val untrustedRecords: List) : Throwable() +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendRepository.kt index 4b36306e0..777f776b9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendRepository.kt @@ -2,19 +2,17 @@ package org.thoughtcrime.securesms.mediasend.v2.text.send import android.content.Context import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.schedulers.Schedulers import org.signal.core.util.ThreadUtil import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.contacts.paged.RecipientSearchKey import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.database.identity.IdentityRecordList import org.thoughtcrime.securesms.database.model.StoryType import org.thoughtcrime.securesms.database.model.databaseprotos.StoryTextPost -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.fonts.TextFont import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.linkpreview.LinkPreview +import org.thoughtcrime.securesms.mediasend.v2.UntrustedRecords import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationState import org.thoughtcrime.securesms.mms.OutgoingMediaMessage import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage @@ -35,28 +33,23 @@ class TextStoryPostSendRepository(context: Context) { } fun send(contactSearchKey: Set, textStoryPostCreationState: TextStoryPostCreationState, linkPreview: LinkPreview?): Single { - return checkForBadIdentityRecords(contactSearchKey).flatMap { result -> - if (result is TextStoryPostSendResult.Success) { - performSend(contactSearchKey, textStoryPostCreationState, linkPreview) - } else { - Single.just(result) + return UntrustedRecords + .checkForBadIdentityRecords(contactSearchKey.filterIsInstance(RecipientSearchKey::class.java).toSet()) + .toSingleDefault(TextStoryPostSendResult.Success) + .onErrorReturn { + if (it is UntrustedRecords.UntrustedRecordsException) { + TextStoryPostSendResult.UntrustedRecordsError(it.untrustedRecords) + } else { + TextStoryPostSendResult.Failure + } } - } - } - - private fun checkForBadIdentityRecords(contactSearchKeys: Set): Single { - return Single.fromCallable { - val recipients: List = contactSearchKeys - .filterIsInstance() - .map { Recipient.resolved(it.recipientId) } - val identityRecordList: IdentityRecordList = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecords(recipients) - - if (identityRecordList.untrustedRecords.isNotEmpty()) { - TextStoryPostSendResult.UntrustedRecordsError(identityRecordList.untrustedRecords) - } else { - TextStoryPostSendResult.Success + .flatMap { result -> + if (result is TextStoryPostSendResult.Success) { + performSend(contactSearchKey, textStoryPostCreationState, linkPreview) + } else { + Single.just(result) + } } - }.subscribeOn(Schedulers.io()) } private fun performSend(contactSearchKey: Set, textStoryPostCreationState: TextStoryPostCreationState, linkPreview: LinkPreview?): Single { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendResult.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendResult.kt index 3af21e639..8dbe106b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendResult.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendResult.kt @@ -4,5 +4,6 @@ import org.thoughtcrime.securesms.database.model.IdentityRecord sealed class TextStoryPostSendResult { object Success : TextStoryPostSendResult() + object Failure : TextStoryPostSendResult() data class UntrustedRecordsError(val untrustedRecords: List) : TextStoryPostSendResult() }