Notify other devices of acceptance of verification request

This commit is contained in:
Jorge Martín 2022-05-03 09:47:40 +02:00
parent f7303789a0
commit 123ad87eda
7 changed files with 95 additions and 2 deletions

1
changelog.d/5724.sdk Normal file
View File

@ -0,0 +1 @@
- Notifies other devices when a verification request sent from an Android device is accepted.`

View File

@ -27,11 +27,13 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants
import java.util.concurrent.CountDownLatch
import kotlin.coroutines.Continuation
@ -252,4 +254,48 @@ class VerificationTest : InstrumentedTest {
cryptoTestData.cleanUp(testHelper)
}
@Test
fun test_selfVerificationAcceptedCancelsItForOtherSessions() {
val defaultSessionParams = SessionTestParams(true)
val testHelper = CommonTestHelper(context())
val aliceSessionToVerify = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
val aliceSessionThatVerifies = testHelper.logIntoAccount(aliceSessionToVerify.myUserId, TestConstants.PASSWORD, defaultSessionParams)
val aliceSessionThatReceivesCanceledEvent = testHelper.logIntoAccount(aliceSessionToVerify.myUserId, TestConstants.PASSWORD, defaultSessionParams)
val verificationMethods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW)
val serviceOfVerified = aliceSessionToVerify.cryptoService().verificationService()
val serviceOfVerifier = aliceSessionThatVerifies.cryptoService().verificationService()
val serviceOfUserWhoReceivesCancellation = aliceSessionThatReceivesCanceledEvent.cryptoService().verificationService()
serviceOfVerifier.addListener(object : VerificationService.Listener {
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// Accept verification request
serviceOfVerifier.readyPendingVerification(
verificationMethods,
pr.otherUserId,
pr.transactionId!!,
)
}
})
serviceOfVerified.requestKeyVerification(
methods = verificationMethods,
otherUserId = aliceSessionToVerify.myUserId,
otherDevices = listOfNotNull(aliceSessionThatVerifies.sessionParams.deviceId, aliceSessionThatReceivesCanceledEvent.sessionParams.deviceId),
)
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val requests = serviceOfUserWhoReceivesCancellation.getExistingVerificationRequests(aliceSessionToVerify.myUserId)
requests.any { it.cancelConclusion == CancelCode.AcceptedByAnotherDevice }
}
}
testHelper.signOutAndClose(aliceSessionToVerify)
testHelper.signOutAndClose(aliceSessionThatVerifies)
testHelper.signOutAndClose(aliceSessionThatReceivesCanceledEvent)
}
}

View File

@ -28,7 +28,8 @@ enum class CancelCode(val value: String, val humanReadable: String) {
MismatchedKeys("m.key_mismatch", "Key mismatch"),
UserError("m.user_error", "User error"),
MismatchedUser("m.user_mismatch", "User mismatch"),
QrCodeInvalid("m.qr_code.invalid", "Invalid QR code")
QrCodeInvalid("m.qr_code.invalid", "Invalid QR code"),
AcceptedByAnotherDevice("m.accepted", "Verification request accepted by another device")
}
fun safeValueOf(code: String?): CancelCode {

View File

@ -939,9 +939,25 @@ internal class DefaultVerificationService @Inject constructor(
updatePendingRequest(
existingRequest.copy(
readyInfo = readyReq
readyInfo = readyReq
)
)
notifyOthersOfAcceptance(readyReq.transactionId, readyReq.fromDevice)
}
/**
* Gets a list of device ids excluding the current one.
*/
private fun getMyOtherDeviceIds(): List<String> = cryptoStore.getUserDevices(userId)?.keys?.filter { it != deviceId }.orEmpty()
/**
* Notifies other devices that the current verification transaction is being handled by [acceptedByDeviceId].
*/
private fun notifyOthersOfAcceptance(transactionId: String, acceptedByDeviceId: String) {
val deviceIds = getMyOtherDeviceIds().filter { it != acceptedByDeviceId }
val transport = verificationTransportToDeviceFactory.createTransport(null)
transport.cancelTransaction(transactionId, userId, deviceIds, CancelCode.AcceptedByAnotherDevice)
}
private fun createQrCodeData(requestId: String?, otherUserId: String, otherDeviceId: String?): QrCodeData? {

View File

@ -49,6 +49,11 @@ internal interface VerificationTransport {
otherUserDeviceId: String?,
code: CancelCode)
fun cancelTransaction(transactionId: String,
otherUserId: String,
otherUserDeviceIds: List<String>,
code: CancelCode)
fun done(transactionId: String,
onDone: (() -> Unit)?)

View File

@ -160,6 +160,9 @@ internal class VerificationTransportRoomMessage(
}
}
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceIds: List<String>, code: CancelCode) =
cancelTransaction(transactionId, otherUserId, null, code)
override fun done(transactionId: String,
onDone: (() -> Unit)?) {
Timber.d("## SAS sending done for $transactionId")

View File

@ -193,6 +193,27 @@ internal class VerificationTransportToDevice(
.executeBy(taskExecutor)
}
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceIds: List<String>, code: CancelCode) {
Timber.d("## SAS canceling transaction $transactionId for reason $code")
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
val contentMap = MXUsersDevicesMap<Any>()
val messages = otherUserDeviceIds.associateWith { cancelMessage }
contentMap.setObjects(otherUserId, messages)
sendToDeviceTask
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap)) {
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}")
}
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.")
}
}
}
.executeBy(taskExecutor)
}
override fun createAccept(tid: String,
keyAgreementProtocol: String,
hash: String,