Update verification signaling & handing

fix encryption hindering verification
This commit is contained in:
valere 2023-01-12 12:06:57 +01:00
parent 02dc13e38d
commit 3f29c55479
15 changed files with 405 additions and 325 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3f303e8830bb4bd7005b2a166118d7771ed07259822ebb6f888abb0ed459f0cc
size 50458247
oid sha256:a5b58eecbbaa8354901a57c7df727eb2ad6e401dd286ecf049d7a438788ac6ef
size 16077430

View File

@ -17,6 +17,14 @@
package org.matrix.android.sdk.common
import android.util.Log
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.amshove.kluent.fail
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
@ -38,8 +46,13 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.dbgState
import org.matrix.android.sdk.api.session.crypto.verification.getRequest
import org.matrix.android.sdk.api.session.crypto.verification.getTransaction
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
@ -359,99 +372,153 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
}
suspend fun verifySASCrossSign(alice: Session, bob: Session, roomId: String) {
val scope = CoroutineScope(SupervisorJob())
assertTrue(alice.cryptoService().crossSigningService().canCrossSign())
assertTrue(bob.cryptoService().crossSigningService().canCrossSign())
val aliceVerificationService = alice.cryptoService().verificationService()
val bobVerificationService = bob.cryptoService().verificationService()
val localId = UUID.randomUUID().toString()
val bobSeesVerification = CompletableDeferred<PendingVerificationRequest>()
scope.launch(Dispatchers.IO) {
bobVerificationService.requestEventFlow()
.cancellable()
.collect {
val request = it.getRequest()
if (request != null) {
bobSeesVerification.complete(request)
return@collect cancel()
}
}
}
val aliceReady = CompletableDeferred<PendingVerificationRequest>()
scope.launch(Dispatchers.IO) {
aliceVerificationService.requestEventFlow()
.cancellable()
.collect {
val request = it.getRequest()
if (request?.state == EVerificationState.Ready) {
aliceReady.complete(request)
return@collect cancel()
}
}
}
val bobReady = CompletableDeferred<PendingVerificationRequest>()
scope.launch(Dispatchers.IO) {
bobVerificationService.requestEventFlow()
.cancellable()
.collect {
val request = it.getRequest()
if (request?.state == EVerificationState.Ready) {
bobReady.complete(request)
return@collect cancel()
}
}
}
val requestID = aliceVerificationService.requestKeyVerificationInDMs(
localId = localId,
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
otherUserId = bob.myUserId,
roomId = roomId
).transactionId
testHelper.retryWithBackoff(
onFail = {
fail("Bob should see an incoming request from alice")
}
) {
bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull {
it.otherDeviceId == alice.sessionParams.deviceId
} != null
}
val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first {
it.otherDeviceId == alice.sessionParams.deviceId
}
Timber.v("#TEST Incoming request is $incomingRequest")
Timber.v("#TEST let bob ready the verification with SAS method")
bobSeesVerification.await()
bobVerificationService.readyPendingVerification(
listOf(VerificationMethod.SAS),
alice.myUserId,
incomingRequest.transactionId
requestID
)
aliceReady.await()
bobReady.await()
// wait for it to be readied
testHelper.retryWithBackoff(
onFail = {
fail("Alice should see the verification in ready state")
}
) {
val outgoingRequest = aliceVerificationService.getExistingVerificationRequest(bob.myUserId, requestID)
outgoingRequest?.state == EVerificationState.Ready
val bobCode = CompletableDeferred<SasVerificationTransaction>()
scope.launch(Dispatchers.IO) {
bobVerificationService.requestEventFlow()
.cancellable()
.collect {
val transaction = it.getTransaction()
Timber.d("#TEST flow ${bob.myUserId.take(5)} ${transaction?.transactionId}|${transaction?.dbgState()}")
val tx = transaction as? SasVerificationTransaction
if (tx?.state() == SasTransactionState.SasShortCodeReady) {
bobCode.complete(tx)
return@collect cancel()
}
if (it.getRequest()?.state == EVerificationState.Cancelled) {
bobCode.completeExceptionally(AssertionError("Request as been cancelled"))
return@collect cancel()
}
}
}
val aliceCode = CompletableDeferred<SasVerificationTransaction>()
scope.launch(Dispatchers.IO) {
aliceVerificationService.requestEventFlow()
.cancellable()
.collect {
val transaction = it.getTransaction()
Timber.d("#TEST flow ${alice.myUserId.take(5)} ${transaction?.transactionId}|${transaction?.dbgState()}")
val tx = transaction as? SasVerificationTransaction
if (tx?.state() == SasTransactionState.SasShortCodeReady) {
aliceCode.complete(tx)
return@collect cancel()
}
if (it.getRequest()?.state == EVerificationState.Cancelled) {
aliceCode.completeExceptionally(AssertionError("Request as been cancelled"))
return@collect cancel()
}
}
}
Timber.v("#TEST let alice start the verification")
aliceVerificationService.startKeyVerification(
val id = aliceVerificationService.startKeyVerification(
VerificationMethod.SAS,
bob.myUserId,
requestID,
)
Timber.v("#TEST alice started: $id")
// we should reach SHOW SAS on both
var alicePovTx: SasVerificationTransaction? = null
var bobPovTx: SasVerificationTransaction? = null
val bobTx = bobCode.await()
val aliceTx = aliceCode.await()
assertEquals("SAS code do not match", aliceTx.getDecimalCodeRepresentation()!!, bobTx.getDecimalCodeRepresentation())
testHelper.retryWithBackoff(
onFail = {
fail("Alice should should see a verification code")
}
) {
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID)
as? SasVerificationTransaction
Log.v("TEST", "== alicePovTx id:${requestID} is ${alicePovTx?.state()}")
alicePovTx?.getDecimalCodeRepresentation() != null
val aliceDone = CompletableDeferred<Unit>()
scope.launch(Dispatchers.IO) {
aliceVerificationService.requestEventFlow()
.cancellable()
.collect {
val request = it.getRequest()
if (request?.state == EVerificationState.Done) {
aliceDone.complete(Unit)
return@collect cancel()
}
}
}
// wait for alice to get the ready
testHelper.retryWithBackoff(
onFail = {
fail("Bob should should see a verification code")
}
) {
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID)
as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is ${bobPovTx?.state()}")
// bobPovTx?.state == VerificationTxState.ShortCodeReady
bobPovTx?.getDecimalCodeRepresentation() != null
val bobDone = CompletableDeferred<Unit>()
scope.launch(Dispatchers.IO) {
bobVerificationService.requestEventFlow()
.cancellable()
.collect {
val request = it.getRequest()
if (request?.state == EVerificationState.Done) {
bobDone.complete(Unit)
return@collect cancel()
}
}
}
bobTx.userHasVerifiedShortCode()
aliceTx.userHasVerifiedShortCode()
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
bobDone.await()
aliceDone.await()
bobPovTx!!.userHasVerifiedShortCode()
alicePovTx!!.userHasVerifiedShortCode()
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId)
testHelper.retryWithBackoff {
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
}
testHelper.retryWithBackoff {
bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId)
}
scope.cancel()
}
suspend fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData {

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto
import android.util.Log
import androidx.test.filters.LargeTest
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.delay
import org.amshove.kluent.fail
import org.amshove.kluent.internal.assertEquals
@ -44,6 +45,8 @@ import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
@ -91,7 +94,7 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice is sending the message")
val text = "This is my message"
val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text)
val sentEventId: String? = sendMessageInRoom(aliceRoomPOV, text)
Assert.assertTrue("Message should be sent", sentEventId != null)
// All should be able to decrypt
@ -140,7 +143,7 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice sends a new message")
val secondMessage = "2 This is my message"
val secondSentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, secondMessage)
val secondSentEventId: String? = sendMessageInRoom(aliceRoomPOV, secondMessage)
// new members should be able to decrypt it
newAccount.forEach { otherSession ->
@ -203,7 +206,7 @@ class E2eeSanityTests : InstrumentedTest {
val sentEventIds = mutableListOf<String>()
val messagesText = listOf("1. Hello", "2. Bob", "3. Good morning")
messagesText.forEach { text ->
val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also {
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
sentEventIds.add(it)
}
@ -318,7 +321,7 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice sends some messages")
messagesText.forEach { text ->
val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also {
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
sentEventIds.add(it)
}
@ -342,45 +345,24 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Create a new session for Bob")
val newBobSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
// ensure first session is aware of the new one
bobSession.cryptoService().downloadKeysIfNeeded(listOf(bobSession.myUserId), true)
// check that new bob can't currently decrypt
Log.v("#E2E TEST", "check that new bob can't currently decrypt")
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
// Try to request
Log.v("#E2E TEST", "Let bob re-request")
sentEventIds.forEach { sentEventId ->
val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
newBobSession.cryptoService().reRequestRoomKeyForEvent(event)
}
// Ensure that new bob still can't decrypt (keys must have been withheld)
//
// Log.v("#E2E TEST", "Let bob re-request")
// sentEventIds.forEach { sentEventId ->
// val megolmSessionId = newBobSession.getRoom(e2eRoomID)!!
// .getTimelineEvent(sentEventId)!!
// .root.content.toModel<EncryptedEventContent>()!!.sessionId
// testHelper.retryPeriodically {
// val aliceReply = newBobSession.cryptoService().getOutgoingRoomKeyRequests()
// .first {
// it.sessionId == megolmSessionId &&
// it.roomId == e2eRoomID
// }
// .results.also {
// Log.w("##TEST", "result list is $it")
// }
// .firstOrNull { it.userId == aliceSession.myUserId }
// ?.result
// aliceReply != null &&
// aliceReply is RequestResult.Failure &&
// WithHeldCode.UNAUTHORISED == aliceReply.code
// }
// val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
// newBobSession.cryptoService().reRequestRoomKeyForEvent(event)
// }
// */
Log.v("#E2E TEST", "Should not be able to decrypt as not verified")
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
//
// Log.v("#E2E TEST", "Should not be able to decrypt as not verified")
// cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
// Now mark new bob session as verified
@ -422,7 +404,7 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice sends some messages")
firstMessage.let { text ->
firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
firstEventId = sendMessageInRoom(aliceRoomPOV, text)!!
testHelper.retryWithBackoff {
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
@ -448,7 +430,7 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice sends some messages")
secondMessage.let { text ->
secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
secondEventId = sendMessageInRoom(aliceRoomPOV, text)!!
testHelper.retryWithBackoff {
val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
@ -511,26 +493,30 @@ class E2eeSanityTests : InstrumentedTest {
}
}
private suspend fun sendMessageInRoom(testHelper: CommonTestHelper, aliceRoomPOV: Room, text: String): String? {
var sentEventId: String? = null
private suspend fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
aliceRoomPOV.sendService().sendTextMessage(text)
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
timeline.start()
testHelper.retryWithBackoff {
val decryptedMsg = timeline.getSnapshot()
.filter { it.root.getClearType() == EventType.MESSAGE }
.also { list ->
val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
Log.v("#E2E TEST", "Timeline snapshot is $message")
}
.filter { it.root.sendState == SendState.SYNCED }
.firstOrNull { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(text) == true }
sentEventId = decryptedMsg?.eventId
decryptedMsg != null
}
timeline.dispose()
return sentEventId
val messageSent = CompletableDeferred<String>()
timeline.addListener(object : Timeline.Listener {
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val decryptedMsg = timeline.getSnapshot()
.filter { it.root.getClearType() == EventType.MESSAGE }
.also { list ->
val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
Log.v("#E2E TEST", "Timeline snapshot is $message")
}
.filter { it.root.sendState == SendState.SYNCED }
.firstOrNull { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(text) == true }
if (decryptedMsg != null) {
timeline.dispose()
messageSent.complete(decryptedMsg.eventId)
}
}
})
return messageSent.await()
}
/**
@ -646,7 +632,7 @@ class E2eeSanityTests : InstrumentedTest {
val roomFromAlicePOV = aliceSession.getRoom(cryptoTestData.roomId)!!
Timber.v("#TEST: Send a first message that should be withheld")
val sentEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "Hello")!!
val sentEvent = sendMessageInRoom(roomFromAlicePOV, "Hello")!!
// wait for it to be synced back the other side
Timber.v("#TEST: Wait for message to be synced back")
@ -669,7 +655,7 @@ class E2eeSanityTests : InstrumentedTest {
Timber.v("#TEST: Send a second message, outbound session should have rotated and only bob 1rst session should decrypt")
val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!!
val secondEvent = sendMessageInRoom(roomFromAlicePOV, "World")!!
Timber.v("#TEST: Wait for message to be synced back")
testHelper.retryWithBackoff {
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null

View File

@ -16,6 +16,8 @@
package org.matrix.android.sdk.api.session.crypto.verification
import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeVerification
interface VerificationTransaction {
val method: VerificationMethod
@ -38,3 +40,11 @@ interface VerificationTransaction {
fun isSuccessful(): Boolean
}
internal fun VerificationTransaction.dbgState(): String? {
return when (this) {
is SasVerificationTransaction -> "${this.state()}"
is QrCodeVerification -> "${this.state()}"
else -> "??"
}
}

View File

@ -1,46 +0,0 @@
// /*
// * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//
// package org.matrix.android.sdk.internal.crypto
//
// import org.matrix.android.sdk.api.logger.LoggerTag
// import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult
// import org.matrix.android.sdk.api.session.events.model.Content
// import org.matrix.android.sdk.api.session.events.model.EventType
// import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
// import org.matrix.android.sdk.internal.util.time.Clock
// import timber.log.Timber
// import javax.inject.Inject
//
// private val loggerTag = LoggerTag("EncryptEventContentUseCase", LoggerTag.CRYPTO)
//
// internal class EncryptEventContentUseCase @Inject constructor(
// private val olmDevice: MXOlmDevice,
// private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
// private val clock: Clock) {
//
// suspend operator fun invoke(
// eventContent: Content,
// eventType: String,
// roomId: String): MXEncryptEventContentResult {
// val t0 = clock.epochMillis()
// ensureOlmSessionsForDevicesAction.handle()
// prepareToEncrypt(roomId, ensureAllMembersAreLoaded = false)
// val content = olmMachine.encrypt(roomId, eventType, eventContent)
// Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
// return MXEncryptEventContentResult(content, EventType.ENCRYPTED)
// }
// }

View File

@ -22,45 +22,50 @@ import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationEvent
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.dbgState
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber
import javax.inject.Inject
@SessionScope
internal class VerificationListenersHolder @Inject constructor(
private val coroutineDispatchers: MatrixCoroutineDispatchers
coroutineDispatchers: MatrixCoroutineDispatchers,
@UserId myUserId: String,
) {
val myUserId = myUserId.take(5)
val scope = CoroutineScope(SupervisorJob() + coroutineDispatchers.dmVerif)
val eventFlow = MutableSharedFlow<VerificationEvent>(extraBufferCapacity = 20, onBufferOverflow = BufferOverflow.SUSPEND)
fun dispatchTxAdded(tx: VerificationTransaction) {
scope.launch {
Timber.v("## SAS [$myUserId] dispatchTxAdded txId:${tx.transactionId} | ${tx.dbgState()}")
eventFlow.emit(VerificationEvent.TransactionAdded(tx))
}
}
fun dispatchTxUpdated(tx: VerificationTransaction) {
scope.launch {
Timber.v("## SAS dispatchTxUpdated txId:${tx.transactionId} $tx")
Timber.v("## SAS [$myUserId] dispatchTxUpdated txId:${tx.transactionId} | ${tx.dbgState()}")
eventFlow.emit(VerificationEvent.TransactionUpdated(tx))
}
}
fun dispatchRequestAdded(verificationRequest: PendingVerificationRequest) {
fun dispatchRequestAdded(verificationRequest: VerificationRequest) {
scope.launch {
Timber.v("## SAS dispatchRequestAdded txId:${verificationRequest.transactionId} $verificationRequest")
eventFlow.emit(VerificationEvent.RequestAdded(verificationRequest))
Timber.v("## SAS [$myUserId] dispatchRequestAdded txId:${verificationRequest.flowId()} state:${verificationRequest.innerState()}")
eventFlow.emit(VerificationEvent.RequestAdded(verificationRequest.toPendingVerificationRequest()))
}
}
fun dispatchRequestUpdated(verificationRequest: PendingVerificationRequest) {
Timber.v("## SAS dispatchRequestUpdated txId:${verificationRequest.transactionId} $verificationRequest")
fun dispatchRequestUpdated(verificationRequest: VerificationRequest) {
scope.launch {
eventFlow.emit(VerificationEvent.RequestUpdated(verificationRequest))
Timber.v("## SAS [$myUserId] dispatchRequestUpdated txId:${verificationRequest.flowId()} state:${verificationRequest.innerState()}")
eventFlow.emit(VerificationEvent.RequestUpdated(verificationRequest.toPendingVerificationRequest()))
}
}
}

View File

@ -20,6 +20,8 @@ import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber
import javax.inject.Inject
@ -36,9 +38,24 @@ internal class EncryptEventContentUseCase @Inject constructor(
eventType: String,
roomId: String): MXEncryptEventContentResult {
val t0 = clock.epochMillis()
prepareToEncrypt(roomId, ensureAllMembersAreLoaded = false)
/**
* When using in-room messages and the room has encryption enabled,
* clients should ensure that encryption does not hinder the verification.
* For example, if the verification messages are encrypted, clients must ensure that all the recipients
* unverified devices receive the keys necessary to decrypt the messages,
* even if they would normally not be given the keys to decrypt messages in the room.
*/
val shouldSendToUnverified = isVerificationEvent(eventType, eventContent)
prepareToEncrypt(roomId, ensureAllMembersAreLoaded = false, forceDistributeToUnverified = shouldSendToUnverified)
val content = olmMachine.encrypt(roomId, eventType, eventContent)
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
return MXEncryptEventContentResult(content, EventType.ENCRYPTED)
}
private fun isVerificationEvent(eventType: String, eventContent: Content) =
EventType.isVerificationEvent(eventType) ||
(eventType == EventType.MESSAGE &&
eventContent.get(MessageContent.MSG_TYPE_JSON_KEY) == MessageType.MSGTYPE_VERIFICATION_REQUEST)
}

View File

@ -298,15 +298,19 @@ internal class OlmMachine @Inject constructor(
return ToDeviceSyncResponse(events = response)
}
//
// suspend fun receiveUnencryptedVerificationEvent(roomId: String, event: Event) = withContext(coroutineDispatchers.io) {
// val adapter = moshi
// .adapter(Event::class.java)
// val serializedEvent = adapter.toJson(event)
// inner.receiveUnencryptedVerificationEvent(serializedEvent, roomId)
// }
suspend fun receiveUnencryptedVerificationEvent(roomId: String, event: Event) = withContext(coroutineDispatchers.io) {
suspend fun receiveVerificationEvent(roomId: String, event: Event) = withContext(coroutineDispatchers.io) {
val adapter = moshi
.newBuilder()
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
.build()
.adapter(Event::class.java)
val serializedEvent = adapter.toJson(event)
inner.receiveUnencryptedVerificationEvent(serializedEvent, roomId)
inner.receiveVerificationEvent(serializedEvent, roomId)
}
/**

View File

@ -57,7 +57,7 @@ internal class PrepareToEncryptUseCase @Inject constructor(
private val keyClaimLock: Mutex = Mutex()
private val roomKeyShareLocks: ConcurrentHashMap<String, Mutex> = ConcurrentHashMap()
suspend operator fun invoke(roomId: String, ensureAllMembersAreLoaded: Boolean) {
suspend operator fun invoke(roomId: String, ensureAllMembersAreLoaded: Boolean, forceDistributeToUnverified: Boolean = false) {
withContext(coroutineDispatchers.crypto) {
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
// Ensure to load all room members
@ -76,7 +76,7 @@ internal class PrepareToEncryptUseCase @Inject constructor(
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
throw IllegalArgumentException("Missing algorithm")
}
preshareRoomKey(roomId, userIds)
preshareRoomKey(roomId, userIds, forceDistributeToUnverified)
}
}
@ -84,7 +84,7 @@ internal class PrepareToEncryptUseCase @Inject constructor(
return cryptoStore.getRoomAlgorithm(roomId)
}
private suspend fun preshareRoomKey(roomId: String, roomMembers: List<String>) {
private suspend fun preshareRoomKey(roomId: String, roomMembers: List<String>, forceDistributeToUnverified: Boolean) {
claimMissingKeys(roomMembers)
val keyShareLock = roomKeyShareLocks.getOrPut(roomId) { Mutex() }
var sharedKey = false
@ -97,7 +97,12 @@ internal class PrepareToEncryptUseCase @Inject constructor(
}
val settings = EncryptionSettings(
algorithm = EventEncryptionAlgorithm.MEGOLM_V1_AES_SHA2,
onlyAllowTrustedDevices = info.blacklistUnverifiedDevices,
onlyAllowTrustedDevices = if (forceDistributeToUnverified) {
false
} else {
cryptoStore.getGlobalBlacklistUnverifiedDevices() ||
info.blacklistUnverifiedDevices
},
rotationPeriod = info.rotationPeriodMs.toULong(),
rotationPeriodMsgs = info.rotationPeriodMsgs.toULong(),
historyVisibility = if (info.shouldShareHistory) {

View File

@ -109,7 +109,7 @@ private val loggerTag = LoggerTag("RustCryptoService", LoggerTag.CRYPTO)
@SessionScope
internal class RustCryptoService @Inject constructor(
@UserId private val userId: String,
@UserId private val myUserId: String,
@DeviceId private val deviceId: String,
// the crypto store
private val cryptoStore: IMXCryptoStore,
@ -167,7 +167,7 @@ internal class RustCryptoService @Inject constructor(
val params = SetDeviceNameTask.Params(deviceId, deviceName)
setDeviceNameTask.execute(params)
try {
downloadKeysIfNeeded(listOf(userId), true)
downloadKeysIfNeeded(listOf(myUserId), true)
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).w(failure, "setDeviceName: Failed to refresh of crypto device")
}
@ -257,7 +257,7 @@ internal class RustCryptoService @Inject constructor(
setRustLogger()
Timber.tag(loggerTag.value).v(
"## CRYPTO | Successfully started up an Olm machine for " +
"$userId, $deviceId, identity keys: ${this.olmMachine.identityKeys()}"
"$myUserId, $deviceId, identity keys: ${this.olmMachine.identityKeys()}"
)
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).v("Failed create an Olm machine: $throwable")
@ -342,7 +342,7 @@ internal class RustCryptoService @Inject constructor(
}
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
return getLiveCryptoDeviceInfo(listOf(userId))
return getLiveCryptoDeviceInfo(listOf(myUserId))
}
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
@ -350,8 +350,8 @@ internal class RustCryptoService @Inject constructor(
}
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
return olmMachine.getLiveDevices(listOf(userId)).map {
it.filter { it.userId == userId }
return olmMachine.getLiveDevices(listOf(myUserId)).map {
it.filter { it.userId == myUserId }
}
}
@ -609,7 +609,7 @@ internal class RustCryptoService @Inject constructor(
// Notify the our listeners about room keys so decryption is retried.
toDeviceEvents.events.orEmpty().forEach { event ->
Timber.tag(loggerTag.value).d("Processed ToDevice event msgid:${event.toDeviceTracingId()} id:${event.eventId} type:${event.type}")
Timber.tag(loggerTag.value).d("[${myUserId.take(7)}|${deviceId}] Processed ToDevice event msgid:${event.toDeviceTracingId()} id:${event.eventId} type:${event.type}")
if (event.getClearType() == EventType.ENCRYPTED) {
// rust failed to decrypt it
@ -832,7 +832,7 @@ internal class RustCryptoService @Inject constructor(
* ========================================================================================== */
override fun toString(): String {
return "DefaultCryptoService of $userId ($deviceId)"
return "DefaultCryptoService of $myUserId ($deviceId)"
}
override fun getOutgoingRoomKeyRequests(): List<OutgoingKeyRequest> {

View File

@ -66,8 +66,10 @@ import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.DEFAULT_REQUEST_RETRY_COUNT
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.session.room.send.SendResponse
import org.matrix.rustcomponents.sdk.crypto.OutgoingVerificationRequest
import org.matrix.rustcomponents.sdk.crypto.Request
@ -77,6 +79,8 @@ import timber.log.Timber
import javax.inject.Inject
internal class RequestSender @Inject constructor(
@UserId
private val myUserId: String,
private val sendToDeviceTask: SendToDeviceTask,
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask,
private val uploadKeysTask: UploadKeysTask,
@ -94,8 +98,9 @@ internal class RequestSender @Inject constructor(
private val getRoomSessionsDataTask: GetRoomSessionsDataTask,
private val getRoomSessionDataTask: GetRoomSessionDataTask,
private val moshi: Moshi,
private val cryptoCoroutineScope: CoroutineScope,
cryptoCoroutineScope: CoroutineScope,
private val rateLimiter: PerSessionBackupQueryRateLimiter,
private val localEchoRepository: LocalEchoRepository
) {
private val scope = CoroutineScope(
@ -136,7 +141,13 @@ internal class RequestSender @Inject constructor(
}
private suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom, retryCount: Int): SendResponse {
return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId, retryCount)
return sendRoomMessage(
eventType = request.eventType,
roomId = request.roomId,
content = request.content,
transactionId = request.requestId,
retryCount = retryCount
)
}
suspend fun sendRoomMessage(request: Request.RoomMessage, retryCount: Int = DEFAULT_REQUEST_RETRY_COUNT): String {
@ -153,7 +164,13 @@ internal class RequestSender @Inject constructor(
): SendResponse {
val paramsAdapter = moshi.adapter<Content>(Map::class.java)
val jsonContent = paramsAdapter.fromJson(content)
val event = Event(eventType, transactionId, jsonContent, roomId = roomId)
val event = Event(
senderId = myUserId,
type = eventType,
eventId = transactionId,
content = jsonContent,
roomId = roomId)
localEchoRepository.createLocalEcho(event)
val params = SendVerificationMessageTask.Params(event, retryCount)
return sendVerificationMessageTask.get().execute(params)
}

View File

@ -80,19 +80,25 @@ internal class RustVerificationService @Inject constructor(
* All verification related events should be forwarded through this method to
* the verification service.
*
* If the verification event is not encrypted it should be provided to the olmMachine.
* Otherwise events are at this point already handled by the rust-sdk through the receival
* of the to-device events and the decryption of room events. In this case this method mainly just
* fetches the appropriate rust object that will be created or updated by the event and
* This method mainly just fetches the appropriate rust object that will be created or updated by the event and
* dispatches updates to our listeners.
*/
internal suspend fun onEvent(roomId: String?, event: Event) {
if (roomId != null && !event.isEncrypted()) {
if (roomId != null && event.unsignedData?.transactionId == null) {
if (isVerificationEvent(event)) {
try {
olmMachine.receiveUnencryptedVerificationEvent(roomId, event)
val clearEvent = if (event.isEncrypted()) {
event.copy(
content = event.getDecryptedContent(),
type = event.getDecryptedType(),
roomId = roomId
)
} else {
event
}
olmMachine.receiveVerificationEvent(roomId, clearEvent)
} catch (failure: Throwable) {
Timber.w(failure, "Failed to receiveUnencryptedVerificationEvent")
Timber.w(failure, "Failed to receiveUnencryptedVerificationEvent ${failure.message}")
}
}
}
@ -111,8 +117,8 @@ internal class RustVerificationService @Inject constructor(
}
private fun isVerificationEvent(event: Event): Boolean {
val eventType = event.type ?: return false
val eventContent = event.content ?: return false
val eventType = event.getClearType()
val eventContent = event.getClearContent() ?: return false
return EventType.isVerificationEvent(eventType) ||
(eventType == EventType.MESSAGE &&
eventContent[MessageContent.MSG_TYPE_JSON_KEY] == MessageType.MSGTYPE_VERIFICATION_REQUEST)
@ -127,22 +133,23 @@ internal class RustVerificationService @Inject constructor(
/** Dispatch updates after a verification event has been received */
private suspend fun onUpdate(event: Event) {
Timber.v("[${olmMachine.userId().take(6)}] Verification on event ${event.getClearType()}")
val sender = event.senderId ?: return
val flowId = getFlowId(event) ?: return
val flowId = getFlowId(event) ?: return Unit.also {
Timber.w("onUpdate for unknown flowId senderId ${event.getClearType()}")
}
val verificationRequest = olmMachine.getVerificationRequest(sender, flowId)
if (event.getClearType() == EventType.KEY_VERIFICATION_READY) {
// we start the qr here in order to display the code
verificationRequest?.startQrCode()
}
verificationRequest?.dispatchRequestUpdated()
val verification = getExistingTransaction(sender, flowId) ?: return
verificationListenersHolder.dispatchTxUpdated(verification)
}
/** Check if the start event created new verification objects and dispatch updates */
private suspend fun onStart(event: Event) {
if (event.unsignedData?.transactionId != null) return // remote echo
Timber.w("VALR onStart $event")
Timber.w("VALR onStart ${event.eventId}")
val sender = event.senderId ?: return
val flowId = getFlowId(event) ?: return
@ -167,7 +174,6 @@ internal class RustVerificationService @Inject constructor(
Timber.d("## Verification: start for $sender")
// update the request as the start updates it's state
request.dispatchRequestUpdated()
verificationListenersHolder.dispatchTxUpdated(transaction)
} else {
// This didn't originate from a request, so tell our listeners that
@ -187,19 +193,11 @@ internal class RustVerificationService @Inject constructor(
event.getClearContent().toModel<ToDeviceVerificationEvent>()?.transactionId
} ?: return
val sender = event.senderId ?: return
val request = getExistingVerificationRequest(sender, flowId) ?: return
val request = olmMachine.getVerificationRequest(sender, flowId) ?: return
verificationListenersHolder.dispatchRequestAdded(request)
}
// override fun addListener(listener: VerificationService.Listener) {
// verificationListenersHolder.addListener(listener)
// }
//
// override fun removeListener(listener: VerificationService.Listener) {
// verificationListenersHolder.removeListener(listener)
// }
override suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
olmMachine.getDevice(userId, deviceID)?.markAsTrusted()
}
@ -269,14 +267,14 @@ internal class RustVerificationService @Inject constructor(
roomId: String,
localId: String?
): PendingVerificationRequest {
olmMachine.ensureUsersKeys(listOf(otherUserId))
Timber.w("verification: requestKeyVerificationInDMs in room $roomId with $otherUserId")
olmMachine.ensureUsersKeys(listOf(otherUserId), true)
val verification = when (val identity = olmMachine.getIdentity(otherUserId)) {
is UserIdentity -> identity.requestVerification(methods, roomId, localId!!)
is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user")
null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing")
}
Timber.w("##VALR requestKeyVerificationInDMs ${verification.flowId()} > $verification")
return verification.toPendingVerificationRequest()
}
@ -319,13 +317,15 @@ internal class RustVerificationService @Inject constructor(
override suspend fun startKeyVerification(method: VerificationMethod, otherUserId: String, requestId: String): String? {
return if (method == VerificationMethod.SAS) {
val request = olmMachine.getVerificationRequest(otherUserId, requestId)
?: throw IllegalArgumentException("Unknown request with id: $requestId")
val sas = request?.startSasVerification()
val sas = request.startSasVerification()
if (sas != null) {
verificationListenersHolder.dispatchTxAdded(sas)
sas.transactionId
} else {
Timber.w("Failed to start verification with method $method")
null
}
} else {
@ -338,7 +338,6 @@ internal class RustVerificationService @Inject constructor(
?: return null
val qrVerification = matchingRequest.scanQrCode(scannedData)
?: return null
verificationListenersHolder.dispatchRequestUpdated(matchingRequest.toPendingVerificationRequest())
verificationListenersHolder.dispatchTxAdded(qrVerification)
return qrVerification.transactionId
}

View File

@ -83,20 +83,6 @@ internal class SasVerification @AssistedInject constructor(
SasState.Done -> SasTransactionState.Done(true)
is SasState.Cancelled -> SasTransactionState.Cancelled(safeValueOf(state.cancelInfo.cancelCode), state.cancelInfo.cancelledByUs)
}
// refreshData()
// val cancelInfo = inner.cancelInfo
//
// return when {
// cancelInfo != null -> {
// val cancelCode = safeValueOf(cancelInfo.cancelCode)
// SasTransactionState.Cancelled(cancelCode, cancelInfo.cancelledByUs)
// }
// inner.isDone -> SasTransactionState.Done(true)
// inner.haveWeConfirmed -> SasTransactionState.SasAccepted
// inner.canBePresented -> SasTransactionState.SasShortCodeReady
// inner.hasBeenAccepted -> SasTransactionState.SasAccepted
// else -> SasTransactionState.SasStarted
// }
}
/** Get the unique id of this verification */

View File

@ -38,17 +38,17 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeVerification
import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.rustcomponents.sdk.crypto.VerificationRequestListener
import org.matrix.rustcomponents.sdk.crypto.VerificationRequestState
import timber.log.Timber
import org.matrix.rustcomponents.sdk.crypto.VerificationRequest as InnerVerificationRequest
fun InnerVerificationRequest.dbgString(): String {
val that = this
return buildString {
append("InnerVerificationRequest(")
append("isDone=${that.isDone()},")
append("isReady=${that.isReady()},")
append("isPassive=${that.isPassive()},")
append("weStarted=${that.weStarted()},")
append("isCancelled=${that.isCancelled()}")
append("(")
append("flowId=${that.flowId()}")
append("state=${that.state()},")
append(")")
}
}
@ -69,7 +69,7 @@ internal class VerificationRequest @AssistedInject constructor(
private val sasVerificationFactory: SasVerification.Factory,
private val qrCodeVerificationFactory: QrCodeVerification.Factory,
private val clock: Clock,
) {
) : VerificationRequestListener {
private val innerOlmMachine = olmMachine.inner()
@ -78,14 +78,18 @@ internal class VerificationRequest @AssistedInject constructor(
fun create(innerVerificationRequest: InnerVerificationRequest): VerificationRequest
}
init {
innerVerificationRequest.setChangesListener(this)
}
fun startQrCode() {
innerVerificationRequest.startQrVerification()
}
internal fun dispatchRequestUpdated() {
val tx = toPendingVerificationRequest()
verificationListenersHolder.dispatchRequestUpdated(tx)
}
// internal fun dispatchRequestUpdated() {
// val tx = toPendingVerificationRequest()
// verificationListenersHolder.dispatchRequestUpdated(tx)
// }
/** Get the flow ID of this verification request
*
@ -97,6 +101,8 @@ internal class VerificationRequest @AssistedInject constructor(
return innerVerificationRequest.flowId()
}
fun innerState() = innerVerificationRequest.state()
/** The user ID of the other user that is participating in this verification flow */
internal fun otherUser(): String {
return innerVerificationRequest.otherUserId()
@ -108,7 +114,6 @@ internal class VerificationRequest @AssistedInject constructor(
* didn't yet accept the verification flow.
* */
internal fun otherDeviceId(): String? {
refreshData()
return innerVerificationRequest.otherDeviceId()
}
@ -132,13 +137,11 @@ internal class VerificationRequest @AssistedInject constructor(
* verification.
*/
internal fun isReady(): Boolean {
refreshData()
return innerVerificationRequest.isReady()
}
/** Did we advertise that we're able to scan QR codes */
internal fun canScanQrCodes(): Boolean {
refreshData()
return innerVerificationRequest.ourSupportedMethods()?.contains(VERIFICATION_METHOD_QR_CODE_SCAN) ?: false
}
@ -161,12 +164,7 @@ internal class VerificationRequest @AssistedInject constructor(
val request = innerVerificationRequest.accept(stringMethods)
?: return // should throw here?
try {
dispatchRequestUpdated()
requestSender.sendVerificationRequest(request)
// if (innerVerificationRequest.isReady()) {
// activeQRCode = innerVerificationRequest.startQrVerification()
// }
} catch (failure: Throwable) {
cancel()
}
@ -190,9 +188,9 @@ internal class VerificationRequest @AssistedInject constructor(
internal suspend fun startSasVerification(): SasVerification? {
return withContext(coroutineDispatchers.io) {
val result = innerVerificationRequest.startSasVerification()
?: return@withContext null
// sasStartResult.request
// val result = innerOlmMachine.startSasVerification(innerVerificationRequest.otherUserId, innerVerificationRequest.flowId) ?: return@withContext null
?: return@withContext null.also {
Timber.w("Failed to start verification")
}
try {
requestSender.sendVerificationRequest(result.request)
sasVerificationFactory.create(result.sas)
@ -242,69 +240,92 @@ internal class VerificationRequest @AssistedInject constructor(
* The method turns into a noop, if the verification flow has already been cancelled.
*/
internal suspend fun cancel() = withContext(NonCancellable) {
// TODO damir how to add the code?
val request = innerVerificationRequest.cancel() ?: return@withContext
dispatchRequestUpdated()
tryOrNull("Fail to send cancel request") {
requestSender.sendVerificationRequest(request, retryCount = Int.MAX_VALUE)
}
}
/** Fetch fresh data from the Rust side for our verification flow */
private fun refreshData() {
val request = innerOlmMachine.getVerificationRequest(innerVerificationRequest.otherUserId(), innerVerificationRequest.flowId())
if (request != null) {
innerVerificationRequest = request
}
}
private fun state(): EVerificationState {
if (innerVerificationRequest.isCancelled()) {
return if (innerVerificationRequest.cancelInfo()?.cancelCode == CancelCode.AcceptedByAnotherDevice.value) {
EVerificationState.HandledByOtherSession
} else {
EVerificationState.Cancelled
}
}
if (innerVerificationRequest.isPassive()) {
return EVerificationState.HandledByOtherSession
}
if (innerVerificationRequest.isDone()) {
return EVerificationState.Done
}
val started = innerOlmMachine.getVerification(otherUser(), flowId())
if (started != null) {
val asSas = started.asSas()
if (asSas != null) {
return if (asSas.weStarted()) {
EVerificationState.WeStarted
Timber.v("Verification state() ${innerVerificationRequest.state()}")
when (innerVerificationRequest.state()) {
VerificationRequestState.Requested -> {
return if (weStarted()) {
EVerificationState.WaitingForReady
} else {
EVerificationState.Started
EVerificationState.Requested
}
}
val asQR = started.asQr()
if (asQR != null) {
// Timber.w("VALR: weStarted ${asQR.weStarted()}")
// Timber.w("VALR: reciprocated ${asQR.reciprocated()}")
// Timber.w("VALR: isDone ${asQR.isDone()}")
// Timber.w("VALR: hasBeenScanned ${asQR.hasBeenScanned()}")
if (asQR.reciprocated() || asQR.hasBeenScanned()) {
return if (weStarted()) {
EVerificationState.WeStarted
} else EVerificationState.Started
is VerificationRequestState.Ready -> {
val started = innerOlmMachine.getVerification(otherUser(), flowId())
if (started != null) {
val asSas = started.asSas()
if (asSas != null) {
return if (asSas.weStarted()) {
EVerificationState.WeStarted
} else {
EVerificationState.Started
}
}
val asQR = started.asQr()
if (asQR != null) {
if (asQR.reciprocated() || asQR.hasBeenScanned()) {
return if (weStarted()) {
EVerificationState.WeStarted
} else EVerificationState.Started
}
}
}
return EVerificationState.Ready
}
VerificationRequestState.Done -> {
return EVerificationState.Done
}
is VerificationRequestState.Cancelled -> {
return if (innerVerificationRequest.cancelInfo()?.cancelCode == CancelCode.AcceptedByAnotherDevice.value) {
EVerificationState.HandledByOtherSession
} else {
EVerificationState.Cancelled
}
}
}
if (innerVerificationRequest.isReady()) {
return EVerificationState.Ready
}
return if (weStarted()) {
EVerificationState.WaitingForReady
} else {
EVerificationState.Requested
}
//
// if (innerVerificationRequest.isCancelled()) {
// return if (innerVerificationRequest.cancelInfo()?.cancelCode == CancelCode.AcceptedByAnotherDevice.value) {
// EVerificationState.HandledByOtherSession
// } else {
// EVerificationState.Cancelled
// }
// }
// if (innerVerificationRequest.isPassive()) {
// return EVerificationState.HandledByOtherSession
// }
// if (innerVerificationRequest.isDone()) {
// return EVerificationState.Done
// }
//
// val started = innerOlmMachine.getVerification(otherUser(), flowId())
// if (started != null) {
// val asSas = started.asSas()
// if (asSas != null) {
// return if (asSas.weStarted()) {
// EVerificationState.WeStarted
// } else {
// EVerificationState.Started
// }
// }
// val asQR = started.asQr()
// if (asQR != null) {
// if (asQR.reciprocated() || asQR.hasBeenScanned()) {
// return if (weStarted()) {
// EVerificationState.WeStarted
// } else EVerificationState.Started
// }
// }
// }
// if (innerVerificationRequest.isReady()) {
// return EVerificationState.Ready
// }
}
/** Convert the VerificationRequest into a PendingVerificationRequest
@ -317,7 +338,6 @@ internal class VerificationRequest @AssistedInject constructor(
* @return The PendingVerificationRequest that matches data from this VerificationRequest.
*/
internal fun toPendingVerificationRequest(): PendingVerificationRequest {
refreshData()
val cancelInfo = innerVerificationRequest.cancelInfo()
val cancelCode =
if (cancelInfo != null) {
@ -364,6 +384,10 @@ internal class VerificationRequest @AssistedInject constructor(
}
}
override fun onChange(state: VerificationRequestState) {
verificationListenersHolder.dispatchRequestUpdated(this)
}
override fun toString(): String {
return super.toString() + "\n${innerVerificationRequest.dbgString()}"
}

View File

@ -331,22 +331,28 @@ class UserVerificationViewModel @AssistedInject constructor(
val roomId = session.roomService().getExistingDirectRoomWithUser(initialState.otherUserId)
?: session.roomService().createDirectRoom(initialState.otherUserId)
val request = session.cryptoService().verificationService()
.requestKeyVerificationInDMs(
supportedVerificationMethodsProvider.provide(),
initialState.otherUserId,
roomId,
try {
val request = session.cryptoService().verificationService()
.requestKeyVerificationInDMs(
methods = supportedVerificationMethodsProvider.provide(),
otherUserId = initialState.otherUserId,
roomId = roomId,
)
currentTransactionId = request.transactionId
setState {
copy(
pendingRequest = Success(request),
transactionId = request.transactionId
)
currentTransactionId = request.transactionId
Timber.w("VALR started request is $request")
setState {
copy(
pendingRequest = Success(request),
transactionId = request.transactionId
)
}
} catch (failure: Throwable) {
setState {
copy(
pendingRequest = Fail(failure),
)
}
}
}
}