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"
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
implementation("com.google.guava:guava:28.2-jre")
// Network
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 requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<String>?): PendingVerificationRequest
fun requestKeyVerificationInDMs(userId: String, roomId: String): PendingVerificationRequest
fun beginKeyVerificationInDMs(method: String,
transactionId: String,

View File

@ -180,6 +180,9 @@ internal abstract class CryptoModule {
@Binds
abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask
@Binds
abstract fun bindSendVerificationMessageTask(sendDefaultSendVerificationMessageTask: DefaultSendVerificationMessageTask): SendVerificationMessageTask
@Binds
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
: 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
import com.zhuinden.monarchy.Monarchy
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.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.internal.di.UserId
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 SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, SendResponse> {
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, String> {
data class Params(
val type: String,
val event: Event,
val cryptoService: CryptoService?
)
fun createParamsAndLocalEcho(type: String,
roomId: String,
content: Content,
cryptoService: CryptoService?) : Params
}
internal class DefaultSendVerificationMessageTask @Inject constructor(
private val localEchoUpdater: LocalEchoUpdater,
private val localEchoEventFactory: LocalEchoEventFactory,
private val encryptEventTask: DefaultEncryptEventTask,
private val monarchy: Monarchy,
@UserId private val userId: String,
private val roomAPI: RoomAPI) : SendVerificationMessageTask {
override fun createParamsAndLocalEcho(type: String, roomId: String, content: Content, cryptoService: CryptoService?): SendVerificationMessageTask.Params {
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 {
override suspend fun execute(params: SendVerificationMessageTask.Params): String {
val event = handleEncryption(params)
val localID = event.eventId!!
@ -87,7 +52,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
)
}
localEchoUpdater.updateSendState(localID, SendState.SENT)
return executeRequest
return executeRequest.eventId
} catch (e: Throwable) {
localEchoUpdater.updateSendState(localID, SendState.UNDELIVERED)
throw e

View File

@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.crypto.verification
import android.content.Context
import android.os.Handler
import android.os.Looper
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.events.model.Event
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.room.model.message.*
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.rest.*
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.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 kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -55,16 +52,15 @@ import kotlin.collections.set
@SessionScope
internal class DefaultSasVerificationService @Inject constructor(
private val context: Context,
private val credentials: Credentials,
private val cryptoStore: IMXCryptoStore,
private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
private val deviceListManager: DeviceListManager,
private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val requestVerificationDMTask: DefaultRequestVerificationDMTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory,
private val sasTransportToDeviceFactory: SasTransportToDeviceFactory,
private val taskExecutor: TaskExecutor
private val sasTransportToDeviceFactory: SasTransportToDeviceFactory
) : VerificationTransaction.Listener, SasVerificationService {
private val uiHandler = Handler(Looper.getMainLooper())
@ -279,8 +275,9 @@ internal class DefaultSasVerificationService @Inject constructor(
if (startReq?.isValid()?.not() == true) {
Timber.e("## received invalid verification request")
if (startReq.transactionID != null) {
sasTransportRoomMessageFactory.createTransport(event.roomId
?: "", cryptoService, null).cancelTransaction(
sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId
?: "", event.roomId
?: "", null).cancelTransaction(
startReq.transactionID ?: "",
otherUserId!!,
startReq.fromDevice ?: event.getSenderKey()!!,
@ -291,11 +288,13 @@ internal class DefaultSasVerificationService @Inject constructor(
}
handleStart(otherUserId, startReq as VerificationInfoStart) {
it.transport = sasTransportRoomMessageFactory.createTransport(event.roomId
?: "", cryptoService, it)
it.transport = sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId
?: "", event.roomId
?: "", it)
}?.let {
sasTransportRoomMessageFactory.createTransport(event.roomId
?: "", cryptoService, null).cancelTransaction(
sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId
?: "", event.roomId
?: "", null).cancelTransaction(
startReq.transactionID ?: "",
otherUserId!!,
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 {
Timber.i("## SAS Requesting verification to user: $userId in room $roomId")
val requestsForUser = pendingRequests[userId]
?: ArrayList<PendingVerificationRequest>().also {
pendingRequests[userId] = it
}
val params = requestVerificationDMTask.createParamsAndLocalEcho(
roomId = roomId,
from = credentials.deviceId ?: "",
methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
to = userId,
cryptoService = cryptoService
)
val transport = sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId
?: "", roomId, null)
val localID = LocalEcho.createLocalEchoId()
val verificationRequest = PendingVerificationRequest(
ageLocalTs = System.currentTimeMillis(),
isIncoming = false,
localID = params.event.eventId ?: "",
localID = localID,
otherUserId = userId
)
requestsForUser.add(verificationRequest)
dispatchRequestAdded(verificationRequest)
requestVerificationDMTask.configureWith(
requestVerificationDMTask.createParamsAndLocalEcho(
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 {
transport.sendVerificationRequest(localID, userId, roomId) { syncedId, info ->
// We need to update with the syncedID
updatePendingRequest(verificationRequest.copy(
transactionId = data.eventId,
requestInfo = it
transactionId = syncedId,
requestInfo = info
))
}
callback?.onSuccess(data.eventId)
}
override fun onFailure(failure: Throwable) {
callback?.onFailure(failure)
}
}
constraints = TaskConstraints(true)
retryCount = 3
}.executeBy(taskExecutor)
requestsForUser.add(verificationRequest)
dispatchRequestAdded(verificationRequest)
return verificationRequest
}
@ -776,7 +755,8 @@ internal class DefaultSasVerificationService @Inject constructor(
transactionId,
otherUserId,
otherDeviceId)
tx.transport = sasTransportRoomMessageFactory.createTransport(roomId, cryptoService, tx)
tx.transport = sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId
?: "", roomId, tx)
addTransaction(tx)
tx.start()
@ -790,7 +770,8 @@ internal class DefaultSasVerificationService @Inject constructor(
// Let's find the related request
getExistingVerificationRequest(otherUserId)?.find { it.transactionId == transactionId }?.let {
// 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()
if (methods.isNullOrEmpty()) {
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)
}
}
//
// 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.SasVerificationTxState
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
/**
* SAS verification can be performed using toDevice events or via DM.
@ -33,6 +34,8 @@ internal interface SasTransport {
onErrorReason: CancelCode,
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 done(transactionId: String)

View File

@ -15,33 +15,41 @@
*/
package im.vector.matrix.android.internal.crypto.verification
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.crypto.CryptoService
import android.content.Context
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.SasVerificationTxState
import im.vector.matrix.android.api.session.events.model.EventType
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.events.model.*
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.internal.crypto.tasks.DefaultSendVerificationMessageTask
import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask
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.TaskThread
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import javax.inject.Inject
internal class SasTransportRoomMessage(
private val context: Context,
private val userId: String,
private val userDevice: String,
private val roomId: String,
private val cryptoService: CryptoService,
private val tx: SASVerificationTransaction?,
private val sendVerificationMessageTask: SendVerificationMessageTask,
private val taskExecutor: TaskExecutor
private val monarchy: Monarchy,
private val localEchoEventFactory: LocalEchoEventFactory,
private val tx: SASVerificationTransaction?
) : SasTransport {
private val listenerExecutor = Executors.newSingleThreadExecutor()
override fun sendToOther(type: String,
verificationInfo: VerificationInfo,
nextState: SasVerificationTxState,
@ -49,83 +57,167 @@ internal class SasTransportRoomMessage(
onDone: (() -> Unit)?) {
Timber.d("## SAS sending msg type $type")
Timber.v("## SAS sending msg info $verificationInfo")
sendVerificationMessageTask.configureWith(
sendVerificationMessageTask.createParamsAndLocalEcho(
type,
roomId,
verificationInfo.toEventContent()!!,
cryptoService
val event = createEventAndLocalEcho(
type = type,
roomId = roomId,
content = verificationInfo.toEventContent()!!
)
) {
callbackThread = TaskThread.DM_VERIF
executionThread = TaskThread.DM_VERIF
constraints = TaskConstraints(true)
callback = object : MatrixCallback<SendResponse> {
override fun onSuccess(data: SendResponse) {
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
userId = userId,
event = event
))
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) {
onDone()
} else {
tx?.state = nextState
}
}
workLiveData.removeObserver(this)
}
}
}
override fun onFailure(failure: Throwable) {
Timber.e("## SAS verification [${tx?.transactionId}] failed to send toDevice in state : ${tx?.state}")
tx?.cancel(onErrorReason)
// TODO listen to DB to get synced info
GlobalScope.launch(Dispatchers.Main) {
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) {
Timber.d("## SAS canceling transaction $transactionId for reason $code")
sendVerificationMessageTask.configureWith(
sendVerificationMessageTask.createParamsAndLocalEcho(
EventType.KEY_VERIFICATION_CANCEL,
roomId,
MessageVerificationCancelContent.create(transactionId, code).toContent(),
cryptoService
val event = createEventAndLocalEcho(
type = EventType.KEY_VERIFICATION_CANCEL,
roomId = roomId,
content = MessageVerificationCancelContent.create(transactionId, code).toContent()
)
) {
callbackThread = TaskThread.DM_VERIF
executionThread = TaskThread.DM_VERIF
constraints = TaskConstraints(true)
retryCount = 3
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)
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
userId = userId,
event = event
))
enqueueSendWork(workerParams)
}
override fun done(transactionId: String) {
sendVerificationMessageTask.configureWith(
sendVerificationMessageTask.createParamsAndLocalEcho(
EventType.KEY_VERIFICATION_DONE,
roomId,
MessageVerificationDoneContent(
val event = createEventAndLocalEcho(
type = EventType.KEY_VERIFICATION_DONE,
roomId = roomId,
content = MessageVerificationDoneContent(
relatesTo = RelationDefaultContent(
RelationType.REFERENCE,
transactionId
)
).toContent(),
cryptoService
).toContent()
)
) {
callbackThread = TaskThread.DM_VERIF
executionThread = TaskThread.DM_VERIF
constraints = TaskConstraints(true)
retryCount = 3
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
userId = userId,
event = event
))
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,
@ -178,16 +270,28 @@ internal class SasTransportRoomMessage(
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(
private val sendVerificationMessageTask: DefaultSendVerificationMessageTask,
private val taskExecutor: TaskExecutor) {
private val monarchy: Monarchy,
private val localEchoEventFactory: LocalEchoEventFactory) {
fun createTransport(roomId: String,
cryptoService: CryptoService,
tx: SASVerificationTransaction?
fun createTransport(context: Context, userId: String, userDevice: String, roomId: String, tx: SASVerificationTransaction?
): 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.SasVerificationTxState
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.rest.*
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
@ -33,6 +34,10 @@ internal class SasTransportToDevice(
private var taskExecutor: TaskExecutor
) : SasTransport {
override fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, callback: (String?, MessageVerificationRequestContent?) -> Unit) {
// TODO "not implemented"
}
override fun sendToOther(type: String,
verificationInfo: VerificationInfo,
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.session.Session
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.SessionAssistedInjectModule
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
@ -98,6 +99,8 @@ internal interface SessionComponent {
fun inject(addHttpPusherWorker: AddHttpPusherWorker)
fun inject(sendVerificationMessageWorker: SendVerificationMessageWorker)
@Component.Factory
interface Factory {
fun create(

View File

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

View File

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