mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Prepare support for toDevice .request
This commit is contained in:
parent
ff95392e10
commit
4ddd831d7f
9 changed files with 196 additions and 35 deletions
|
@ -58,6 +58,14 @@ interface VerificationService {
|
|||
localId: String? = LocalEcho.createLocalEchoId()
|
||||
): PendingVerificationRequest
|
||||
|
||||
/**
|
||||
* Request a key verification from another user using toDevice events.
|
||||
*/
|
||||
fun requestKeyVerification(methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
otherDevices: List<String>?
|
||||
): PendingVerificationRequest
|
||||
|
||||
fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String)
|
||||
|
||||
// Only SAS method is supported for the moment
|
||||
|
|
|
@ -18,18 +18,33 @@ package im.vector.matrix.android.api.session.room.model.message
|
|||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRequest
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessageVerificationRequestContent(
|
||||
@Json(name = "msgtype") override val type: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
|
||||
@Json(name = "body") override val body: String,
|
||||
@Json(name = "from_device") val fromDevice: String,
|
||||
@Json(name = "methods") val methods: List<String>,
|
||||
@Json(name = "from_device") override val fromDevice: String?,
|
||||
@Json(name = "methods") override val methods: List<String>,
|
||||
@Json(name = "to") val toUserId: String,
|
||||
// @Json(name = "timestamp") val timestamp: Int,
|
||||
@Json(name = "timestamp") override val timestamp: Long?,
|
||||
@Json(name = "format") val format: String? = null,
|
||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
) : MessageContent, VerificationInfoRequest {
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override val transactionID: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
}
|
||||
|
|
|
@ -17,32 +17,26 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfo
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRequest
|
||||
|
||||
/**
|
||||
* Requests a key verification with another user's devices.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeyVerificationRequest(
|
||||
@Json(name = "from_device") override val fromDevice: String?,
|
||||
@Json(name = "methods") override val methods: List<String>,
|
||||
@Json(name = "methods") override val timestamp: Long?,
|
||||
@Json(name = "transaction_id") override var transactionID: String? = null
|
||||
|
||||
@Json(name = "from_device")
|
||||
val fromDevice: String,
|
||||
/** The verification methods supported by the sender. */
|
||||
val methods: List<String>,
|
||||
/**
|
||||
* The POSIX timestamp in milliseconds for when the request was made.
|
||||
* If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
|
||||
* the message should be ignored by the receiver.
|
||||
*/
|
||||
val timestamp: Int,
|
||||
) : SendToDeviceObject, VerificationInfoRequest {
|
||||
|
||||
@Json(name = "transaction_id")
|
||||
override var transactionID: String? = null
|
||||
|
||||
) : SendToDeviceObject, VerificationInfo {
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
// TODO
|
||||
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
|||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationRequest
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
||||
|
@ -124,6 +125,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
EventType.KEY_VERIFICATION_MAC -> {
|
||||
onMacReceived(event)
|
||||
}
|
||||
MessageType.MSGTYPE_VERIFICATION_REQUEST -> {
|
||||
onRequestReceived(event)
|
||||
}
|
||||
else -> {
|
||||
// ignore
|
||||
}
|
||||
|
@ -259,11 +263,51 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun onRequestReceived(event: Event) {
|
||||
val requestInfo = event.getClearContent().toModel<KeyVerificationRequest>()!!
|
||||
|
||||
if (!requestInfo.isValid()) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
return
|
||||
}
|
||||
val senderId = event.senderId ?: return
|
||||
|
||||
// We don't want to block here
|
||||
val otherDeviceId = requestInfo.fromDevice ?: return
|
||||
|
||||
GlobalScope.launch {
|
||||
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
|
||||
Timber.e("## Verification device $otherDeviceId is not knwon")
|
||||
}
|
||||
}
|
||||
|
||||
// Remember this request
|
||||
val requestsForUser = pendingRequests[senderId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[event.senderId] = it
|
||||
}
|
||||
|
||||
val pendingVerificationRequest = PendingVerificationRequest(
|
||||
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
|
||||
isIncoming = true,
|
||||
otherUserId = senderId, // requestInfo.toUserId,
|
||||
roomId = null,
|
||||
transactionId = event.eventId,
|
||||
requestInfo = requestInfo
|
||||
)
|
||||
requestsForUser.add(pendingVerificationRequest)
|
||||
dispatchRequestAdded(pendingVerificationRequest)
|
||||
}
|
||||
|
||||
|
||||
suspend fun onRoomRequestReceived(event: Event) {
|
||||
Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}")
|
||||
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>()
|
||||
?: return
|
||||
val senderId = event.senderId ?: return
|
||||
val fromDevice = requestInfo.fromDevice ?: return
|
||||
|
||||
if (requestInfo.toUserId != userId) {
|
||||
// I should ignore this, it's not for me
|
||||
|
@ -273,8 +317,8 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
|
||||
// We don't want to block here
|
||||
GlobalScope.launch {
|
||||
if (checkKeysAreDownloaded(senderId, requestInfo.fromDevice) == null) {
|
||||
Timber.e("## SAS Verification device ${requestInfo.fromDevice} is not knwon")
|
||||
if (checkKeysAreDownloaded(senderId, fromDevice) == null) {
|
||||
Timber.e("## SAS Verification device $fromDevice is not knwon")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -894,7 +938,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
.distinct()
|
||||
|
||||
transport.sendVerificationRequest(methodValues, localID, otherUserId, roomId) { syncedId, info ->
|
||||
transport.sendVerificationRequest(methodValues, localID, otherUserId, roomId, null) { syncedId, info ->
|
||||
// We need to update with the syncedID
|
||||
updatePendingRequest(verificationRequest.copy(
|
||||
transactionId = syncedId,
|
||||
|
@ -908,6 +952,69 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
return verificationRequest
|
||||
}
|
||||
|
||||
override fun requestKeyVerification(methods: List<VerificationMethod>, otherUserId: String, otherDevices: List<String>?): PendingVerificationRequest {
|
||||
// TODO refactor this with the DM one
|
||||
Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices")
|
||||
|
||||
val targetDevices = otherDevices ?: cryptoService.getUserDevices(otherUserId).map { it.deviceId }
|
||||
val requestsForUser = pendingRequests[otherUserId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[otherUserId] = it
|
||||
}
|
||||
|
||||
val transport = verificationTransportToDeviceFactory.createTransport(null)
|
||||
|
||||
// Cancel existing pending requests?
|
||||
requestsForUser.toImmutableList().forEach { existingRequest ->
|
||||
existingRequest.transactionId?.let { tid ->
|
||||
if (!existingRequest.isFinished) {
|
||||
Timber.d("## SAS, cancelling pending requests to start a new one")
|
||||
updatePendingRequest(existingRequest.copy(cancelConclusion = CancelCode.User))
|
||||
existingRequest.targetDevices?.forEach {
|
||||
transport.cancelTransaction(tid, existingRequest.otherUserId, it, CancelCode.User)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val localID = LocalEcho.createLocalEchoId()
|
||||
|
||||
val verificationRequest = PendingVerificationRequest(
|
||||
transactionId = localID,
|
||||
ageLocalTs = System.currentTimeMillis(),
|
||||
isIncoming = false,
|
||||
roomId = null,
|
||||
localID = localID,
|
||||
otherUserId = otherUserId,
|
||||
targetDevices = targetDevices
|
||||
)
|
||||
|
||||
// We can SCAN or SHOW QR codes only if cross-signing is enabled
|
||||
val methodValues = if (crossSigningService.isCrossSigningEnabled()) {
|
||||
// Add reciprocate method if application declares it can scan or show QR codes
|
||||
// Not sure if it ok to do that (?)
|
||||
val reciprocateMethod = methods
|
||||
.firstOrNull { it == VerificationMethod.QR_CODE_SCAN || it == VerificationMethod.QR_CODE_SHOW }
|
||||
?.let { listOf(VERIFICATION_METHOD_RECIPROCATE) }.orEmpty()
|
||||
methods.map { it.toValue() } + reciprocateMethod
|
||||
} else {
|
||||
// Filter out SCAN and SHOW qr code method
|
||||
methods
|
||||
.filter { it != VerificationMethod.QR_CODE_SHOW && it != VerificationMethod.QR_CODE_SCAN }
|
||||
.map { it.toValue() }
|
||||
}
|
||||
.distinct()
|
||||
|
||||
transport.sendVerificationRequest(methodValues, localID, otherUserId, null, targetDevices) { _, _ ->
|
||||
// Nothing special to do in to device mode
|
||||
}
|
||||
|
||||
requestsForUser.add(verificationRequest)
|
||||
dispatchRequestAdded(verificationRequest)
|
||||
|
||||
return verificationRequest
|
||||
}
|
||||
|
||||
override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) {
|
||||
verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
||||
.cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User)
|
||||
|
|
|
@ -34,11 +34,13 @@ data class PendingVerificationRequest(
|
|||
val otherUserId: String,
|
||||
val roomId: String?,
|
||||
val transactionId: String? = null,
|
||||
val requestInfo: MessageVerificationRequestContent? = null,
|
||||
val requestInfo: VerificationInfoRequest? = null,
|
||||
val readyInfo: VerificationInfoReady? = null,
|
||||
val cancelConclusion: CancelCode? = null,
|
||||
val isSuccessful: Boolean = false,
|
||||
val handledByOtherSession: Boolean = false
|
||||
val handledByOtherSession: Boolean = false,
|
||||
// In case of to device it is sent to a list of devices
|
||||
val targetDevices: List<String> ? = null
|
||||
) {
|
||||
val isReady: Boolean = readyInfo != null
|
||||
val isSent: Boolean = transactionId != null
|
||||
|
|
|
@ -17,7 +17,6 @@ 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.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||
|
||||
/**
|
||||
* Verification can be performed using toDevice events or via DM.
|
||||
|
@ -37,8 +36,9 @@ internal interface VerificationTransport {
|
|||
fun sendVerificationRequest(supportedMethods: List<String>,
|
||||
localID: String,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
callback: (String?, MessageVerificationRequestContent?) -> Unit)
|
||||
roomId: String?,
|
||||
toDevices: List<String>?,
|
||||
callback: (String?, VerificationInfoRequest?) -> Unit)
|
||||
|
||||
fun cancelTransaction(transactionId: String,
|
||||
otherUserId: String,
|
||||
|
|
|
@ -140,12 +140,17 @@ internal class VerificationTransportRoomMessage(
|
|||
override fun sendVerificationRequest(supportedMethods: List<String>,
|
||||
localID: String,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
callback: (String?, MessageVerificationRequestContent?) -> Unit) {
|
||||
roomId: String?,
|
||||
toDevices: List<String>?,
|
||||
callback: (String?, VerificationInfoRequest?) -> Unit) {
|
||||
// This transport requires a room
|
||||
requireNotNull(roomId)
|
||||
|
||||
val info = MessageVerificationRequestContent(
|
||||
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
|
||||
fromDevice = userDeviceId ?: "",
|
||||
toUserId = otherUserId,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
methods = supportedMethods
|
||||
)
|
||||
val content = info.toContent()
|
||||
|
|
|
@ -19,17 +19,19 @@ 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.VerificationTxState
|
||||
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.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationReady
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationRequest
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||
import im.vector.matrix.android.internal.di.DeviceId
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import timber.log.Timber
|
||||
|
@ -38,15 +40,41 @@ import javax.inject.Inject
|
|||
internal class VerificationTransportToDevice(
|
||||
private var tx: DefaultVerificationTransaction?,
|
||||
private var sendToDeviceTask: SendToDeviceTask,
|
||||
private val myDeviceId: String?,
|
||||
private var taskExecutor: TaskExecutor
|
||||
) : VerificationTransport {
|
||||
|
||||
override fun sendVerificationRequest(supportedMethods: List<String>,
|
||||
localID: String,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
callback: (String?, MessageVerificationRequestContent?) -> Unit) {
|
||||
// TODO "not implemented"
|
||||
roomId: String?,
|
||||
toDevices: List<String>?,
|
||||
callback: (String?, VerificationInfoRequest?) -> Unit) {
|
||||
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
val keyReq = KeyVerificationRequest(
|
||||
fromDevice = myDeviceId,
|
||||
methods = supportedMethods,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
transactionID = localID
|
||||
)
|
||||
toDevices?.forEach {
|
||||
contentMap.setObject(otherUserId, it, keyReq)
|
||||
}
|
||||
sendToDeviceTask
|
||||
.configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localID)) {
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.v("## verification [$tx.transactionId] send toDevice request success")
|
||||
callback.invoke(localID, keyReq)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e("## verification [$tx.transactionId] failed to send toDevice request")
|
||||
}
|
||||
}
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun sendToOther(type: String,
|
||||
|
@ -168,9 +196,10 @@ internal class VerificationTransportToDevice(
|
|||
|
||||
internal class VerificationTransportToDeviceFactory @Inject constructor(
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
@DeviceId val myDeviceId: String?,
|
||||
private val taskExecutor: TaskExecutor) {
|
||||
|
||||
fun createTransport(tx: DefaultVerificationTransaction?): VerificationTransportToDevice {
|
||||
return VerificationTransportToDevice(tx, sendToDeviceTask, taskExecutor)
|
||||
return VerificationTransportToDevice(tx, sendToDeviceTask, myDeviceId, taskExecutor)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -298,6 +298,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
|
||||
fromDevice = fromDevice,
|
||||
toUserId = toUserId,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
methods = methods
|
||||
).toContent(),
|
||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
||||
|
|
Loading…
Reference in a new issue