Use workers to send verification messages

This commit is contained in:
Valere 2020-01-02 11:37:33 +01:00
parent 5b210df7c5
commit f541661059
13 changed files with 311 additions and 304 deletions

View File

@ -106,6 +106,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
implementation("com.google.guava:guava:28.2-jre")
// Network // Network
implementation 'com.squareup.retrofit2:retrofit:2.6.2' implementation 'com.squareup.retrofit2:retrofit:2.6.2'

View File

@ -55,7 +55,7 @@ interface SasVerificationService {
*/ */
fun beginKeyVerification(method: String, userId: String, deviceID: String): String? fun beginKeyVerification(method: String, userId: String, deviceID: String): String?
fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<String>?): PendingVerificationRequest fun requestKeyVerificationInDMs(userId: String, roomId: String): PendingVerificationRequest
fun beginKeyVerificationInDMs(method: String, fun beginKeyVerificationInDMs(method: String,
transactionId: String, transactionId: String,

View File

@ -180,6 +180,9 @@ internal abstract class CryptoModule {
@Binds @Binds
abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask
@Binds
abstract fun bindSendVerificationMessageTask(sendDefaultSendVerificationMessageTask: DefaultSendVerificationMessageTask): SendVerificationMessageTask
@Binds @Binds
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice) abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
: ClaimOneTimeKeysForUsersDeviceTask : ClaimOneTimeKeysForUsersDeviceTask

View File

@ -1,95 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* 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 im.vector.matrix.android.internal.crypto.tasks
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater
import im.vector.matrix.android.internal.session.room.send.SendResponse
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
internal interface RequestVerificationDMTask : Task<RequestVerificationDMTask.Params, SendResponse> {
data class Params(
val event: Event,
val cryptoService: CryptoService
)
fun createParamsAndLocalEcho(roomId: String, from: String, methods: List<String>, to: String, cryptoService: CryptoService): Params
}
internal class DefaultRequestVerificationDMTask @Inject constructor(
private val localEchoUpdater: LocalEchoUpdater,
private val localEchoEventFactory: LocalEchoEventFactory,
private val encryptEventTask: DefaultEncryptEventTask,
private val monarchy: Monarchy,
private val roomAPI: RoomAPI)
: RequestVerificationDMTask {
override fun createParamsAndLocalEcho(roomId: String, from: String, methods: List<String>, to: String, cryptoService: CryptoService)
: RequestVerificationDMTask.Params {
val event = localEchoEventFactory.createVerificationRequest(roomId, from, to, methods)
.also { localEchoEventFactory.saveLocalEcho(monarchy, it) }
return RequestVerificationDMTask.Params(
event,
cryptoService
)
}
override suspend fun execute(params: RequestVerificationDMTask.Params): SendResponse {
val event = handleEncryption(params)
val localID = event.eventId!!
try {
localEchoUpdater.updateSendState(localID, SendState.SENDING)
val executeRequest = executeRequest<SendResponse> {
apiCall = roomAPI.send(
localID,
roomId = event.roomId ?: "",
content = event.content,
eventType = event.type // message or room.encrypted
)
}
localEchoUpdater.updateSendState(localID, SendState.SENT)
return executeRequest
} catch (e: Throwable) {
localEchoUpdater.updateSendState(localID, SendState.UNDELIVERED)
throw e
}
}
private suspend fun handleEncryption(params: RequestVerificationDMTask.Params): Event {
val roomId = params.event.roomId ?: ""
if (params.cryptoService.isRoomEncrypted(roomId)) {
try {
return encryptEventTask.execute(EncryptEventTask.Params(
roomId,
params.event,
listOf("m.relates_to"),
params.cryptoService
))
} catch (throwable: Throwable) {
// We said it's ok to send verification request in clear
}
}
return params.event
}
}

View File

@ -15,64 +15,29 @@
*/ */
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.api.session.events.model.UnsignedData
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater
import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.session.room.send.SendResponse
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, SendResponse> { internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, String> {
data class Params( data class Params(
val type: String,
val event: Event, val event: Event,
val cryptoService: CryptoService? val cryptoService: CryptoService?
) )
fun createParamsAndLocalEcho(type: String,
roomId: String,
content: Content,
cryptoService: CryptoService?) : Params
} }
internal class DefaultSendVerificationMessageTask @Inject constructor( internal class DefaultSendVerificationMessageTask @Inject constructor(
private val localEchoUpdater: LocalEchoUpdater, private val localEchoUpdater: LocalEchoUpdater,
private val localEchoEventFactory: LocalEchoEventFactory,
private val encryptEventTask: DefaultEncryptEventTask, private val encryptEventTask: DefaultEncryptEventTask,
private val monarchy: Monarchy,
@UserId private val userId: String,
private val roomAPI: RoomAPI) : SendVerificationMessageTask { private val roomAPI: RoomAPI) : SendVerificationMessageTask {
override fun createParamsAndLocalEcho(type: String, roomId: String, content: Content, cryptoService: CryptoService?): SendVerificationMessageTask.Params { override suspend fun execute(params: SendVerificationMessageTask.Params): String {
val localID = LocalEcho.createLocalEchoId()
val event = Event(
roomId = roomId,
originServerTs = System.currentTimeMillis(),
senderId = userId,
eventId = localID,
type = type,
content = content,
unsignedData = UnsignedData(age = null, transactionId = localID)
).also {
localEchoEventFactory.saveLocalEcho(monarchy, it)
}
return SendVerificationMessageTask.Params(
type,
event,
cryptoService
)
}
override suspend fun execute(params: SendVerificationMessageTask.Params): SendResponse {
val event = handleEncryption(params) val event = handleEncryption(params)
val localID = event.eventId!! val localID = event.eventId!!
@ -87,7 +52,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
) )
} }
localEchoUpdater.updateSendState(localID, SendState.SENT) localEchoUpdater.updateSendState(localID, SendState.SENT)
return executeRequest return executeRequest.eventId
} catch (e: Throwable) { } catch (e: Throwable) {
localEchoUpdater.updateSendState(localID, SendState.UNDELIVERED) localEchoUpdater.updateSendState(localID, SendState.UNDELIVERED)
throw e throw e

View File

@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.crypto.verification package im.vector.matrix.android.internal.crypto.verification
import android.content.Context
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import dagger.Lazy import dagger.Lazy
@ -28,6 +29,7 @@ import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.api.session.crypto.sas.safeValueOf import im.vector.matrix.android.api.session.crypto.sas.safeValueOf
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.DeviceListManager
@ -37,12 +39,7 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.model.rest.*
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.DefaultRequestVerificationDMTask
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.send.SendResponse
import im.vector.matrix.android.internal.task.TaskConstraints
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -55,16 +52,15 @@ import kotlin.collections.set
@SessionScope @SessionScope
internal class DefaultSasVerificationService @Inject constructor( internal class DefaultSasVerificationService @Inject constructor(
private val context: Context,
private val credentials: Credentials, private val credentials: Credentials,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>, private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val setDeviceVerificationAction: SetDeviceVerificationAction, private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val requestVerificationDMTask: DefaultRequestVerificationDMTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory, private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory,
private val sasTransportToDeviceFactory: SasTransportToDeviceFactory, private val sasTransportToDeviceFactory: SasTransportToDeviceFactory
private val taskExecutor: TaskExecutor
) : VerificationTransaction.Listener, SasVerificationService { ) : VerificationTransaction.Listener, SasVerificationService {
private val uiHandler = Handler(Looper.getMainLooper()) private val uiHandler = Handler(Looper.getMainLooper())
@ -279,8 +275,9 @@ internal class DefaultSasVerificationService @Inject constructor(
if (startReq?.isValid()?.not() == true) { if (startReq?.isValid()?.not() == true) {
Timber.e("## received invalid verification request") Timber.e("## received invalid verification request")
if (startReq.transactionID != null) { if (startReq.transactionID != null) {
sasTransportRoomMessageFactory.createTransport(event.roomId sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId
?: "", cryptoService, null).cancelTransaction( ?: "", event.roomId
?: "", null).cancelTransaction(
startReq.transactionID ?: "", startReq.transactionID ?: "",
otherUserId!!, otherUserId!!,
startReq.fromDevice ?: event.getSenderKey()!!, startReq.fromDevice ?: event.getSenderKey()!!,
@ -291,11 +288,13 @@ internal class DefaultSasVerificationService @Inject constructor(
} }
handleStart(otherUserId, startReq as VerificationInfoStart) { handleStart(otherUserId, startReq as VerificationInfoStart) {
it.transport = sasTransportRoomMessageFactory.createTransport(event.roomId it.transport = sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId
?: "", cryptoService, it) ?: "", event.roomId
?: "", it)
}?.let { }?.let {
sasTransportRoomMessageFactory.createTransport(event.roomId sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId
?: "", cryptoService, null).cancelTransaction( ?: "", event.roomId
?: "", null).cancelTransaction(
startReq.transactionID ?: "", startReq.transactionID ?: "",
otherUserId!!, otherUserId!!,
startReq.fromDevice ?: event.getSenderKey()!!, startReq.fromDevice ?: event.getSenderKey()!!,
@ -693,57 +692,37 @@ internal class DefaultSasVerificationService @Inject constructor(
} }
} }
override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<String>?) override fun requestKeyVerificationInDMs(userId: String, roomId: String)
: PendingVerificationRequest { : PendingVerificationRequest {
Timber.i("## SAS Requesting verification to user: $userId in room $roomId") Timber.i("## SAS Requesting verification to user: $userId in room $roomId")
val requestsForUser = pendingRequests[userId] val requestsForUser = pendingRequests[userId]
?: ArrayList<PendingVerificationRequest>().also { ?: ArrayList<PendingVerificationRequest>().also {
pendingRequests[userId] = it pendingRequests[userId] = it
} }
val params = requestVerificationDMTask.createParamsAndLocalEcho( val transport = sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId
roomId = roomId, ?: "", roomId, null)
from = credentials.deviceId ?: "",
methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS), val localID = LocalEcho.createLocalEchoId()
to = userId,
cryptoService = cryptoService
)
val verificationRequest = PendingVerificationRequest( val verificationRequest = PendingVerificationRequest(
ageLocalTs = System.currentTimeMillis(), ageLocalTs = System.currentTimeMillis(),
isIncoming = false, isIncoming = false,
localID = params.event.eventId ?: "", localID = localID,
otherUserId = userId otherUserId = userId
) )
requestsForUser.add(verificationRequest)
dispatchRequestAdded(verificationRequest)
requestVerificationDMTask.configureWith( transport.sendVerificationRequest(localID, userId, roomId) { syncedId, info ->
requestVerificationDMTask.createParamsAndLocalEcho( // We need to update with the syncedID
roomId = roomId,
from = credentials.deviceId ?: "",
methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
to = userId,
cryptoService = cryptoService
)
) {
this.callback = object : MatrixCallback<SendResponse> {
override fun onSuccess(data: SendResponse) {
params.event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
updatePendingRequest(verificationRequest.copy( updatePendingRequest(verificationRequest.copy(
transactionId = data.eventId, transactionId = syncedId,
requestInfo = it requestInfo = info
)) ))
} }
callback?.onSuccess(data.eventId)
}
override fun onFailure(failure: Throwable) { requestsForUser.add(verificationRequest)
callback?.onFailure(failure) dispatchRequestAdded(verificationRequest)
}
}
constraints = TaskConstraints(true)
retryCount = 3
}.executeBy(taskExecutor)
return verificationRequest return verificationRequest
} }
@ -776,7 +755,8 @@ internal class DefaultSasVerificationService @Inject constructor(
transactionId, transactionId,
otherUserId, otherUserId,
otherDeviceId) otherDeviceId)
tx.transport = sasTransportRoomMessageFactory.createTransport(roomId, cryptoService, tx) tx.transport = sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId
?: "", roomId, tx)
addTransaction(tx) addTransaction(tx)
tx.start() tx.start()
@ -790,7 +770,8 @@ internal class DefaultSasVerificationService @Inject constructor(
// Let's find the related request // Let's find the related request
getExistingVerificationRequest(otherUserId)?.find { it.transactionId == transactionId }?.let { getExistingVerificationRequest(otherUserId)?.find { it.transactionId == transactionId }?.let {
// we need to send a ready event, with matching methods // we need to send a ready event, with matching methods
val transport = sasTransportRoomMessageFactory.createTransport(roomId, cryptoService, null) val transport = sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId
?: "", roomId, null)
val methods = it.requestInfo?.methods?.intersect(listOf(KeyVerificationStart.VERIF_METHOD_SAS))?.toList() val methods = it.requestInfo?.methods?.intersect(listOf(KeyVerificationStart.VERIF_METHOD_SAS))?.toList()
if (methods.isNullOrEmpty()) { if (methods.isNullOrEmpty()) {
Timber.i("Cannot ready this request, no common methods found txId:$transactionId") Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
@ -831,28 +812,4 @@ internal class DefaultSasVerificationService @Inject constructor(
this.removeTransaction(tx.otherUserId, tx.transactionId) this.removeTransaction(tx.otherUserId, tx.transactionId)
} }
} }
//
// fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode, roomId: String? = null) {
// val cancelMessage = KeyVerificationCancel.create(transactionId, code)
// val contentMap = MXUsersDevicesMap<Any>()
// contentMap.setObject(userId, userDevice, cancelMessage)
//
// if (roomId != null) {
//
// } else {
// sendToDeviceTask
// .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) {
// 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)
// }
// }
} }

View File

@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.crypto.verification
import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
/** /**
* SAS verification can be performed using toDevice events or via DM. * SAS verification can be performed using toDevice events or via DM.
@ -33,6 +34,8 @@ internal interface SasTransport {
onErrorReason: CancelCode, onErrorReason: CancelCode,
onDone: (() -> Unit)?) onDone: (() -> Unit)?)
fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, callback: (String?, MessageVerificationRequestContent?) -> Unit)
fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode)
fun done(transactionId: String) fun done(transactionId: String)

View File

@ -15,33 +15,41 @@
*/ */
package im.vector.matrix.android.internal.crypto.verification package im.vector.matrix.android.internal.crypto.verification
import im.vector.matrix.android.api.MatrixCallback import android.content.Context
import im.vector.matrix.android.api.session.crypto.CryptoService import androidx.lifecycle.Observer
import androidx.work.*
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.R
import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.*
import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendVerificationMessageTask import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.task.TaskConstraints import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.task.TaskExecutor import kotlinx.coroutines.Dispatchers
import im.vector.matrix.android.internal.task.TaskThread import kotlinx.coroutines.GlobalScope
import im.vector.matrix.android.internal.task.configureWith import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
internal class SasTransportRoomMessage( internal class SasTransportRoomMessage(
private val context: Context,
private val userId: String,
private val userDevice: String,
private val roomId: String, private val roomId: String,
private val cryptoService: CryptoService, private val monarchy: Monarchy,
private val tx: SASVerificationTransaction?, private val localEchoEventFactory: LocalEchoEventFactory,
private val sendVerificationMessageTask: SendVerificationMessageTask, private val tx: SASVerificationTransaction?
private val taskExecutor: TaskExecutor
) : SasTransport { ) : SasTransport {
private val listenerExecutor = Executors.newSingleThreadExecutor()
override fun sendToOther(type: String, override fun sendToOther(type: String,
verificationInfo: VerificationInfo, verificationInfo: VerificationInfo,
nextState: SasVerificationTxState, nextState: SasVerificationTxState,
@ -49,83 +57,167 @@ internal class SasTransportRoomMessage(
onDone: (() -> Unit)?) { onDone: (() -> Unit)?) {
Timber.d("## SAS sending msg type $type") Timber.d("## SAS sending msg type $type")
Timber.v("## SAS sending msg info $verificationInfo") Timber.v("## SAS sending msg info $verificationInfo")
sendVerificationMessageTask.configureWith( val event = createEventAndLocalEcho(
sendVerificationMessageTask.createParamsAndLocalEcho( type = type,
type, roomId = roomId,
roomId, content = verificationInfo.toEventContent()!!
verificationInfo.toEventContent()!!,
cryptoService
) )
) {
callbackThread = TaskThread.DM_VERIF val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
executionThread = TaskThread.DM_VERIF userId = userId,
constraints = TaskConstraints(true) event = event
callback = object : MatrixCallback<SendResponse> { ))
override fun onSuccess(data: SendResponse) { val enqueueInfo = enqueueSendWork(workerParams)
// I cannot just listen to the given work request, because when used in a uniqueWork,
// The callback is called while it is still Running ...
// Futures.addCallback(enqueueInfo.first.result, object : FutureCallback<Operation.State.SUCCESS> {
// override fun onSuccess(result: Operation.State.SUCCESS?) {
// if (onDone != null) {
// onDone()
// } else {
// tx?.state = nextState
// }
// }
//
// override fun onFailure(t: Throwable) {
// Timber.e("## SAS verification [${tx?.transactionId}] failed to send toDevice in state : ${tx?.state}, reason: ${t.localizedMessage}")
// tx?.cancel(onErrorReason)
// }
// }, listenerExecutor)
val workLiveData = WorkManager.getInstance(context).getWorkInfosForUniqueWorkLiveData("${roomId}_VerificationWork")
val observer = object : Observer<List<WorkInfo>> {
override fun onChanged(workInfoList: List<WorkInfo>?) {
workInfoList
?.filter { it.state == WorkInfo.State.SUCCEEDED }
?.firstOrNull { it.id == enqueueInfo.second }
?.let { wInfo ->
if (wInfo.outputData.getBoolean("failed", false)) {
Timber.e("## SAS verification [${tx?.transactionId}] failed to send verification message in state : ${tx?.state}")
tx?.cancel(onErrorReason)
} else {
if (onDone != null) { if (onDone != null) {
onDone() onDone()
} else { } else {
tx?.state = nextState tx?.state = nextState
} }
} }
workLiveData.removeObserver(this)
}
}
}
override fun onFailure(failure: Throwable) { // TODO listen to DB to get synced info
Timber.e("## SAS verification [${tx?.transactionId}] failed to send toDevice in state : ${tx?.state}") GlobalScope.launch(Dispatchers.Main) {
tx?.cancel(onErrorReason) workLiveData.observeForever(observer)
}
}
override fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, callback: (String?, MessageVerificationRequestContent?) -> Unit) {
val info = MessageVerificationRequestContent(
body = context.getString(R.string.key_verification_request_fallback_message, userId),
fromDevice = userDevice,
toUserId = otherUserId,
methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS)
)
val content = info.toContent()
val event = createEventAndLocalEcho(
localID,
EventType.MESSAGE,
roomId,
content
)
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
userId = userId,
event = event
))
val workRequest = WorkManagerUtil.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
.setConstraints(WorkManagerUtil.workConstraints)
.setInputData(workerParams)
.setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance(context)
.beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND, workRequest)
.enqueue()
// I cannot just listen to the given work request, because when used in a uniqueWork,
// The callback is called while it is still Running ...
val workLiveData = WorkManager.getInstance(context).getWorkInfosForUniqueWorkLiveData("${roomId}_VerificationWork")
val observer = object : Observer<List<WorkInfo>> {
override fun onChanged(workInfoList: List<WorkInfo>?) {
workInfoList
?.filter { it.state == WorkInfo.State.SUCCEEDED }
?.firstOrNull { it.id == workRequest.id }
?.let { wInfo ->
if (wInfo.outputData.getBoolean("failed", false)) {
callback(null, null)
} else if (wInfo.outputData.getString(localID) != null) {
callback(wInfo.outputData.getString(localID), info)
} else {
callback(null, null)
}
workLiveData.removeObserver(this)
} }
} }
retryCount = 3
} }
.executeBy(taskExecutor)
// TODO listen to DB to get synced info
GlobalScope.launch(Dispatchers.Main) {
workLiveData.observeForever(observer)
}
} }
override fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) { override fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) {
Timber.d("## SAS canceling transaction $transactionId for reason $code") Timber.d("## SAS canceling transaction $transactionId for reason $code")
sendVerificationMessageTask.configureWith( val event = createEventAndLocalEcho(
sendVerificationMessageTask.createParamsAndLocalEcho( type = EventType.KEY_VERIFICATION_CANCEL,
EventType.KEY_VERIFICATION_CANCEL, roomId = roomId,
roomId, content = MessageVerificationCancelContent.create(transactionId, code).toContent()
MessageVerificationCancelContent.create(transactionId, code).toContent(),
cryptoService
) )
) { val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
callbackThread = TaskThread.DM_VERIF userId = userId,
executionThread = TaskThread.DM_VERIF event = event
constraints = TaskConstraints(true) ))
retryCount = 3 enqueueSendWork(workerParams)
callback = object : MatrixCallback<SendResponse> {
override fun onSuccess(data: SendResponse) {
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 done(transactionId: String) { override fun done(transactionId: String) {
sendVerificationMessageTask.configureWith( val event = createEventAndLocalEcho(
sendVerificationMessageTask.createParamsAndLocalEcho( type = EventType.KEY_VERIFICATION_DONE,
EventType.KEY_VERIFICATION_DONE, roomId = roomId,
roomId, content = MessageVerificationDoneContent(
MessageVerificationDoneContent(
relatesTo = RelationDefaultContent( relatesTo = RelationDefaultContent(
RelationType.REFERENCE, RelationType.REFERENCE,
transactionId transactionId
) )
).toContent(), ).toContent()
cryptoService
) )
) { val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
callbackThread = TaskThread.DM_VERIF userId = userId,
executionThread = TaskThread.DM_VERIF event = event
constraints = TaskConstraints(true) ))
retryCount = 3 enqueueSendWork(workerParams)
} }
.executeBy(taskExecutor)
private fun enqueueSendWork(workerParams: Data): Pair<Operation, UUID> {
val workRequest = WorkManagerUtil.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
.setConstraints(WorkManagerUtil.workConstraints)
.setInputData(workerParams)
.setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS)
.build()
return WorkManager.getInstance(context)
.beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND, workRequest)
.enqueue() to workRequest.id
} }
override fun createAccept(tid: String, override fun createAccept(tid: String,
@ -178,16 +270,28 @@ internal class SasTransportRoomMessage(
methods = methods methods = methods
) )
} }
private fun createEventAndLocalEcho(localID: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
return Event(
roomId = roomId,
originServerTs = System.currentTimeMillis(),
senderId = userId,
eventId = localID,
type = type,
content = content,
unsignedData = UnsignedData(age = null, transactionId = localID)
).also {
localEchoEventFactory.saveLocalEcho(monarchy, it)
}
}
} }
internal class SasTransportRoomMessageFactory @Inject constructor( internal class SasTransportRoomMessageFactory @Inject constructor(
private val sendVerificationMessageTask: DefaultSendVerificationMessageTask, private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor) { private val localEchoEventFactory: LocalEchoEventFactory) {
fun createTransport(roomId: String, fun createTransport(context: Context, userId: String, userDevice: String, roomId: String, tx: SASVerificationTransaction?
cryptoService: CryptoService,
tx: SASVerificationTransaction?
): SasTransportRoomMessage { ): SasTransportRoomMessage {
return SasTransportRoomMessage(roomId, cryptoService, tx, sendVerificationMessageTask, taskExecutor) return SasTransportRoomMessage(context, userId, userDevice, roomId, monarchy, localEchoEventFactory, tx)
} }
} }

View File

@ -19,6 +19,7 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.model.rest.*
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
@ -33,6 +34,10 @@ internal class SasTransportToDevice(
private var taskExecutor: TaskExecutor private var taskExecutor: TaskExecutor
) : SasTransport { ) : SasTransport {
override fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, callback: (String?, MessageVerificationRequestContent?) -> Unit) {
// TODO "not implemented"
}
override fun sendToOther(type: String, override fun sendToOther(type: String,
verificationInfo: VerificationInfo, verificationInfo: VerificationInfo,
nextState: SasVerificationTxState, nextState: SasVerificationTxState,

View File

@ -0,0 +1,61 @@
package im.vector.matrix.android.internal.crypto.verification
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent
import javax.inject.Inject
internal class SendVerificationMessageWorker constructor(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
@JsonClass(generateAdapter = true)
internal data class Params(
val userId: String,
val event: Event
)
@Inject
lateinit var sendVerificationMessageTask: SendVerificationMessageTask
@Inject
lateinit var cryptoService: CryptoService
override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success(Data.Builder().putBoolean("failed", true).build())
val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
sessionComponent.inject(this)
val localId = params.event.eventId ?: ""
return try {
val eventId = sendVerificationMessageTask.execute(
SendVerificationMessageTask.Params(
event = params.event,
cryptoService = cryptoService
)
)
Result.success(Data.Builder().putString(localId, eventId).build())
} catch (exception: Throwable) {
if (exception.shouldBeRetried()) {
Result.retry()
} else {
Result.success(Data.Builder().putBoolean("failed", true).build())
}
}
}
private fun Throwable.shouldBeRetried(): Boolean {
return this is Failure.NetworkConnection
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
}
}

View File

@ -21,6 +21,7 @@ import dagger.Component
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.crypto.CryptoModule import im.vector.matrix.android.internal.crypto.CryptoModule
import im.vector.matrix.android.internal.crypto.verification.SendVerificationMessageWorker
import im.vector.matrix.android.internal.di.MatrixComponent import im.vector.matrix.android.internal.di.MatrixComponent
import im.vector.matrix.android.internal.di.SessionAssistedInjectModule import im.vector.matrix.android.internal.di.SessionAssistedInjectModule
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
@ -98,6 +99,8 @@ internal interface SessionComponent {
fun inject(addHttpPusherWorker: AddHttpPusherWorker) fun inject(addHttpPusherWorker: AddHttpPusherWorker)
fun inject(sendVerificationMessageWorker: SendVerificationMessageWorker)
@Component.Factory @Component.Factory
interface Factory { interface Factory {
fun create( fun create(

View File

@ -109,7 +109,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
is VerificationAction.RequestVerificationByDM -> { is VerificationAction.RequestVerificationByDM -> {
// session // session
setState { setState {
copy(pendingRequest = session.getSasVerificationService().requestKeyVerificationInDMs(otherUserId, roomId, null)) copy(pendingRequest = session.getSasVerificationService().requestKeyVerificationInDMs(otherUserId, roomId))
} }
} }
is VerificationAction.StartSASVerification -> { is VerificationAction.StartSASVerification -> {

View File

@ -398,7 +398,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
popDraft() popDraft()
} }
is ParsedCommand.VerifyUser -> { is ParsedCommand.VerifyUser -> {
session.getSasVerificationService().requestKeyVerificationInDMs(slashCommandResult.userId, room.roomId, null) session.getSasVerificationService().requestKeyVerificationInDMs(slashCommandResult.userId, room.roomId)
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
popDraft() popDraft()
} }