Merge pull request #3711 from vector-im/feature/bma/sendToDevice

Ensure that txnId is the same if the request is retried
This commit is contained in:
Benoit Marty 2021-08-23 16:11:33 +02:00 committed by GitHub
commit 9fa862ec76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 78 additions and 24 deletions

View File

@ -52,7 +52,7 @@ interface VerificationService {
transactionId: String?): String? transactionId: String?): String?
/** /**
* Request a key verification from another user using toDevice events. * Request key verification with another user via room events (instead of the to-device API)
*/ */
fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
otherUserId: String, otherUserId: String,

View File

@ -23,12 +23,12 @@ import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams import org.matrix.android.sdk.internal.worker.SessionWorkerParams
@ -43,6 +43,9 @@ internal class CancelGossipRequestWorker(context: Context,
override val sessionId: String, override val sessionId: String,
val requestId: String, val requestId: String,
val recipients: Map<String, List<String>>, val recipients: Map<String, List<String>>,
// The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided
// to use the same value if this worker is retried.
val txnId: String? = null,
override val lastFailureMessage: String? = null override val lastFailureMessage: String? = null
) : SessionWorkerParams { ) : SessionWorkerParams {
companion object { companion object {
@ -51,6 +54,7 @@ internal class CancelGossipRequestWorker(context: Context,
sessionId = sessionId, sessionId = sessionId,
requestId = request.requestId, requestId = request.requestId,
recipients = request.recipients, recipients = request.recipients,
txnId = createUniqueTxnId(),
lastFailureMessage = null lastFailureMessage = null
) )
} }
@ -66,7 +70,10 @@ internal class CancelGossipRequestWorker(context: Context,
} }
override suspend fun doSafeWork(params: Params): Result { override suspend fun doSafeWork(params: Params): Result {
val localId = LocalEcho.createLocalEchoId() // params.txnId should be provided in all cases now. But Params can be deserialized by
// the WorkManager from data serialized in a previous version of the application, so without the txnId field.
// So if not present, we create a txnId
val txnId = params.txnId ?: createUniqueTxnId()
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()
val toDeviceContent = ShareRequestCancellation( val toDeviceContent = ShareRequestCancellation(
requestingDeviceId = credentials.deviceId, requestingDeviceId = credentials.deviceId,
@ -92,7 +99,7 @@ internal class CancelGossipRequestWorker(context: Context,
SendToDeviceTask.Params( SendToDeviceTask.Params(
eventType = EventType.ROOM_KEY_REQUEST, eventType = EventType.ROOM_KEY_REQUEST,
contentMap = contentMap, contentMap = contentMap,
transactionId = localId transactionId = txnId
) )
) )
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED) cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED)

View File

@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.GossipingDefaultContent
import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
@ -356,7 +357,8 @@ internal class IncomingGossipingRequestManager @Inject constructor(
secretValue = secretValue, secretValue = secretValue,
requestUserId = request.userId, requestUserId = request.userId,
requestDeviceId = request.deviceId, requestDeviceId = request.deviceId,
requestId = request.requestId requestId = request.requestId,
txnId = createUniqueTxnId()
) )
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING) cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
@ -376,13 +378,13 @@ internal class IncomingGossipingRequestManager @Inject constructor(
} }
request.share = { secretValue -> request.share = { secretValue ->
val params = SendGossipWorker.Params( val params = SendGossipWorker.Params(
sessionId = userId, sessionId = userId,
secretValue = secretValue, secretValue = secretValue,
requestUserId = request.userId, requestUserId = request.userId,
requestDeviceId = request.deviceId, requestDeviceId = request.deviceId,
requestId = request.requestId requestId = request.requestId,
txnId = createUniqueTxnId()
) )
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING) cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)

View File

@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.SessionId
@ -26,6 +25,8 @@ import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -131,7 +132,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
val params = SendGossipRequestWorker.Params( val params = SendGossipRequestWorker.Params(
sessionId = sessionId, sessionId = sessionId,
keyShareRequest = request as? OutgoingRoomKeyRequest, keyShareRequest = request as? OutgoingRoomKeyRequest,
secretShareRequest = request as? OutgoingSecretRequest secretShareRequest = request as? OutgoingSecretRequest,
txnId = createUniqueTxnId()
) )
cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING) cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING)
val workRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(params), true) val workRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(params), true)
@ -154,7 +156,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
if (resend) { if (resend) {
val reSendParams = SendGossipRequestWorker.Params( val reSendParams = SendGossipRequestWorker.Params(
sessionId = sessionId, sessionId = sessionId,
keyShareRequest = request.copy(requestId = LocalEcho.createLocalEchoId()) keyShareRequest = request.copy(requestId = RequestIdHelper.createUniqueRequestId()),
txnId = createUniqueTxnId()
) )
val reSendWorkRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(reSendParams), true) val reSendWorkRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(reSendParams), true)
gossipingWorkManager.postWork(reSendWorkRequest) gossipingWorkManager.postWork(reSendWorkRequest)

View File

@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
@ -31,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams import org.matrix.android.sdk.internal.worker.SessionWorkerParams
@ -46,6 +46,9 @@ internal class SendGossipRequestWorker(context: Context,
override val sessionId: String, override val sessionId: String,
val keyShareRequest: OutgoingRoomKeyRequest? = null, val keyShareRequest: OutgoingRoomKeyRequest? = null,
val secretShareRequest: OutgoingSecretRequest? = null, val secretShareRequest: OutgoingSecretRequest? = null,
// The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided
// to use the same value if this worker is retried.
val txnId: String? = null,
override val lastFailureMessage: String? = null override val lastFailureMessage: String? = null
) : SessionWorkerParams ) : SessionWorkerParams
@ -58,7 +61,10 @@ internal class SendGossipRequestWorker(context: Context,
} }
override suspend fun doSafeWork(params: Params): Result { override suspend fun doSafeWork(params: Params): Result {
val localId = LocalEcho.createLocalEchoId() // params.txnId should be provided in all cases now. But Params can be deserialized by
// the WorkManager from data serialized in a previous version of the application, so without the txnId field.
// So if not present, we create a txnId
val txnId = params.txnId ?: createUniqueTxnId()
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()
val eventType: String val eventType: String
val requestId: String val requestId: String
@ -122,7 +128,7 @@ internal class SendGossipRequestWorker(context: Context,
SendToDeviceTask.Params( SendToDeviceTask.Params(
eventType = eventType, eventType = eventType,
contentMap = contentMap, contentMap = contentMap,
transactionId = localId transactionId = txnId
) )
) )
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT) cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT)

View File

@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
@ -31,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams import org.matrix.android.sdk.internal.worker.SessionWorkerParams
@ -48,6 +48,9 @@ internal class SendGossipWorker(context: Context,
val requestUserId: String?, val requestUserId: String?,
val requestDeviceId: String?, val requestDeviceId: String?,
val requestId: String?, val requestId: String?,
// The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided
// to use the same value if this worker is retried.
val txnId: String? = null,
override val lastFailureMessage: String? = null override val lastFailureMessage: String? = null
) : SessionWorkerParams ) : SessionWorkerParams
@ -62,7 +65,10 @@ internal class SendGossipWorker(context: Context,
} }
override suspend fun doSafeWork(params: Params): Result { override suspend fun doSafeWork(params: Params): Result {
val localId = LocalEcho.createLocalEchoId() // params.txnId should be provided in all cases now. But Params can be deserialized by
// the WorkManager from data serialized in a previous version of the application, so without the txnId field.
// So if not present, we create a txnId
val txnId = params.txnId ?: createUniqueTxnId()
val eventType: String = EventType.SEND_SECRET val eventType: String = EventType.SEND_SECRET
val toDeviceContent = SecretSendEventContent( val toDeviceContent = SecretSendEventContent(
@ -127,7 +133,7 @@ internal class SendGossipWorker(context: Context,
SendToDeviceTask.Params( SendToDeviceTask.Params(
eventType = EventType.ENCRYPTED, eventType = EventType.ENCRYPTED,
contentMap = sendToDeviceMap, contentMap = sendToDeviceMap,
transactionId = localId transactionId = txnId
) )
) )
cryptoStore.updateGossipingRequestState( cryptoStore.updateGossipingRequestState(

View File

@ -27,7 +27,6 @@ import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.api.util.toOptional
@ -88,6 +87,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.query.delete
import org.matrix.android.sdk.internal.crypto.store.db.query.get import org.matrix.android.sdk.internal.crypto.store.db.query.get
import org.matrix.android.sdk.internal.crypto.store.db.query.getById import org.matrix.android.sdk.internal.crypto.store.db.query.getById
import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate
import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper
import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.database.tools.RealmDebugTools import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.CryptoDatabase
@ -1121,7 +1121,7 @@ internal class RealmCryptoStore @Inject constructor(
if (existing == null) { if (existing == null) {
request = realm.createObject(OutgoingGossipingRequestEntity::class.java).apply { request = realm.createObject(OutgoingGossipingRequestEntity::class.java).apply {
this.requestId = LocalEcho.createLocalEchoId() this.requestId = RequestIdHelper.createUniqueRequestId()
this.setRecipients(recipients) this.setRecipients(recipients)
this.requestState = OutgoingGossipingRequestState.UNSENT this.requestState = OutgoingGossipingRequestState.UNSENT
this.type = GossipRequestType.KEY this.type = GossipRequestType.KEY
@ -1151,7 +1151,7 @@ internal class RealmCryptoStore @Inject constructor(
this.type = GossipRequestType.SECRET this.type = GossipRequestType.SECRET
setRecipients(recipients) setRecipients(recipients)
this.requestState = OutgoingGossipingRequestState.UNSENT this.requestState = OutgoingGossipingRequestState.UNSENT
this.requestId = LocalEcho.createLocalEchoId() this.requestId = RequestIdHelper.createUniqueRequestId()
this.requestedInfoStr = secretName this.requestedInfoStr = secretName
}.toOutgoingGossipingRequest() as? OutgoingSecretRequest }.toOutgoingGossipingRequest() as? OutgoingSecretRequest
} else { } else {

View File

@ -22,8 +22,8 @@ import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import kotlin.random.Random
internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> { internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
data class Params( data class Params(
@ -31,7 +31,7 @@ internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
val eventType: String, val eventType: String,
// the content to send. Map from user_id to device_id to content dictionary. // the content to send. Map from user_id to device_id to content dictionary.
val contentMap: MXUsersDevicesMap<Any>, val contentMap: MXUsersDevicesMap<Any>,
// the transactionId // the transactionId. If not provided, a transactionId will be created by the task
val transactionId: String? = null val transactionId: String? = null
) )
} }
@ -46,16 +46,23 @@ internal class DefaultSendToDeviceTask @Inject constructor(
messages = params.contentMap.map messages = params.contentMap.map
) )
// If params.transactionId is not provided, we create a unique txnId.
// It's important to do that outside the requestBlock parameter of executeRequest()
// to use the same value if the request is retried
val txnId = params.transactionId ?: createUniqueTxnId()
return executeRequest( return executeRequest(
globalErrorReceiver, globalErrorReceiver,
canRetry = true, canRetry = true,
maxRetriesCount = 3 maxRetriesCount = 3
) { ) {
cryptoApi.sendToDevice( cryptoApi.sendToDevice(
params.eventType, eventType = params.eventType,
params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), transactionId = txnId,
sendToDeviceBody body = sendToDeviceBody
) )
} }
} }
} }
internal fun createUniqueTxnId() = UUID.randomUUID().toString()

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2021 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.util
import java.util.UUID
internal object RequestIdHelper {
fun createUniqueRequestId() = UUID.randomUUID().toString()
}