Manage done states + cleaning

This commit is contained in:
Valere 2019-12-31 10:35:50 +01:00
parent 935b3d7f3f
commit 5b210df7c5
23 changed files with 115 additions and 54 deletions

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, callback: MatrixCallback<String>?): PendingVerificationRequest
fun beginKeyVerificationInDMs(method: String,
transactionId: String,
@ -76,4 +76,17 @@ interface SasVerificationService {
fun verificationRequestCreated(pr: PendingVerificationRequest) {}
fun verificationRequestUpdated(pr: PendingVerificationRequest) {}
}
companion object {
fun isValidRequest(age: Long?): Boolean {
if (age == null) return false
val now = System.currentTimeMillis()
val tooInThePast = now - (10 * 60 * 1000)
val fiveMinInMs = 5 * 60 * 1000
val tooInTheFuture = System.currentTimeMillis() + fiveMinInMs
return !(age < tooInThePast || age > tooInTheFuture)
}
}
}

View file

@ -17,6 +17,8 @@ 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.VerificationInfo
@ -24,5 +26,11 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfo
internal data class MessageVerificationDoneContent(
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
) : VerificationInfo {
override fun isValid() = true
override val transactionID: String?
get() = relatesTo?.eventId
override fun isValid() = transactionID?.isNotEmpty() == true
override fun toEventContent(): Content? = this.toContent()
}

View file

@ -37,7 +37,7 @@ internal data class KeyVerificationRequest(
val timestamp: Int,
@Json(name = "transaction_id")
var transactionID: String? = null
override var transactionID: String? = null
) : SendToDeviceObject, VerificationInfo {

View file

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.tasks
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
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.toModel
@ -52,27 +53,16 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
override suspend fun execute(params: RoomVerificationUpdateTask.Params) {
// TODO ignore initial sync or back pagination?
val now = System.currentTimeMillis()
val tooInThePast = now - (10 * 60 * 1000)
val fiveMinInMs = 5 * 60 * 1000
val tooInTheFuture = System.currentTimeMillis() + fiveMinInMs
params.events.forEach { event ->
Timber.d("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}")
Timber.v("## SAS Verification live observer: received msgId: $event")
// 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 ageLocalTs = event.ageLocalTs
if (ageLocalTs != null && (now - ageLocalTs) > fiveMinInMs) {
Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is too old (age: ${(now - ageLocalTs)})")
return@forEach
} else {
val eventOrigin = event.originServerTs ?: -1
if (eventOrigin < tooInThePast || eventOrigin > tooInTheFuture) {
Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is too old (ts: $eventOrigin")
return@forEach
}
if (!SasVerificationService.isValidRequest(event.ageLocalTs
?: event.originServerTs)) return@forEach Unit.also {
Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated")
}
// decrypt if needed?
@ -149,7 +139,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
EventType.KEY_VERIFICATION_DONE -> {
params.sasVerificationService.onRoomEvent(event)
}
EventType.MESSAGE -> {
EventType.MESSAGE -> {
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.type) {
params.sasVerificationService.onRoomRequestReceived(event)
}

View file

@ -77,6 +77,7 @@ internal class DefaultSasVerificationService @Inject constructor(
/**
* Map [sender: [PendingVerificationRequest]]
* For now we keep all requests (even terminated ones) during the lifetime of the app.
*/
private val pendingRequests = HashMap<String, ArrayList<PendingVerificationRequest>>()
@ -84,7 +85,7 @@ internal class DefaultSasVerificationService @Inject constructor(
fun onToDeviceEvent(event: Event) {
GlobalScope.launch(coroutineDispatchers.crypto) {
when (event.getClearType()) {
EventType.KEY_VERIFICATION_START -> {
EventType.KEY_VERIFICATION_START -> {
onStartRequestReceived(event)
}
EventType.KEY_VERIFICATION_CANCEL -> {
@ -93,13 +94,13 @@ internal class DefaultSasVerificationService @Inject constructor(
EventType.KEY_VERIFICATION_ACCEPT -> {
onAcceptReceived(event)
}
EventType.KEY_VERIFICATION_KEY -> {
EventType.KEY_VERIFICATION_KEY -> {
onKeyReceived(event)
}
EventType.KEY_VERIFICATION_MAC -> {
EventType.KEY_VERIFICATION_MAC -> {
onMacReceived(event)
}
else -> {
else -> {
// ignore
}
}
@ -109,7 +110,7 @@ internal class DefaultSasVerificationService @Inject constructor(
fun onRoomEvent(event: Event) {
GlobalScope.launch(coroutineDispatchers.crypto) {
when (event.getClearType()) {
EventType.KEY_VERIFICATION_START -> {
EventType.KEY_VERIFICATION_START -> {
onRoomStartRequestReceived(event)
}
EventType.KEY_VERIFICATION_CANCEL -> {
@ -119,24 +120,24 @@ internal class DefaultSasVerificationService @Inject constructor(
EventType.KEY_VERIFICATION_ACCEPT -> {
onRoomAcceptReceived(event)
}
EventType.KEY_VERIFICATION_KEY -> {
EventType.KEY_VERIFICATION_KEY -> {
onRoomKeyRequestReceived(event)
}
EventType.KEY_VERIFICATION_MAC -> {
EventType.KEY_VERIFICATION_MAC -> {
onRoomMacReceived(event)
}
EventType.KEY_VERIFICATION_READY -> {
EventType.KEY_VERIFICATION_READY -> {
onRoomReadyReceived(event)
}
EventType.KEY_VERIFICATION_DONE -> {
// TODO?
EventType.KEY_VERIFICATION_DONE -> {
onRoomDoneReceived(event)
}
EventType.MESSAGE -> {
EventType.MESSAGE -> {
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.type) {
onRoomRequestReceived(event)
}
}
else -> {
else -> {
// ignore
}
}
@ -247,6 +248,7 @@ internal class DefaultSasVerificationService @Inject constructor(
}
val pendingVerificationRequest = PendingVerificationRequest(
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
isIncoming = true,
otherUserId = senderId, // requestInfo.toUserId,
transactionId = event.eventId,
@ -411,7 +413,7 @@ internal class DefaultSasVerificationService @Inject constructor(
return
}
getExistingVerificationRequest(event.senderId ?: "", cancelReq.transactionID)?.let {
updateOutgoingPendingRequest(it.copy(cancelConclusion = safeValueOf(cancelReq.code)))
updatePendingRequest(it.copy(cancelConclusion = safeValueOf(cancelReq.code)))
// Should we remove it from the list?
}
handleOnCancel(event.senderId!!, cancelReq)
@ -433,14 +435,20 @@ internal class DefaultSasVerificationService @Inject constructor(
private fun handleOnCancel(otherUserId: String, cancelReq: VerificationInfoCancel) {
Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}")
val existing = getExistingTransaction(otherUserId, cancelReq.transactionID!!)
if (existing == null) {
Timber.e("## Received invalid cancel request")
return
val existingTransaction = getExistingTransaction(otherUserId, cancelReq.transactionID!!)
val existingRequest = getExistingVerificationRequest(otherUserId, cancelReq.transactionID!!)
if (existingRequest != null) {
// Mark this request as cancelled
updatePendingRequest(existingRequest.copy(
cancelConclusion = safeValueOf(cancelReq.code)
))
}
if (existing is SASVerificationTransaction) {
existing.cancelledReason = safeValueOf(cancelReq.code)
existing.state = SasVerificationTxState.OnCancelled
if (existingTransaction is SASVerificationTransaction) {
existingTransaction.cancelledReason = safeValueOf(cancelReq.code)
existingTransaction.state = SasVerificationTxState.OnCancelled
}
}
@ -558,6 +566,23 @@ internal class DefaultSasVerificationService @Inject constructor(
handleReadyReceived(event.senderId, readyReq)
}
private fun onRoomDoneReceived(event: Event) {
val doneReq = event.getClearContent().toModel<MessageVerificationDoneContent>()
?.copy(
// relates_to is in clear in encrypted payload
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
)
if (doneReq == null || doneReq.isValid().not() || event.senderId == null) {
// ignore
Timber.e("## SAS Received invalid Done request")
// TODO should we cancel?
return
}
handleDoneReceived(event.senderId, doneReq)
}
private fun onMacReceived(event: Event) {
val macReq = event.getClearContent().toModel<KeyVerificationMac>()!!
@ -589,7 +614,16 @@ internal class DefaultSasVerificationService @Inject constructor(
Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionID} fromDevice ${readyReq.fromDevice}")
return
}
updateOutgoingPendingRequest(existingRequest.copy(readyInfo = readyReq))
updatePendingRequest(existingRequest.copy(readyInfo = readyReq))
}
private fun handleDoneReceived(senderId: String, doneInfo: VerificationInfo) {
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionID }
if (existingRequest == null) {
Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionID}")
return
}
updatePendingRequest(existingRequest.copy(isSuccessful = true))
}
override fun getExistingTransaction(otherUser: String, tid: String): VerificationTransaction? {
@ -675,6 +709,7 @@ internal class DefaultSasVerificationService @Inject constructor(
cryptoService = cryptoService
)
val verificationRequest = PendingVerificationRequest(
ageLocalTs = System.currentTimeMillis(),
isIncoming = false,
localID = params.event.eventId ?: "",
otherUserId = userId
@ -694,7 +729,7 @@ internal class DefaultSasVerificationService @Inject constructor(
this.callback = object : MatrixCallback<SendResponse> {
override fun onSuccess(data: SendResponse) {
params.event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
updateOutgoingPendingRequest(verificationRequest.copy(
updatePendingRequest(verificationRequest.copy(
transactionId = data.eventId,
requestInfo = it
))
@ -713,7 +748,7 @@ internal class DefaultSasVerificationService @Inject constructor(
return verificationRequest
}
private fun updateOutgoingPendingRequest(updated: PendingVerificationRequest) {
private fun updatePendingRequest(updated: PendingVerificationRequest) {
val requestsForUser = pendingRequests[updated.otherUserId]
?: ArrayList<PendingVerificationRequest>().also {
pendingRequests[updated.otherUserId] = it
@ -768,7 +803,7 @@ internal class DefaultSasVerificationService @Inject constructor(
CancelCode.User,
null // TODO handle error?
)
updateOutgoingPendingRequest(it.copy(readyInfo = readyMsg))
updatePendingRequest(it.copy(readyInfo = readyMsg))
}
}

View file

@ -23,6 +23,7 @@ import java.util.*
* Stores current pending verification requests
*/
data class PendingVerificationRequest(
val ageLocalTs : Long,
val isIncoming: Boolean = false,
val localID: String = UUID.randomUUID().toString(),
val otherUserId: String,

View file

@ -22,4 +22,5 @@ interface VerificationInfo {
fun toEventContent(): Content? = null
fun toSendToDeviceObject(): SendToDeviceObject? = null
fun isValid() : Boolean
val transactionID: String?
}

View file

@ -17,7 +17,7 @@ package im.vector.matrix.android.internal.crypto.verification
internal interface VerificationInfoAccept : VerificationInfo {
val transactionID: String?
override val transactionID: String?
/**
* The key agreement protocol that Bobs device has selected to use, out of the list proposed by Alices device

View file

@ -17,7 +17,7 @@ package im.vector.matrix.android.internal.crypto.verification
internal interface VerificationInfoCancel : VerificationInfo {
val transactionID: String?
override val transactionID: String?
/**
* machine-readable reason for cancelling, see [CancelCode]
*/

View file

@ -20,7 +20,7 @@ package im.vector.matrix.android.internal.crypto.verification
*/
internal interface VerificationInfoKey : VerificationInfo {
val transactionID: String?
override val transactionID: String?
/**
* The devices ephemeral public key, as an unpadded base64 string
*/

View file

@ -17,7 +17,7 @@ package im.vector.matrix.android.internal.crypto.verification
internal interface VerificationInfoMac : VerificationInfo {
val transactionID: String?
override val transactionID: String?
/**
* A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key

View file

@ -24,7 +24,7 @@ package im.vector.matrix.android.internal.crypto.verification
*/
interface VerificationInfoReady : VerificationInfo {
val transactionID: String?
override val transactionID: String?
/**
* The ID of the device that sent the m.key.verification.ready message

View file

@ -28,7 +28,7 @@ internal interface VerificationInfoStart : VerificationInfo {
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid.
* Alices device should record this ID and use it in future messages in this transaction.
*/
val transactionID: String?
override val transactionID: String?
/**
* An array of key agreement protocols that Alices client understands.

View file

@ -31,6 +31,7 @@ import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.SimpleFragmentActivity
import im.vector.riotx.core.platform.WaitingViewData
// TODO Deprecated("replaced by bottomsheet UX")
class SASVerificationActivity : SimpleFragmentActivity() {
companion object {

View file

@ -29,6 +29,7 @@ import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
// TODO Deprecated("replaced by bottomsheet UX")
class SASVerificationIncomingFragment @Inject constructor(
private var avatarRenderer: AvatarRenderer
) : VectorBaseFragment() {

View file

@ -29,6 +29,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import javax.inject.Inject
// TODO Deprecated("replaced by bottomsheet UX")
class SASVerificationShortCodeFragment @Inject constructor(): VectorBaseFragment() {
private lateinit var viewModel: SasVerificationViewModel

View file

@ -32,6 +32,7 @@ import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.platform.VectorBaseFragment
import javax.inject.Inject
// TODO Deprecated("replaced by bottomsheet UX")
class SASVerificationStartFragment @Inject constructor(): VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_sas_verification_start

View file

@ -21,6 +21,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import javax.inject.Inject
// TODO Deprecated("replaced by bottomsheet UX")
class SASVerificationVerifiedFragment @Inject constructor() : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_sas_verification_verified

View file

@ -27,6 +27,7 @@ import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotx.core.utils.LiveEvent
import javax.inject.Inject
// TODO Deprecated("replaced by bottomsheet UX")
class SasVerificationViewModel @Inject constructor() : ViewModel(),
SasVerificationService.SasVerificationListener {

View file

@ -46,6 +46,7 @@ class EncryptionItemFactory @Inject constructor(private val stringProvider: Stri
eventId = event.root.eventId ?: "?",
senderId = event.root.senderId ?: "",
sendState = event.root.sendState,
ageLocalTS = event.root.ageLocalTs,
avatarUrl = event.senderAvatar,
memberName = event.getDisambiguatedDisplayName(),
showInformation = false,

View file

@ -73,6 +73,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
senderId = event.root.senderId ?: "",
sendState = event.root.sendState,
time = time,
ageLocalTS = event.root.ageLocalTs,
avatarUrl = avatarUrl,
memberName = formattedMemberName,
showInformation = showInformation,

View file

@ -28,6 +28,7 @@ data class MessageInformationData(
val senderId: String,
val sendState: SendState,
val time: CharSequence? = null,
val ageLocalTS : Long?,
val avatarUrl: String?,
val memberName: CharSequence? = null,
val showInformation: Boolean = true,

View file

@ -28,6 +28,7 @@ import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
import im.vector.matrix.android.internal.session.room.VerificationState
import im.vector.riotx.R
import im.vector.riotx.core.resources.ColorProvider
@ -72,7 +73,7 @@ abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestI
when (attributes.informationData.referencesInfoData?.verificationStatus) {
VerificationState.REQUEST,
null -> {
null -> {
holder.buttonBar.isVisible = !attributes.informationData.sentByMe
holder.statusTextView.text = null
holder.statusTextView.isVisible = false
@ -82,17 +83,17 @@ abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestI
holder.statusTextView.text = holder.view.context.getString(R.string.verification_request_other_cancelled, attributes.informationData.memberName)
holder.statusTextView.isVisible = true
}
VerificationState.CANCELED_BY_ME -> {
VerificationState.CANCELED_BY_ME -> {
holder.buttonBar.isVisible = false
holder.statusTextView.text = holder.view.context.getString(R.string.verification_request_you_cancelled)
holder.statusTextView.isVisible = true
}
VerificationState.WAITING -> {
VerificationState.WAITING -> {
holder.buttonBar.isVisible = false
holder.statusTextView.text = holder.view.context.getString(R.string.verification_request_waiting)
holder.statusTextView.isVisible = true
}
VerificationState.DONE -> {
VerificationState.DONE -> {
holder.buttonBar.isVisible = false
holder.statusTextView.text = if (attributes.informationData.sentByMe) {
holder.view.context.getString(R.string.verification_request_other_accepted, attributes.otherUserName)
@ -101,13 +102,16 @@ abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestI
}
holder.statusTextView.isVisible = true
}
else -> {
else -> {
holder.buttonBar.isVisible = false
holder.statusTextView.text = null
holder.statusTextView.isVisible = false
}
}
// Always hide buttons if request is too old
holder.buttonBar.isVisible = holder.buttonBar.isVisible && SasVerificationService.isValidRequest(attributes.informationData.ageLocalTS)
holder.callback = callback
holder.attributes = attributes