From 6bf3a703df3f9bc916db938564092e91688af979 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 30 Dec 2019 17:20:43 +0100 Subject: [PATCH 01/12] BottomSheet UX --- .../crypto/sas/SasVerificationService.kt | 4 + .../tasks/RoomVerificationUpdateTask.kt | 164 +++++++++++++++ .../DefaultSasVerificationService.kt | 113 +++++++++- .../PendingVerificationRequest.kt | 39 ++++ .../SASVerificationTransaction.kt | 3 +- .../VerificationMessageLiveObserver.kt | 121 +---------- .../im/vector/riotx/core/di/FragmentModule.kt | 23 +++ .../vector/riotx/core/utils/SpannableUtils.kt | 58 ++++++ .../SASVerificationCodeFragment.kt | 164 +++++++++++++++ .../SASVerificationCodeViewModel.kt | 170 +++++++++++++++ .../SASVerificationStartFragment.kt | 2 +- .../verification/SasVerificationViewModel.kt | 1 + .../verification/VerificationBottomSheet.kt | 195 ++++++++++++++++++ .../VerificationBottomSheetViewModel.kt | 178 ++++++++++++++++ .../VerificationChooseMethodFragment.kt | 69 +++++++ .../VerificationChooseMethodViewModel.kt | 77 +++++++ .../VerificationConclusionFragment.kt | 73 +++++++ .../VerificationConclusionViewModel.kt | 61 ++++++ .../VerificationRequestFragment.kt | 83 ++++++++ .../VerificationRequestViewModel.kt | 109 ++++++++++ .../home/room/detail/RoomDetailFragment.kt | 21 +- .../home/room/detail/RoomDetailViewModel.kt | 29 ++- .../timeline/factory/MessageItemFactory.kt | 24 +-- .../timeline/factory/TimelineItemFactory.kt | 1 + .../timeline/format/NoticeEventFormatter.kt | 1 + .../helper/TimelineDisplayableEvents.kt | 3 +- .../fragment_bottom_sas_verification_code.xml | 195 ++++++++++++++++++ .../fragment_verification_choose_method.xml | 117 +++++++++++ .../fragment_verification_conclusion.xml | 65 ++++++ vector/src/main/res/values/strings.xml | 2 +- vector/src/main/res/values/strings_riotX.xml | 22 +- 31 files changed, 2034 insertions(+), 153 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt create mode 100644 vector/src/main/java/im/vector/riotx/core/utils/SpannableUtils.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt create mode 100644 vector/src/main/res/layout/fragment_bottom_sas_verification_code.xml create mode 100644 vector/src/main/res/layout/fragment_verification_choose_method.xml create mode 100644 vector/src/main/res/layout/fragment_verification_conclusion.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt index 3c3c43dbd4..cc3b57da20 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt @@ -39,6 +39,10 @@ interface SasVerificationService { fun getExistingTransaction(otherUser: String, tid: String): SasVerificationTransaction? + fun getExistingVerificationRequest(otherUser: String): List? + + fun getExistingVerificationRequest(otherUser: String, tid: String?): PendingVerificationRequest? + /** * Shortcut for KeyVerificationStart.VERIF_METHOD_SAS * @see beginKeyVerification diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt new file mode 100644 index 0000000000..0cbefd5f3b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt @@ -0,0 +1,164 @@ +/* + * 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 im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.crypto.MXCryptoError +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 +import im.vector.matrix.android.api.session.room.model.message.* +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult +import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService +import im.vector.matrix.android.internal.di.DeviceId +import im.vector.matrix.android.internal.di.SessionDatabase +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.task.Task +import io.realm.RealmConfiguration +import timber.log.Timber +import java.util.* +import javax.inject.Inject + +internal interface RoomVerificationUpdateTask : Task { + data class Params( + val events: List, + val sasVerificationService: DefaultSasVerificationService, + val cryptoService: CryptoService + ) +} + +internal class DefaultRoomVerificationUpdateTask @Inject constructor( + @UserId private val userId: String, + @DeviceId private val deviceId: String?, + private val cryptoService: CryptoService) : RoomVerificationUpdateTask { + + companion object { + // XXX what about multi-account? + private val transactionsHandledByOtherDevice = ArrayList() + } + + override suspend fun execute(params: RoomVerificationUpdateTask.Params): Unit { + + // 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 + } + } + + // decrypt if needed? + if (event.isEncrypted() && event.mxDecryptionResult == null) { + // TODO use a global event decryptor? attache to session and that listen to new sessionId? + // for now decrypt sync + try { + val result = cryptoService.decryptEvent(event, event.roomId + UUID.randomUUID().toString()) + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + Timber.e("## SAS Failed to decrypt event: ${event.eventId}") + } + } + Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") + + if (event.senderId == userId) { + // If it's send from me, we need to keep track of Requests or Start + // done from another device of mine + + if (EventType.MESSAGE == event.type) { + val msgType = event.getClearContent().toModel()?.type + if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) { + event.getClearContent().toModel()?.let { + if (it.fromDevice != deviceId) { + // The verification is requested from another device + Timber.v("## SAS Verification live observer: Transaction requested from other device tid:${event.eventId} ") + event.eventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + } + } + } + } else if (EventType.KEY_VERIFICATION_START == event.type) { + event.getClearContent().toModel()?.let { + if (it.fromDevice != deviceId) { + // The verification is started from another device + Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") + it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + } + } + } else if (EventType.KEY_VERIFICATION_READY == event.type) { + event.getClearContent().toModel()?.let { + if (it.fromDevice != deviceId) { + // The verification is started from another device + Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") + it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + } + } + } else if (EventType.KEY_VERIFICATION_CANCEL == event.type || EventType.KEY_VERIFICATION_DONE == event.type) { + event.getClearContent().toModel()?.relatesTo?.eventId?.let { + transactionsHandledByOtherDevice.remove(it) + } + } + + return@forEach + } + + val relatesTo = event.getClearContent().toModel()?.relatesTo?.eventId + if (relatesTo != null && transactionsHandledByOtherDevice.contains(relatesTo)) { + // Ignore this event, it is directed to another of my devices + Timber.v("## SAS Verification live observer: Ignore Transaction handled by other device tid:$relatesTo ") + return@forEach + } + when (event.getClearType()) { + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_READY, + EventType.KEY_VERIFICATION_DONE -> { + params.sasVerificationService.onRoomEvent(event) + } + EventType.MESSAGE -> { + if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.type) { + params.sasVerificationService.onRoomRequestReceived(event) + } + } + } + } + } + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 1d50fc89fe..45acc80e40 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -189,9 +189,49 @@ internal class DefaultSasVerificationService @Inject constructor( } } - fun onRoomRequestReceived(event: Event) { - // TODO + suspend fun onRoomRequestReceived(event: Event) { Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}") + val requestInfo = event.getClearContent().toModel() + ?: return + val senderId = event.senderId ?: return + + if (requestInfo.toUserId != credentials.userId) { + //I should ignore this, it's not for me + Timber.w("## SAS Verification ignoring request from ${event.senderId}, not sent to me") + return + } + + if(checkKeysAreDownloaded(senderId, requestInfo.fromDevice) == null) { + //I should ignore this, it's not for me + Timber.e("## SAS Verification device ${requestInfo.fromDevice} is not knwon") + // TODO cancel? + return + } + + // Remember this request + val requestsForUser = pendingRequests[senderId] + ?: ArrayList().also { + pendingRequests[event.senderId] = it + } + + val pendingVerificationRequest = PendingVerificationRequest( + isIncoming = true, + otherUserId = senderId,//requestInfo.toUserId, + transactionId = event.eventId, + requestInfo = requestInfo + ) + requestsForUser.add(pendingVerificationRequest) + + /* + * After the m.key.verification.ready event is sent, either party can send an m.key.verification.start event + * to begin the verification. + * If both parties send an m.key.verification.start event, and they both specify the same verification method, + * then the event sent by the user whose user ID is the smallest is used, and the other m.key.verification.start + * event is ignored. + * In the case of a single user verifying two of their devices, the device ID is compared instead. + * If both parties send an m.key.verification.start event, but they specify different verification methods, + * the verification should be cancelled with a code of m.unexpected_message. + */ } private suspend fun onRoomStartRequestReceived(event: Event) { @@ -263,7 +303,7 @@ internal class DefaultSasVerificationService @Inject constructor( private suspend fun handleStart(otherUserId: String?, startReq: VerificationInfoStart, txConfigure: (SASVerificationTransaction) -> Unit): CancelCode? { Timber.d("## SAS onStartRequestReceived ${startReq.transactionID!!}") - if (checkKeysAreDownloaded(otherUserId!!, startReq) != null) { + if (checkKeysAreDownloaded(otherUserId!!, startReq.fromDevice ?: "") != null) { Timber.v("## SAS onStartRequestReceived $startReq") val tid = startReq.transactionID!! val existing = getExistingTransaction(otherUserId, tid) @@ -311,11 +351,11 @@ internal class DefaultSasVerificationService @Inject constructor( } private suspend fun checkKeysAreDownloaded(otherUserId: String, - startReq: VerificationInfoStart): MXUsersDevicesMap? { + fromDevice: String): MXUsersDevicesMap? { return try { val keys = deviceListManager.downloadKeys(listOf(otherUserId), true) val deviceIds = keys.getUserDeviceIds(otherUserId) ?: return null - keys.takeIf { deviceIds.contains(startReq.fromDevice) } + keys.takeIf { deviceIds.contains(fromDevice) } } catch (e: Exception) { null } @@ -333,6 +373,10 @@ internal class DefaultSasVerificationService @Inject constructor( // TODO should we cancel? return } + getExistingVerificationRequest(event.senderId ?: "", cancelReq.transactionID)?.let { + updateOutgoingPendingRequest(it.copy(cancelConclusion = safeValueOf(cancelReq.code))) + // Should we remove it from the list? + } handleOnCancel(event.senderId!!, cancelReq) } @@ -456,6 +500,28 @@ internal class DefaultSasVerificationService @Inject constructor( handleMacReceived(event.senderId, macReq) } + private suspend fun onRoomReadyReceived(event: Event) { + val readyReq = event.getClearContent().toModel() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.relatesTo + ) + if (readyReq == null || readyReq.isValid().not() || event.senderId == null) { + // ignore + Timber.e("## SAS Received invalid ready request") + // TODO should we cancel? + return + } + if(checkKeysAreDownloaded(event.senderId, readyReq.fromDevice ?: "") == null) { + Timber.e("## SAS Verification device ${readyReq.fromDevice} is not knwown") + // TODO cancel? + return + } + + + handleReadyReceived(event.senderId, readyReq) + } + private fun onMacReceived(event: Event) { val macReq = event.getClearContent().toModel()!! @@ -487,6 +553,18 @@ internal class DefaultSasVerificationService @Inject constructor( } } + override fun getExistingVerificationRequest(otherUser: String): List? { + synchronized(lock = pendingRequests) { + return pendingRequests[otherUser] + } + } + + override fun getExistingVerificationRequest(otherUser: String, tid: String?): PendingVerificationRequest? { + synchronized(lock = pendingRequests) { + return tid?.let { tid -> pendingRequests[otherUser]?.firstOrNull { it.transactionId == tid } } + } + } + private fun getExistingTransactionsForUser(otherUser: String): Collection? { synchronized(txMap) { return txMap[otherUser]?.values @@ -536,7 +614,30 @@ internal class DefaultSasVerificationService @Inject constructor( } } - override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) { + override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) + : PendingVerificationRequest { + + Timber.i("## SAS Requesting verification to user: $userId in room ${roomId}") + val requestsForUser = pendingRequests[userId] + ?: ArrayList().also { + pendingRequests[userId] = it + } + + val params = requestVerificationDMTask.createParamsAndLocalEcho( + roomId = roomId, + from = credentials.deviceId ?: "", + methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS), + to = userId, + cryptoService = cryptoService + ) + val verificationRequest = PendingVerificationRequest( + isIncoming = false, + localID = params.event.eventId ?: "", + otherUserId = userId + ) + requestsForUser.add(verificationRequest) + dispatchRequestAdded(verificationRequest) + requestVerificationDMTask.configureWith( requestVerificationDMTask.createParamsAndLocalEcho( roomId = roomId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt new file mode 100644 index 0000000000..6447b8668b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt @@ -0,0 +1,39 @@ +/* + * 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.verification + +import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent +import java.util.* + +/** + * Stores current pending verification requests + */ +data class PendingVerificationRequest( + val isIncoming: Boolean = false, + val localID: String = UUID.randomUUID().toString(), + val otherUserId: String, + val transactionId: String? = null, + val requestInfo: MessageVerificationRequestContent? = null, + val readyInfo: VerificationInfoReady? = null, + val cancelConclusion: CancelCode? = null, + val isSuccessful : Boolean = false + +) { + + val isReady: Boolean = readyInfo != null + val isSent: Boolean = transactionId != null +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt index 31d6fd4b5c..c0e3d292c8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt @@ -222,13 +222,14 @@ internal abstract class SASVerificationTransaction( val keyIDNoPrefix = if (it.startsWith("ed25519:")) it.substring("ed25519:".length) else it val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint() if (otherDeviceKey == null) { - Timber.e("Verification: Could not find device $keyIDNoPrefix to verify") + Timber.e("## SAS Verification: Could not find device $keyIDNoPrefix to verify") // just ignore and continue return@forEach } val mac = macUsingAgreedMethod(otherDeviceKey, baseInfo + it) if (mac != theirMac?.mac?.get(it)) { // WRONG! + Timber.e("## SAS Verification: mac mismatch for $otherDeviceKey with id $keyIDNoPrefix") cancel(CancelCode.MismatchedKeys) return } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt index 2fee568895..1b25e32d7c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt @@ -17,38 +17,31 @@ package im.vector.matrix.android.internal.crypto.verification import com.zhuinden.monarchy.Monarchy 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.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.algorithms.olm.OlmDecryptionResult +import im.vector.matrix.android.internal.crypto.tasks.DefaultRoomVerificationUpdateTask +import im.vector.matrix.android.internal.crypto.tasks.RoomVerificationUpdateTask import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.types -import im.vector.matrix.android.internal.di.DeviceId import im.vector.matrix.android.internal.di.SessionDatabase -import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith import io.realm.OrderedCollectionChangeSet import io.realm.RealmConfiguration import io.realm.RealmResults -import timber.log.Timber -import java.util.* import javax.inject.Inject -import kotlin.collections.ArrayList internal class VerificationMessageLiveObserver @Inject constructor( @SessionDatabase realmConfiguration: RealmConfiguration, - @UserId private val userId: String, - @DeviceId private val deviceId: String?, + private val roomVerificationUpdateTask: DefaultRoomVerificationUpdateTask, private val cryptoService: CryptoService, private val sasVerificationService: DefaultSasVerificationService, private val taskExecutor: TaskExecutor ) : RealmLiveEntityObserver(realmConfiguration) { - override val query = Monarchy.Query { + override val query = Monarchy.Query { EventEntity.types(it, listOf( EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_ACCEPT, @@ -61,11 +54,8 @@ internal class VerificationMessageLiveObserver @Inject constructor( ) } - val transactionsHandledByOtherDevice = ArrayList() - override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { - // TODO do that in a task - // TODO how to ignore when it's an initial sync? + // Should we ignore when it's an initial sync? val events = changeSet.insertions .asSequence() .mapNotNull { results[it]?.asDomain() } @@ -75,102 +65,9 @@ internal class VerificationMessageLiveObserver @Inject constructor( } .toList() - // TODO ignore initial sync or back pagination? + roomVerificationUpdateTask.configureWith( + RoomVerificationUpdateTask.Params(events, sasVerificationService, cryptoService) + ).executeBy(taskExecutor) - val now = System.currentTimeMillis() - val tooInThePast = now - (10 * 60 * 1000) - val fiveMinInMs = 5 * 60 * 1000 - val tooInTheFuture = System.currentTimeMillis() + fiveMinInMs - - 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 - } - } - - // decrypt if needed? - if (event.isEncrypted() && event.mxDecryptionResult == null) { - // TODO use a global event decryptor? attache to session and that listen to new sessionId? - // for now decrypt sync - try { - val result = cryptoService.decryptEvent(event, event.roomId + UUID.randomUUID().toString()) - event.mxDecryptionResult = OlmDecryptionResult( - payload = result.clearEvent, - senderKey = result.senderCurve25519Key, - keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, - forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain - ) - } catch (e: MXCryptoError) { - Timber.e("## SAS Failed to decrypt event: ${event.eventId}") - } - } - Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") - - if (event.senderId == userId) { - // If it's send from me, we need to keep track of Requests or Start - // done from another device of mine - - if (EventType.MESSAGE == event.type) { - val msgType = event.getClearContent().toModel()?.type - if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) { - event.getClearContent().toModel()?.let { - if (it.fromDevice != deviceId) { - // The verification is requested from another device - Timber.v("## SAS Verification live observer: Transaction requested from other device tid:${event.eventId} ") - event.eventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } - } - } - } - } else if (EventType.KEY_VERIFICATION_START == event.type) { - event.getClearContent().toModel()?.let { - if (it.fromDevice != deviceId) { - // The verification is started from another device - Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") - it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } - } - } - } else if (EventType.KEY_VERIFICATION_CANCEL == event.type || EventType.KEY_VERIFICATION_DONE == event.type) { - event.getClearContent().toModel()?.relatesTo?.eventId?.let { - transactionsHandledByOtherDevice.remove(it) - } - } - - return@forEach - } - - val relatesTo = event.getClearContent().toModel()?.relatesTo?.eventId - if (relatesTo != null && transactionsHandledByOtherDevice.contains(relatesTo)) { - // Ignore this event, it is directed to another of my devices - Timber.v("## SAS Verification live observer: Ignore Transaction handled by other device tid:$relatesTo ") - return@forEach - } - when (event.getClearType()) { - EventType.KEY_VERIFICATION_START, - EventType.KEY_VERIFICATION_ACCEPT, - EventType.KEY_VERIFICATION_KEY, - EventType.KEY_VERIFICATION_MAC, - EventType.KEY_VERIFICATION_CANCEL, - EventType.KEY_VERIFICATION_DONE -> { - sasVerificationService.onRoomEvent(event) - } - EventType.MESSAGE -> { - if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.type) { - sasVerificationService.onRoomRequestReceived(event) - } - } - } - } } } diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index d457581c8e..5fde9c1444 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -272,4 +272,27 @@ interface FragmentModule { @IntoMap @FragmentKey(SoftLogoutFragment::class) fun bindSoftLogoutFragment(fragment: SoftLogoutFragment): Fragment + + + @Binds + @IntoMap + @FragmentKey(VerificationRequestFragment::class) + fun bindVerificationRequestFragment(fragment: VerificationRequestFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(VerificationChooseMethodFragment::class) + fun bindVerificationMethodChooserFragment(fragment: VerificationChooseMethodFragment): Fragment + + + @Binds + @IntoMap + @FragmentKey(SASVerificationCodeFragment::class) + fun bindVerificationSasCodeFragment(fragment: SASVerificationCodeFragment): Fragment + + + @Binds + @IntoMap + @FragmentKey(VerificationConclusionFragment::class) + fun bindVerificationSasConclusionFragment(fragment: VerificationConclusionFragment): Fragment } diff --git a/vector/src/main/java/im/vector/riotx/core/utils/SpannableUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/SpannableUtils.kt new file mode 100644 index 0000000000..1b56fc2a57 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/utils/SpannableUtils.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2018 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.riotx.core.utils + +import android.text.Spannable +import android.text.style.BulletSpan +import android.text.style.ClickableSpan +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan +import androidx.annotation.ColorInt +import me.gujun.android.span.Span + +fun Spannable.styleMatchingText(match: String, typeFace: Int): Spannable { + if (match.isEmpty()) return this + indexOf(match).takeIf { it != -1 }?.let { start -> + this.setSpan(StyleSpan(typeFace), start, start + match.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + return this +} + +fun Spannable.colorizeMatchingText(match: String, @ColorInt color: Int): Spannable { + if (match.isEmpty()) return this + indexOf(match).takeIf { it != -1 }?.let { start -> + this.setSpan(ForegroundColorSpan(color), start, start + match.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + return this +} + +fun Spannable.tappableMatchingText(match: String, clickSpan: ClickableSpan): Spannable { + if (match.isEmpty()) return this + indexOf(match).takeIf { it != -1 }?.let { start -> + this.setSpan(clickSpan, start, start + match.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + return this +} + +fun Span.bullet(text: CharSequence = "", + init: Span.() -> Unit = {}): Span = apply { + append(Span(parent = this).apply { + this.text = text + this.spans.add(BulletSpan()) + init() + build() + }) +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt new file mode 100644 index 0000000000..ebd7f351a4 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt @@ -0,0 +1,164 @@ +/* + * 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.riotx.features.crypto.verification + +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import butterknife.BindView +import butterknife.OnClick +import com.airbnb.mvrx.* +import im.vector.riotx.R +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.core.platform.parentFragmentViewModel +import kotlinx.android.synthetic.main.fragment_bottom_sas_verification_code.* +import javax.inject.Inject + +class SASVerificationCodeFragment @Inject constructor( + val viewModelFactory: SASVerificationCodeViewModel.Factory +) : VectorBaseFragment() { + + override fun getLayoutResId() = R.layout.fragment_bottom_sas_verification_code + + @BindView(R.id.sas_emoji_grid) + lateinit var emojiGrid: ViewGroup + + + @BindView(R.id.sas_decimal_code) + lateinit var decimalTextView: TextView + + @BindView(R.id.emoji0) + lateinit var emoji0View: ViewGroup + @BindView(R.id.emoji1) + lateinit var emoji1View: ViewGroup + @BindView(R.id.emoji2) + lateinit var emoji2View: ViewGroup + @BindView(R.id.emoji3) + lateinit var emoji3View: ViewGroup + @BindView(R.id.emoji4) + lateinit var emoji4View: ViewGroup + @BindView(R.id.emoji5) + lateinit var emoji5View: ViewGroup + @BindView(R.id.emoji6) + lateinit var emoji6View: ViewGroup + + + private val viewModel by fragmentViewModel(SASVerificationCodeViewModel::class) + + private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) + + override fun invalidate() = withState(viewModel) { state -> + + if (state.supportsEmoji) { + decimalTextView.isVisible = false + when(val emojiDescription = state.emojiDescription) { + is Success -> { + sasLoadingProgress.isVisible = false + emojiGrid.isVisible = true + ButtonsVisibilityGroup.isVisible = true + emojiDescription.invoke().forEachIndexed { index, emojiRepresentation -> + when (index) { + 0 -> { + emoji0View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji + emoji0View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) + } + 1 -> { + emoji1View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji + emoji1View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) + } + 2 -> { + emoji2View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji + emoji2View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) + } + 3 -> { + emoji3View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji + emoji3View.findViewById(R.id.item_emoji_name_tv)?.setText(emojiRepresentation.nameResId) + } + 4 -> { + emoji4View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji + emoji4View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) + } + 5 -> { + emoji5View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji + emoji5View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) + } + 6 -> { + emoji6View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji + emoji6View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) + } + } + } + + if (state.isWaitingFromOther) { + //hide buttons + ButtonsVisibilityGroup.isInvisible = true + sasCodeWaitingPartnerText.isVisible = true + } else { + ButtonsVisibilityGroup.isVisible = true + sasCodeWaitingPartnerText.isVisible = false + } + + } + is Fail -> { + sasLoadingProgress.isVisible = false + emojiGrid.isInvisible = true + ButtonsVisibilityGroup.isInvisible = true + //TODO? + } + else -> { + sasLoadingProgress.isVisible = true + emojiGrid.isInvisible = true + ButtonsVisibilityGroup.isInvisible = true + } + } + } else { + //Decimal + emojiGrid.isInvisible = true + decimalTextView.isVisible = true + val decimalCode = state.decimalDescription.invoke() + decimalTextView.text = decimalCode + + //TODO + if (state.isWaitingFromOther) { + //hide buttons + ButtonsVisibilityGroup.isInvisible = true + sasCodeWaitingPartnerText.isVisible = true + } else { + ButtonsVisibilityGroup.isVisible = decimalCode != null + sasCodeWaitingPartnerText.isVisible = false + } + } + } + + + @OnClick(R.id.sas_request_continue_button) + fun onMatchButtonTapped() = withState(viewModel) { state -> + //UX echo + ButtonsVisibilityGroup.isInvisible = true + sasCodeWaitingPartnerText.isVisible = true + sharedViewModel.handle(VerificationAction.SASMatchAction(state.otherUserId, state.transactionId)) + } + + @OnClick(R.id.sas_request_cancel_button) + fun onDoNotMatchButtonTapped() = withState(viewModel) { state -> + //UX echo + ButtonsVisibilityGroup.isInvisible = true + sasCodeWaitingPartnerText.isVisible = true + sharedViewModel.handle(VerificationAction.SASDoNotMatchAction(state.otherUserId, state.transactionId)) + } + +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt new file mode 100644 index 0000000000..5fa9cc6cff --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt @@ -0,0 +1,170 @@ +/* + * 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.riotx.features.crypto.verification + +import com.airbnb.mvrx.* +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState +import im.vector.matrix.android.api.util.MatrixItem +import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.VectorViewModel + +data class SASVerificationCodeViewState( + val transactionId: String, + val otherUserId: String, + val otherUser: MatrixItem? = null, + val supportsEmoji: Boolean = true, + val emojiDescription: Async> = Uninitialized, + val decimalDescription: Async = Uninitialized, + val isWaitingFromOther: Boolean = false +) : MvRxState + +class SASVerificationCodeViewModel @AssistedInject constructor( + @Assisted initialState: SASVerificationCodeViewState, + private val session: Session +) : VectorViewModel(initialState) + , SasVerificationService.SasVerificationListener { + + init { + withState { state -> + val matrixItem = session.getUser(state.otherUserId)?.toMatrixItem() + setState { + copy(otherUser = matrixItem) + } + val sasTx = session.getSasVerificationService() + .getExistingTransaction(state.otherUserId, state.transactionId) + if (sasTx == null) { + setState { + copy( + isWaitingFromOther = false, + emojiDescription = Fail(Throwable("Unknown Transaction")), + decimalDescription = Fail(Throwable("Unknown Transaction")) + ) + } + } else { + refreshStateFromTx(sasTx) + } + } + + session.getSasVerificationService().addListener(this) + } + + override fun onCleared() { + session.getSasVerificationService().removeListener(this) + super.onCleared() + } + + private fun refreshStateFromTx(sasTx: SasVerificationTransaction) { + when (sasTx.state) { + SasVerificationTxState.None, + SasVerificationTxState.SendingStart, + SasVerificationTxState.Started, + SasVerificationTxState.OnStarted, + SasVerificationTxState.SendingAccept, + SasVerificationTxState.Accepted, + SasVerificationTxState.OnAccepted, + SasVerificationTxState.SendingKey, + SasVerificationTxState.KeySent, + SasVerificationTxState.OnKeyReceived -> { + setState { + copy( + isWaitingFromOther = false, + supportsEmoji = sasTx.supportsEmoji(), + emojiDescription = Loading>() + .takeIf { sasTx.supportsEmoji() } + ?: Uninitialized, + decimalDescription = Loading() + .takeIf { sasTx.supportsEmoji().not() } + ?: Uninitialized + ) + } + } + SasVerificationTxState.ShortCodeReady -> { + setState { + copy( + isWaitingFromOther = false, + supportsEmoji = sasTx.supportsEmoji(), + emojiDescription = if (sasTx.supportsEmoji()) Success(sasTx.getEmojiCodeRepresentation()) + else Uninitialized, + decimalDescription = if (!sasTx.supportsEmoji()) Success(sasTx.getDecimalCodeRepresentation()) + else Uninitialized + ) + } + } + SasVerificationTxState.ShortCodeAccepted, + SasVerificationTxState.SendingMac, + SasVerificationTxState.MacSent, + SasVerificationTxState.Verifying, + SasVerificationTxState.Verified -> { + setState { + copy(isWaitingFromOther = true) + } + } + SasVerificationTxState.Cancelled, + SasVerificationTxState.OnCancelled -> { + // The fragment should not be rendered in this state, + // it should have been replaced by a conclusion fragment + setState { + copy( + isWaitingFromOther = false, + supportsEmoji = sasTx.supportsEmoji(), + emojiDescription = Fail(Throwable("Transaction Cancelled")), + decimalDescription = Fail(Throwable("Transaction Cancelled")) + ) + } + } + } + } + + override fun transactionCreated(tx: SasVerificationTransaction) { + transactionUpdated(tx) + } + + override fun transactionUpdated(tx: SasVerificationTransaction) { + refreshStateFromTx(tx) + } + + @AssistedInject.Factory + interface Factory { + fun create(initialState: SASVerificationCodeViewState): SASVerificationCodeViewModel + } + + companion object : MvRxViewModelFactory { + + override fun create(viewModelContext: ViewModelContext, state: SASVerificationCodeViewState): SASVerificationCodeViewModel? { + val factory = (viewModelContext as FragmentViewModelContext).fragment().viewModelFactory + return factory.create(state) + } + + override fun initialState(viewModelContext: ViewModelContext): SASVerificationCodeViewState? { + val args = viewModelContext.args() + return SASVerificationCodeViewState( + transactionId = args.verificationId ?: "", + otherUserId = args.otherUserId + ) + } + } + + override fun handle(action: EmptyAction) { + + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationStartFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationStartFragment.kt index d9c3b1d155..d33167518f 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationStartFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationStartFragment.kt @@ -91,7 +91,7 @@ class SASVerificationStartFragment @Inject constructor(): VectorBaseFragment() { (requireActivity() as VectorBaseActivity).notImplemented() /* - viewModel.session.crypto?.getDeviceInfo(viewModel.otherUserId ?: "", viewModel.otherDeviceId + viewModel.session.crypto?.getDeviceInfo(viewModel.otherUserMxItem ?: "", viewModel.otherDeviceId ?: "", object : SimpleApiCallback() { override fun onSuccess(info: MXDeviceInfo?) { info?.let { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt index f14a85c516..637df8818e 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.user.model.User +import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.riotx.core.utils.LiveEvent import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt new file mode 100644 index 0000000000..a92910cc92 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -0,0 +1,195 @@ +/* + * 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.riotx.features.crypto.verification + +import android.os.Bundle +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.text.toSpannable +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.transition.AutoTransition +import androidx.transition.TransitionManager +import butterknife.BindView +import butterknife.ButterKnife +import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState +import im.vector.riotx.R +import im.vector.riotx.core.di.ScreenComponent +import im.vector.riotx.core.extensions.commitTransactionNow +import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.riotx.core.utils.colorizeMatchingText +import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.themes.ThemeUtils +import kotlinx.android.parcel.Parcelize +import kotlinx.android.synthetic.main.bottom_sheet_verification.* +import javax.inject.Inject +import kotlin.reflect.KClass + +class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { + + @Parcelize + data class VerificationArgs( + val otherUserId: String, + val verificationId: String? = null, + val roomId: String? = null + ) : Parcelable + + + @Inject + lateinit var verificationRequestViewModelFactory: VerificationRequestViewModel.Factory + @Inject + lateinit var verificationViewModelFactory: VerificationBottomSheetViewModel.Factory + @Inject + lateinit var avatarRenderer: AvatarRenderer + + private val viewModel by fragmentViewModel(VerificationBottomSheetViewModel::class) + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + @BindView(R.id.verificationRequestName) + lateinit var otherUserNameText: TextView + + @BindView(R.id.verificationRequestAvatar) + lateinit var otherUserAvatarImageView: ImageView + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.bottom_sheet_verification, container, false) + ButterKnife.bind(this, view) + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewModel.requestLiveData.observe(this, Observer { + it.peekContent().let { va -> + when (va) { + is Success -> { + if (va.invoke() is VerificationAction.GotItConclusion) { + dismiss() + } + } + } + } + }) + } + + override fun invalidate() = withState(viewModel) { + + it.otherUserMxItem?.let { matrixItem -> + val displayName = matrixItem.displayName ?: "" + otherUserNameText.text = getString(R.string.verification_request_alert_title, displayName) + .toSpannable() + .colorizeMatchingText(displayName, ThemeUtils.getColor(requireContext(), R.attr.vctr_notice_text_color)) + + avatarRenderer.render(matrixItem, otherUserAvatarImageView) + } + + // Did the request result in a SAS transaction? + if (it.sasTransactionState != null) { + + when (it.sasTransactionState) { + SasVerificationTxState.None, + SasVerificationTxState.SendingStart, + SasVerificationTxState.Started, + SasVerificationTxState.OnStarted, + SasVerificationTxState.SendingAccept, + SasVerificationTxState.Accepted, + SasVerificationTxState.OnAccepted, + SasVerificationTxState.SendingKey, + SasVerificationTxState.KeySent, + SasVerificationTxState.OnKeyReceived, + SasVerificationTxState.ShortCodeReady, + SasVerificationTxState.ShortCodeAccepted, + SasVerificationTxState.SendingMac, + SasVerificationTxState.MacSent, + SasVerificationTxState.Verifying -> { + showFragment(SASVerificationCodeFragment::class, Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationArgs( + it.otherUserMxItem?.id ?: "", + it.pendingRequest?.transactionId)) + }) + } + SasVerificationTxState.Verified, + SasVerificationTxState.Cancelled, + SasVerificationTxState.OnCancelled -> { + showFragment(VerificationConclusionFragment::class, Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args( + it.sasTransactionState == SasVerificationTxState.Verified, + it.cancelCode?.value)) + }) + } + } + + return@withState + } + + + // Transaction has not yet started + if (it.pendingRequest == null || !it.pendingRequest.isReady) { + showFragment(VerificationRequestFragment::class, Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id ?: "")) + }) + } else if (it.pendingRequest.isReady) { + showFragment(VerificationChooseMethodFragment::class, Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id + ?: "", it.pendingRequest.transactionId)) + }) + + } + + super.invalidate() + } + + private fun showFragment(fragmentClass: KClass, bundle: Bundle) { + if (childFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) { + // We want to animate the bottomsheet bound changes + bottomSheetFragmentContainer.getParentCoordinatorLayout()?.let { coordinatorLayout -> + TransitionManager.beginDelayedTransition(coordinatorLayout, AutoTransition().apply { duration = 150 }) + } + // Commit now, to ensure changes occurs before next rendering frame (or bottomsheet want animate) + childFragmentManager.commitTransactionNow { + + replace(R.id.bottomSheetFragmentContainer, + fragmentClass.java, + bundle, + fragmentClass.simpleName + ) + } + } + } +} + + +fun View.getParentCoordinatorLayout(): CoordinatorLayout? { + var current = this as? View + while (current != null) { + if (current is CoordinatorLayout) return current + current = current.parent as? View + } + return null +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt new file mode 100644 index 0000000000..a9265293fa --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -0,0 +1,178 @@ +/* + * 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.riotx.features.crypto.verification + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.airbnb.mvrx.* +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState +import im.vector.matrix.android.api.util.MatrixItem +import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest +import im.vector.riotx.core.di.HasScreenInjector +import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.core.platform.VectorViewModelAction +import im.vector.riotx.core.utils.LiveEvent + + +data class VerificationBottomSheetViewState( + val otherUserMxItem: MatrixItem? = null, + val roomId: String? = null, + val pendingRequest: PendingVerificationRequest? = null, + val sasTransactionState: SasVerificationTxState? = null, + val cancelCode: CancelCode? = null +) : MvRxState + + +sealed class VerificationAction : VectorViewModelAction { + data class RequestVerificationByDM(val userID: String, val roomId: String?) : VerificationAction() + data class StartSASVerification(val userID: String, val pendingRequestTransactionId: String) : VerificationAction() + data class SASMatchAction(val userID: String, val sasTransactionId: String) : VerificationAction() + data class SASDoNotMatchAction(val userID: String, val sasTransactionId: String) : VerificationAction() + object GotItConclusion : VerificationAction() +} + +class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: VerificationBottomSheetViewState, + private val session: Session) + : VectorViewModel(initialState), + SasVerificationService.SasVerificationListener { + + + // Can be used for several actions, for a one shot result + private val _requestLiveData = MutableLiveData>>() + val requestLiveData: LiveData>> + get() = _requestLiveData + + init { + session.getSasVerificationService().addListener(this) + } + + override fun onCleared() { + session.getSasVerificationService().removeListener(this) + super.onCleared() + } + + @AssistedInject.Factory + interface Factory { + fun create(initialState: VerificationBottomSheetViewState): VerificationBottomSheetViewModel + } + + companion object : MvRxViewModelFactory { + + override fun create(viewModelContext: ViewModelContext, state: VerificationBottomSheetViewState): VerificationBottomSheetViewModel? { + val fragment: VerificationBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() + val args: VerificationBottomSheet.VerificationArgs = viewModelContext.args() + + val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() + + val userItem = session.getUser(args.otherUserId) + + val sasTx = state.pendingRequest?.transactionId?.let { + session.getSasVerificationService().getExistingTransaction(args.otherUserId, it) + } + + val pr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId) + ?.firstOrNull { it.transactionId == args.verificationId } + + return fragment.verificationViewModelFactory.create(VerificationBottomSheetViewState( + otherUserMxItem = userItem?.toMatrixItem(), + sasTransactionState = sasTx?.state, + pendingRequest = pr, + roomId = args.roomId) + ) + } + } + + override fun handle(action: VerificationAction) = withState { state -> + val otherUserId = state.otherUserMxItem?.id ?: return@withState + val roomId = state.roomId + ?: session.getExistingDirectRoomWithUser(otherUserId)?.roomId + ?: return@withState + when (action) { + is VerificationAction.RequestVerificationByDM -> { +// session + setState { + copy(pendingRequest = session.getSasVerificationService().requestKeyVerificationInDMs(otherUserId, roomId, null)) + } + } + is VerificationAction.StartSASVerification -> { + val request = session.getSasVerificationService().getExistingVerificationRequest(otherUserId) + ?.firstOrNull { it.transactionId == action.pendingRequestTransactionId } + ?: return@withState + + val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice + session.getSasVerificationService().beginKeyVerificationInDMs( + KeyVerificationStart.VERIF_METHOD_SAS, + transactionId = action.pendingRequestTransactionId, + roomId = roomId, + otherUserId = request.otherUserId, + otherDeviceId = otherDevice ?: "", + callback = null + ) + } + is VerificationAction.SASMatchAction -> { + session.getSasVerificationService() + .getExistingTransaction(action.userID, action.sasTransactionId) + ?.userHasVerifiedShortCode() + } + is VerificationAction.SASDoNotMatchAction -> { + session.getSasVerificationService() + .getExistingTransaction(action.userID, action.sasTransactionId) + ?.shortCodeDoNotMatch() + } + is VerificationAction.GotItConclusion -> { + _requestLiveData.postValue(LiveEvent(Success(action))) + } + } + } + + + override fun transactionCreated(tx: SasVerificationTransaction) { + transactionUpdated(tx) + } + + override fun transactionUpdated(tx: SasVerificationTransaction) = withState { state -> + if (tx.transactionId == state.pendingRequest?.transactionId) { + // A SAS tx has been started following this request + setState { + copy( + sasTransactionState = tx.state, + cancelCode = tx.cancelledReason + ) + } + } + } + + override fun verificationRequestCreated(pr: PendingVerificationRequest) { + verificationRequestUpdated(pr) + } + + override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> + + if (pr.localID == state.pendingRequest?.localID || state.pendingRequest?.transactionId == pr.transactionId) { + setState { + copy(pendingRequest = pr) + } + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt new file mode 100644 index 0000000000..69c599d335 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt @@ -0,0 +1,69 @@ +/* + * 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.riotx.features.crypto.verification + +import android.text.style.ClickableSpan +import android.view.View +import androidx.core.text.toSpannable +import androidx.core.view.isVisible +import butterknife.OnClick +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.riotx.R +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.core.platform.parentFragmentViewModel +import im.vector.riotx.core.utils.tappableMatchingText +import kotlinx.android.synthetic.main.fragment_verification_choose_method.* +import javax.inject.Inject + +class VerificationChooseMethodFragment @Inject constructor( + val verificationChooseMethodViewModelFactory: VerificationChooseMethodViewModel.Factory +) : VectorBaseFragment() { + + override fun getLayoutResId() = R.layout.fragment_verification_choose_method + + private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) + + private val viewModel by fragmentViewModel(VerificationChooseMethodViewModel::class) + + override fun invalidate() = withState(viewModel) { state -> + if (state.QRModeAvailable) { + val cSpan = object : ClickableSpan() { + override fun onClick(widget: View) { + + } + } + val openLink = getString(R.string.verify_open_camera_link) + val descCharSequence = + getString(R.string.verify_by_scanning_description, openLink) + .toSpannable() + .tappableMatchingText(openLink, cSpan) + verifyQRDescription.text = descCharSequence + verifyQRGroup.isVisible = true + } else { + verifyQRGroup.isVisible = false + } + + verifyEmojiGroup.isVisible = state.SASMOdeAvailable + } + + @OnClick(R.id.verificationByEmojiButton) + fun doVerifyBySas() = withState(sharedViewModel) { + sharedViewModel.handle(VerificationAction.StartSASVerification(it.otherUserMxItem?.id ?: "", it.pendingRequest?.transactionId + ?: "")) + } + +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt new file mode 100644 index 0000000000..38482e9b09 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt @@ -0,0 +1,77 @@ +/* + * 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.riotx.features.crypto.verification + +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.VectorViewModel + +data class VerificationChooseMethodViewState( + val otherUserId: String = "", + val transactionId: String = "", + val QRModeAvailable: Boolean = false, + val SASMOdeAvailable: Boolean = false +) : MvRxState + + +class VerificationChooseMethodViewModel @AssistedInject constructor( + @Assisted initialState: VerificationChooseMethodViewState, + private val session: Session +) : VectorViewModel(initialState) { + + + init { + withState { state -> + val pvr = session.getSasVerificationService().getExistingVerificationRequest(state.otherUserId)?.first { + it.transactionId == state.transactionId + } + val qrAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SCAN) ?: false + val emojiAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SAS) ?: false + setState { + copy(QRModeAvailable = qrAvailable, SASMOdeAvailable = emojiAvailable) + } + } + } + + @AssistedInject.Factory + interface Factory { + fun create(initialState: VerificationChooseMethodViewState): VerificationChooseMethodViewModel + } + + companion object : MvRxViewModelFactory { + override fun create(viewModelContext: ViewModelContext, state: VerificationChooseMethodViewState): VerificationChooseMethodViewModel? { + val fragment: VerificationChooseMethodFragment = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.verificationChooseMethodViewModelFactory.create(state) + } + + override fun initialState(viewModelContext: ViewModelContext): VerificationChooseMethodViewState? { + val args: VerificationBottomSheet.VerificationArgs = viewModelContext.args() + return VerificationChooseMethodViewState(otherUserId = args.otherUserId, transactionId = args.verificationId ?: "") + } + } + + + override fun handle(action: EmptyAction) {} + + +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt new file mode 100644 index 0000000000..da3b0dd187 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt @@ -0,0 +1,73 @@ +/* + * 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.riotx.features.crypto.verification + +import android.os.Parcelable +import androidx.core.content.ContextCompat +import butterknife.OnClick +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.riotx.R +import im.vector.riotx.core.extensions.setTextOrHide +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.core.platform.parentFragmentViewModel +import io.noties.markwon.Markwon +import kotlinx.android.parcel.Parcelize +import kotlinx.android.synthetic.main.fragment_verification_conclusion.* +import javax.inject.Inject + +class VerificationConclusionFragment @Inject constructor() : VectorBaseFragment() { + + @Parcelize + data class Args( + val isSuccessFull: Boolean, + val cancelReason: String? + ) : Parcelable + + override fun getLayoutResId() = R.layout.fragment_verification_conclusion + + private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) + + private val viewModel by fragmentViewModel(VerificationConclusionViewModel::class) + + override fun invalidate() = withState(viewModel) { + when (it.conclusionState) { + ConclusionState.SUCCESS -> { + verificationConclusionTitle.text = getString(R.string.sas_verified) + verifyConclusionDescription.setTextOrHide(getString(R.string.sas_verified_successful_description)) + verifyConclusionBottomDescription.text = getString(R.string.verification_green_shield) + verifyConclusionImageView.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_shield_trusted)) + + } + ConclusionState.WARNING -> { + verificationConclusionTitle.text = getString(R.string.verification_conclusion_not_secure) + verifyConclusionDescription.setTextOrHide(null) + verifyConclusionImageView.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_shield_warning)) + + verifyConclusionBottomDescription.text = Markwon.builder(requireContext()).build().toMarkdown(getString(R.string.verification_conclusion_compromised)) + } + ConclusionState.CANCELLED -> { + // Just dismiss in this case + sharedViewModel.handle(VerificationAction.GotItConclusion) + } + } + } + + @OnClick(R.id.verificationConclusionButton) + fun onButtonTapped() { + sharedViewModel.handle(VerificationAction.GotItConclusion) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionViewModel.kt new file mode 100644 index 0000000000..ca069bf853 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionViewModel.kt @@ -0,0 +1,61 @@ +/* + * 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.riotx.features.crypto.verification + +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.crypto.sas.safeValueOf +import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.VectorViewModel + +data class SASVerificationConclusionViewState( + val conclusionState: ConclusionState = ConclusionState.CANCELLED +) : MvRxState + +enum class ConclusionState { + SUCCESS, + WARNING, + CANCELLED +} + +class VerificationConclusionViewModel(initialState: SASVerificationConclusionViewState) + : VectorViewModel(initialState) { + + companion object : MvRxViewModelFactory { + + override fun initialState(viewModelContext: ViewModelContext): SASVerificationConclusionViewState? { + val args = viewModelContext.args() + + return when (safeValueOf(args.cancelReason)) { + CancelCode.MismatchedSas, + CancelCode.MismatchedCommitment, + CancelCode.MismatchedKeys -> { + SASVerificationConclusionViewState(ConclusionState.WARNING) + } + else -> { + SASVerificationConclusionViewState( + if (args.isSuccessFull) ConclusionState.SUCCESS + else ConclusionState.CANCELLED + ) + } + } + } + } + + override fun handle(action: EmptyAction) {} +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt new file mode 100644 index 0000000000..60b89c19d6 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt @@ -0,0 +1,83 @@ +/* + * 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.riotx.features.crypto.verification + +import android.graphics.Typeface +import androidx.core.text.toSpannable +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import butterknife.OnClick +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.riotx.R +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.core.platform.parentFragmentViewModel +import im.vector.riotx.core.utils.colorizeMatchingText +import im.vector.riotx.core.utils.styleMatchingText +import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.themes.ThemeUtils +import kotlinx.android.synthetic.main.fragment_verification_request.* +import javax.inject.Inject + +class VerificationRequestFragment @Inject constructor( + val verificationRequestViewModelFactory: VerificationRequestViewModel.Factory, + val avatarRenderer: AvatarRenderer +) : VectorBaseFragment() { + + private val viewModel by fragmentViewModel(VerificationRequestViewModel::class) + + private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) + + override fun getLayoutResId() = R.layout.fragment_verification_request + + override fun invalidate() = withState(viewModel) { state -> + state.matrixItem.let { + val styledText = getString(R.string.verification_request_alert_description, it.id) + .toSpannable() + .styleMatchingText(it.id, Typeface.BOLD) + .colorizeMatchingText(it.id, ThemeUtils.getColor(requireContext(), R.attr.vctr_notice_text_color)) + verificationRequestText.text = styledText + } + + when (state.started) { + is Loading -> { + //Hide the start button, show waiting + verificationStartButton.isInvisible = true + verificationWaitingText.isVisible = true + val otherUser = state.matrixItem.displayName ?: state.matrixItem.id + verificationWaitingText.text = getString(R.string.verification_request_waiting_for, otherUser) + .toSpannable() + .styleMatchingText(otherUser, Typeface.BOLD) + .colorizeMatchingText(otherUser, ThemeUtils.getColor(requireContext(), R.attr.vctr_notice_text_color)) + } + else -> { + verificationStartButton.isEnabled = true + verificationStartButton.isVisible = true + verificationWaitingText.isInvisible = true + } + } + + Unit + } + + @OnClick(R.id.verificationStartButton) + fun onClickOnVerificationStart() = withState(viewModel) { state -> + verificationStartButton.isEnabled = false + sharedViewModel.handle(VerificationAction.RequestVerificationByDM(state.matrixItem.id, state.roomId)) + } + +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt new file mode 100644 index 0000000000..752d6a6a8a --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt @@ -0,0 +1,109 @@ +/* + * 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.riotx.features.crypto.verification + +import com.airbnb.mvrx.* +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction +import im.vector.matrix.android.api.util.MatrixItem +import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest +import im.vector.riotx.core.di.HasScreenInjector +import im.vector.riotx.core.platform.VectorViewModel + + +data class VerificationRequestViewState( + val roomId: String? = null, + val matrixItem: MatrixItem, + val started: Async = Success(false) +) : MvRxState + + +class VerificationRequestViewModel @AssistedInject constructor( + @Assisted initialState: VerificationRequestViewState, + private val session: Session +) : VectorViewModel(initialState), SasVerificationService.SasVerificationListener { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: VerificationRequestViewState): VerificationRequestViewModel + } + + init { + withState { + val pr = session.getSasVerificationService() + .getExistingVerificationRequest(it.matrixItem.id) + ?.firstOrNull() + setState { + copy( + started = Success(false).takeIf { pr == null } + ?: Success(true).takeIf { pr?.isReady == true } + ?: Loading() + ) + } + } + session.getSasVerificationService().addListener(this) + } + + override fun onCleared() { + session.getSasVerificationService().removeListener(this) + super.onCleared() + } + + companion object : MvRxViewModelFactory { + override fun create(viewModelContext: ViewModelContext, state: VerificationRequestViewState): VerificationRequestViewModel? { + val fragment: VerificationRequestFragment = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.verificationRequestViewModelFactory.create(state) + } + + override fun initialState(viewModelContext: ViewModelContext): VerificationRequestViewState? { + val otherUserId = viewModelContext.args().otherUserId + val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() + + return session.getUser(otherUserId)?.let { + VerificationRequestViewState(matrixItem = it.toMatrixItem()) + } + } + } + + override fun handle(action: VerificationAction) { + } + + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) {} + + override fun verificationRequestCreated(pr: PendingVerificationRequest) { + verificationRequestUpdated(pr) + } + + override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> + if (pr.otherUserId == state.matrixItem.id) { + if (pr.isReady) { + setState { + copy(started = Success(true)) + } + } else { + setState { + copy(started = Loading()) + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index e983542ad2..3768820206 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -923,7 +923,7 @@ class RoomDetailFragment @Inject constructor( } is Success -> { when (val data = result.invoke()) { - is RoomDetailAction.ReportContent -> { + is RoomDetailAction.ReportContent -> { when { data.spam -> { AlertDialog.Builder(requireActivity()) @@ -960,6 +960,22 @@ class RoomDetailFragment @Inject constructor( } } } + is RoomDetailAction.RequestVerification -> { + VerificationBottomSheet().apply { + arguments = Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs(data.userId, roomId = roomDetailArgs.roomId)) + } +// setArguments() + }.show(parentFragmentManager, "REQ") + } + is RoomDetailAction.AcceptVerificationRequest -> { + VerificationBottomSheet().apply { + arguments = Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs( + data.otherUserId, data.transactionId, roomId = roomDetailArgs.roomId)) + } + }.show(parentFragmentManager, "REQ") + } } } } @@ -1114,7 +1130,8 @@ class RoomDetailFragment @Inject constructor( } override fun onAvatarClicked(informationData: MessageInformationData) { - vectorBaseActivity.notImplemented("Click on user avatar") + //vectorBaseActivity.notImplemented("Click on user avatar") + roomDetailViewModel.handle(RoomDetailAction.RequestVerification(informationData.senderId)) } override fun onMemberNameClicked(informationData: MessageInformationData) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index b0c0144d66..0212ee75d2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -49,7 +49,6 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap import im.vector.riotx.R @@ -184,8 +183,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages() is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() - is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) - is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) + is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) + is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) + is RoomDetailAction.RequestVerification -> handleRequestVerification(action) } } @@ -796,20 +796,27 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) { - session.getSasVerificationService().beginKeyVerificationInDMs( - KeyVerificationStart.VERIF_METHOD_SAS, - action.transactionId, - room.roomId, - action.otherUserId, - action.otherdDeviceId, - null - ) + session.getSasVerificationService().readyPendingVerificationInDMs(action.otherUserId,room.roomId, + action.transactionId) + _requestLiveData.postValue(LiveEvent(Success(action))) +// session.getSasVerificationService().beginKeyVerificationInDMs( +// KeyVerificationStart.VERIF_METHOD_SAS, +// action.transactionId, +// room.roomId, +// action.otherUserMxItem, +// action.otherdDeviceId, +// null +// ) } private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) { Timber.e("TODO implement $action") } + private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) { + _requestLiveData.postValue(LiveEvent(Success(action))) + } + private fun observeSyncState() { session.rx() .liveSyncState() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index dadf267dd7..36856eebe4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -151,18 +151,18 @@ class MessageItemFactory @Inject constructor( return VerificationRequestItem_() .attributes( VerificationRequestItem.Attributes( - otherUserId, - otherUserName.toString(), - messageContent.fromDevice, - informationData.eventId, - informationData, - attributes.avatarRenderer, - attributes.colorProvider, - attributes.itemLongClickListener, - attributes.itemClickListener, - attributes.reactionPillCallback, - attributes.readReceiptsCallback, - attributes.emojiTypeFace + otherUserId = otherUserId, + otherUserName = otherUserName.toString(), + fromDevide = messageContent.fromDevice, + referenceId = informationData.eventId, + informationData = informationData, + avatarRenderer = attributes.avatarRenderer, + colorProvider = attributes.colorProvider, + itemLongClickListener = attributes.itemLongClickListener, + itemClickListener = attributes.itemClickListener, + reactionPillCallback = attributes.reactionPillCallback, + readReceiptsCallback = attributes.readReceiptsCallback, + emojiTypeFace = attributes.emojiTypeFace ) ) .callback(callback) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 13679cecaf..3ff4af27c2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -70,6 +70,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_MAC -> { // These events are filtered from timeline in normal case // Only visible in developer mode diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 39afebf5af..c8058d0fa8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -52,6 +52,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active EventType.KEY_VERIFICATION_MAC, EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_READY, EventType.REDACTION -> formatDebug(timelineEvent.root) else -> { Timber.v("Type $type not handled by this formatter") diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 71272fe815..2864fe6802 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -50,7 +50,8 @@ object TimelineDisplayableEvents { EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_MAC, - EventType.KEY_VERIFICATION_KEY + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_READY ) } diff --git a/vector/src/main/res/layout/fragment_bottom_sas_verification_code.xml b/vector/src/main/res/layout/fragment_bottom_sas_verification_code.xml new file mode 100644 index 0000000000..bf51aab3df --- /dev/null +++ b/vector/src/main/res/layout/fragment_bottom_sas_verification_code.xml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_verification_choose_method.xml b/vector/src/main/res/layout/fragment_verification_choose_method.xml new file mode 100644 index 0000000000..37b3c6e53a --- /dev/null +++ b/vector/src/main/res/layout/fragment_verification_choose_method.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_verification_conclusion.xml b/vector/src/main/res/layout/fragment_verification_conclusion.xml new file mode 100644 index 0000000000..099297b936 --- /dev/null +++ b/vector/src/main/res/layout/fragment_verification_conclusion.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 07a2f40bbd..213600b2b4 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1500,7 +1500,7 @@ Why choose Riot.im? Verified! You\'ve successfully verified this device. - Secure messages with this user are end-to-end encrypted and not able to be read by third parties. + Messages with this user in this room are end-to-end encrypted and can‘t be read by third parties. Got it Nothing appearing? Not all clients supports interactive verification yet. Use legacy verification. diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 1c7b9756c0..e9db6a4f39 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -5,15 +5,25 @@ Request to verify the given userID Prepends ¯\\_(ツ)_/¯ to a plain-text message + Initial Sync… - File - Audio - Image. - Video. - - Untrusted sign in + They match + They don‘t match + Verify this user by confirming the following unique emoji appear on their screen, in the same order." + For ultimate security, use another trusted means of communication or do this in person. + Look for the green shield to ensure a user is trusted. Trust all users in a room to ensure the room is secure. + + Not secure + One of the following may be compromised:\n\n - Your homeserver\n - The homeserver the user you’re verifying is connected to\n - Yours, or the other users’ internet connection\n - Yours, or the other users’ device + + + Video. + Image. + Audio + File + Waiting… %s cancelled You cancelled From 3eed9b508351de037d263261a49e47d46d803f1b Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 30 Dec 2019 18:42:32 +0100 Subject: [PATCH 02/12] cleaning --- .../tasks/RoomVerificationUpdateTask.kt | 6 +----- .../DefaultSasVerificationService.kt | 14 ++++++------- .../VerificationMessageLiveObserver.kt | 1 - .../im/vector/riotx/core/di/FragmentModule.kt | 3 --- .../SASVerificationCodeFragment.kt | 21 +++++++------------ .../SASVerificationCodeViewModel.kt | 4 +--- .../verification/SasVerificationViewModel.kt | 1 - .../verification/VerificationBottomSheet.kt | 7 ------- .../VerificationBottomSheetViewModel.kt | 4 ---- .../VerificationChooseMethodFragment.kt | 2 -- .../VerificationChooseMethodViewModel.kt | 5 ----- .../VerificationConclusionFragment.kt | 1 - .../VerificationRequestFragment.kt | 3 +-- .../VerificationRequestViewModel.kt | 2 -- .../home/room/detail/RoomDetailFragment.kt | 2 +- .../home/room/detail/RoomDetailViewModel.kt | 2 +- 16 files changed, 19 insertions(+), 59 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt index 0cbefd5f3b..61eff8cf61 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt @@ -25,10 +25,8 @@ import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.di.DeviceId -import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.task.Task -import io.realm.RealmConfiguration import timber.log.Timber import java.util.* import javax.inject.Inject @@ -51,8 +49,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( private val transactionsHandledByOtherDevice = ArrayList() } - override suspend fun execute(params: RoomVerificationUpdateTask.Params): Unit { - + override suspend fun execute(params: RoomVerificationUpdateTask.Params) { // TODO ignore initial sync or back pagination? val now = System.currentTimeMillis() @@ -160,5 +157,4 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( } } } - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 45acc80e40..050dcea9b5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -196,13 +196,13 @@ internal class DefaultSasVerificationService @Inject constructor( val senderId = event.senderId ?: return if (requestInfo.toUserId != credentials.userId) { - //I should ignore this, it's not for me + // I should ignore this, it's not for me Timber.w("## SAS Verification ignoring request from ${event.senderId}, not sent to me") return } - if(checkKeysAreDownloaded(senderId, requestInfo.fromDevice) == null) { - //I should ignore this, it's not for me + if (checkKeysAreDownloaded(senderId, requestInfo.fromDevice) == null) { + // I should ignore this, it's not for me Timber.e("## SAS Verification device ${requestInfo.fromDevice} is not knwon") // TODO cancel? return @@ -216,7 +216,7 @@ internal class DefaultSasVerificationService @Inject constructor( val pendingVerificationRequest = PendingVerificationRequest( isIncoming = true, - otherUserId = senderId,//requestInfo.toUserId, + otherUserId = senderId, // requestInfo.toUserId, transactionId = event.eventId, requestInfo = requestInfo ) @@ -512,13 +512,12 @@ internal class DefaultSasVerificationService @Inject constructor( // TODO should we cancel? return } - if(checkKeysAreDownloaded(event.senderId, readyReq.fromDevice ?: "") == null) { + if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice ?: "") == null) { Timber.e("## SAS Verification device ${readyReq.fromDevice} is not knwown") // TODO cancel? return } - handleReadyReceived(event.senderId, readyReq) } @@ -616,8 +615,7 @@ internal class DefaultSasVerificationService @Inject constructor( override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) : 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] ?: ArrayList().also { pendingRequests[userId] = it diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt index 1b25e32d7c..1c9849a29c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt @@ -68,6 +68,5 @@ internal class VerificationMessageLiveObserver @Inject constructor( roomVerificationUpdateTask.configureWith( RoomVerificationUpdateTask.Params(events, sasVerificationService, cryptoService) ).executeBy(taskExecutor) - } } diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 5fde9c1444..e563989368 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -273,7 +273,6 @@ interface FragmentModule { @FragmentKey(SoftLogoutFragment::class) fun bindSoftLogoutFragment(fragment: SoftLogoutFragment): Fragment - @Binds @IntoMap @FragmentKey(VerificationRequestFragment::class) @@ -284,13 +283,11 @@ interface FragmentModule { @FragmentKey(VerificationChooseMethodFragment::class) fun bindVerificationMethodChooserFragment(fragment: VerificationChooseMethodFragment): Fragment - @Binds @IntoMap @FragmentKey(SASVerificationCodeFragment::class) fun bindVerificationSasCodeFragment(fragment: SASVerificationCodeFragment): Fragment - @Binds @IntoMap @FragmentKey(VerificationConclusionFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt index ebd7f351a4..0e145c0553 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt @@ -37,7 +37,6 @@ class SASVerificationCodeFragment @Inject constructor( @BindView(R.id.sas_emoji_grid) lateinit var emojiGrid: ViewGroup - @BindView(R.id.sas_decimal_code) lateinit var decimalTextView: TextView @@ -56,7 +55,6 @@ class SASVerificationCodeFragment @Inject constructor( @BindView(R.id.emoji6) lateinit var emoji6View: ViewGroup - private val viewModel by fragmentViewModel(SASVerificationCodeViewModel::class) private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) @@ -65,7 +63,7 @@ class SASVerificationCodeFragment @Inject constructor( if (state.supportsEmoji) { decimalTextView.isVisible = false - when(val emojiDescription = state.emojiDescription) { + when (val emojiDescription = state.emojiDescription) { is Success -> { sasLoadingProgress.isVisible = false emojiGrid.isVisible = true @@ -104,20 +102,19 @@ class SASVerificationCodeFragment @Inject constructor( } if (state.isWaitingFromOther) { - //hide buttons + // hide buttons ButtonsVisibilityGroup.isInvisible = true sasCodeWaitingPartnerText.isVisible = true } else { ButtonsVisibilityGroup.isVisible = true sasCodeWaitingPartnerText.isVisible = false } - } is Fail -> { sasLoadingProgress.isVisible = false emojiGrid.isInvisible = true ButtonsVisibilityGroup.isInvisible = true - //TODO? + // TODO? } else -> { sasLoadingProgress.isVisible = true @@ -126,15 +123,15 @@ class SASVerificationCodeFragment @Inject constructor( } } } else { - //Decimal + // Decimal emojiGrid.isInvisible = true decimalTextView.isVisible = true val decimalCode = state.decimalDescription.invoke() decimalTextView.text = decimalCode - //TODO + // TODO if (state.isWaitingFromOther) { - //hide buttons + // hide buttons ButtonsVisibilityGroup.isInvisible = true sasCodeWaitingPartnerText.isVisible = true } else { @@ -144,10 +141,9 @@ class SASVerificationCodeFragment @Inject constructor( } } - @OnClick(R.id.sas_request_continue_button) fun onMatchButtonTapped() = withState(viewModel) { state -> - //UX echo + // UX echo ButtonsVisibilityGroup.isInvisible = true sasCodeWaitingPartnerText.isVisible = true sharedViewModel.handle(VerificationAction.SASMatchAction(state.otherUserId, state.transactionId)) @@ -155,10 +151,9 @@ class SASVerificationCodeFragment @Inject constructor( @OnClick(R.id.sas_request_cancel_button) fun onDoNotMatchButtonTapped() = withState(viewModel) { state -> - //UX echo + // UX echo ButtonsVisibilityGroup.isInvisible = true sasCodeWaitingPartnerText.isVisible = true sharedViewModel.handle(VerificationAction.SASDoNotMatchAction(state.otherUserId, state.transactionId)) } - } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt index 5fa9cc6cff..0b39ac373d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt @@ -41,8 +41,7 @@ data class SASVerificationCodeViewState( class SASVerificationCodeViewModel @AssistedInject constructor( @Assisted initialState: SASVerificationCodeViewState, private val session: Session -) : VectorViewModel(initialState) - , SasVerificationService.SasVerificationListener { +) : VectorViewModel(initialState), SasVerificationService.SasVerificationListener { init { withState { state -> @@ -165,6 +164,5 @@ class SASVerificationCodeViewModel @AssistedInject constructor( } override fun handle(action: EmptyAction) { - } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt index 637df8818e..f14a85c516 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt @@ -24,7 +24,6 @@ import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.user.model.User -import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.riotx.core.utils.LiveEvent import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index a92910cc92..73a604e431 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -56,7 +56,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { val roomId: String? = null ) : Parcelable - @Inject lateinit var verificationRequestViewModelFactory: VerificationRequestViewModel.Factory @Inject @@ -99,7 +98,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } override fun invalidate() = withState(viewModel) { - it.otherUserMxItem?.let { matrixItem -> val displayName = matrixItem.displayName ?: "" otherUserNameText.text = getString(R.string.verification_request_alert_title, displayName) @@ -111,7 +109,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { // Did the request result in a SAS transaction? if (it.sasTransactionState != null) { - when (it.sasTransactionState) { SasVerificationTxState.None, SasVerificationTxState.SendingStart, @@ -148,7 +145,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { return@withState } - // Transaction has not yet started if (it.pendingRequest == null || !it.pendingRequest.isReady) { showFragment(VerificationRequestFragment::class, Bundle().apply { @@ -159,7 +155,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id ?: "", it.pendingRequest.transactionId)) }) - } super.invalidate() @@ -173,7 +168,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } // Commit now, to ensure changes occurs before next rendering frame (or bottomsheet want animate) childFragmentManager.commitTransactionNow { - replace(R.id.bottomSheetFragmentContainer, fragmentClass.java, bundle, @@ -184,7 +178,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } } - fun View.getParentCoordinatorLayout(): CoordinatorLayout? { var current = this as? View while (current != null) { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index a9265293fa..21ae4a776d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -34,7 +34,6 @@ import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.core.utils.LiveEvent - data class VerificationBottomSheetViewState( val otherUserMxItem: MatrixItem? = null, val roomId: String? = null, @@ -43,7 +42,6 @@ data class VerificationBottomSheetViewState( val cancelCode: CancelCode? = null ) : MvRxState - sealed class VerificationAction : VectorViewModelAction { data class RequestVerificationByDM(val userID: String, val roomId: String?) : VerificationAction() data class StartSASVerification(val userID: String, val pendingRequestTransactionId: String) : VerificationAction() @@ -57,7 +55,6 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini : VectorViewModel(initialState), SasVerificationService.SasVerificationListener { - // Can be used for several actions, for a one shot result private val _requestLiveData = MutableLiveData>>() val requestLiveData: LiveData>> @@ -146,7 +143,6 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini } } - override fun transactionCreated(tx: SasVerificationTransaction) { transactionUpdated(tx) } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt index 69c599d335..bea49089eb 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt @@ -43,7 +43,6 @@ class VerificationChooseMethodFragment @Inject constructor( if (state.QRModeAvailable) { val cSpan = object : ClickableSpan() { override fun onClick(widget: View) { - } } val openLink = getString(R.string.verify_open_camera_link) @@ -65,5 +64,4 @@ class VerificationChooseMethodFragment @Inject constructor( sharedViewModel.handle(VerificationAction.StartSASVerification(it.otherUserMxItem?.id ?: "", it.pendingRequest?.transactionId ?: "")) } - } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt index 38482e9b09..96a1cafdb1 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt @@ -33,13 +33,11 @@ data class VerificationChooseMethodViewState( val SASMOdeAvailable: Boolean = false ) : MvRxState - class VerificationChooseMethodViewModel @AssistedInject constructor( @Assisted initialState: VerificationChooseMethodViewState, private val session: Session ) : VectorViewModel(initialState) { - init { withState { state -> val pvr = session.getSasVerificationService().getExistingVerificationRequest(state.otherUserId)?.first { @@ -70,8 +68,5 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( } } - override fun handle(action: EmptyAction) {} - - } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt index da3b0dd187..0f0eaf8b10 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt @@ -50,7 +50,6 @@ class VerificationConclusionFragment @Inject constructor() : VectorBaseFragment( verifyConclusionDescription.setTextOrHide(getString(R.string.sas_verified_successful_description)) verifyConclusionBottomDescription.text = getString(R.string.verification_green_shield) verifyConclusionImageView.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_shield_trusted)) - } ConclusionState.WARNING -> { verificationConclusionTitle.text = getString(R.string.verification_conclusion_not_secure) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt index 60b89c19d6..1d499aa2ed 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt @@ -55,7 +55,7 @@ class VerificationRequestFragment @Inject constructor( when (state.started) { is Loading -> { - //Hide the start button, show waiting + // Hide the start button, show waiting verificationStartButton.isInvisible = true verificationWaitingText.isVisible = true val otherUser = state.matrixItem.displayName ?: state.matrixItem.id @@ -79,5 +79,4 @@ class VerificationRequestFragment @Inject constructor( verificationStartButton.isEnabled = false sharedViewModel.handle(VerificationAction.RequestVerificationByDM(state.matrixItem.id, state.roomId)) } - } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt index 752d6a6a8a..611d7a9b2a 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt @@ -27,14 +27,12 @@ import im.vector.matrix.android.internal.crypto.verification.PendingVerification import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.platform.VectorViewModel - data class VerificationRequestViewState( val roomId: String? = null, val matrixItem: MatrixItem, val started: Async = Success(false) ) : MvRxState - class VerificationRequestViewModel @AssistedInject constructor( @Assisted initialState: VerificationRequestViewState, private val session: Session diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 3768820206..e689f46b0a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -1130,7 +1130,7 @@ class RoomDetailFragment @Inject constructor( } override fun onAvatarClicked(informationData: MessageInformationData) { - //vectorBaseActivity.notImplemented("Click on user avatar") + // vectorBaseActivity.notImplemented("Click on user avatar") roomDetailViewModel.handle(RoomDetailAction.RequestVerification(informationData.senderId)) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 0212ee75d2..3889402ec2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -796,7 +796,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) { - session.getSasVerificationService().readyPendingVerificationInDMs(action.otherUserId,room.roomId, + session.getSasVerificationService().readyPendingVerificationInDMs(action.otherUserId, room.roomId, action.transactionId) _requestLiveData.postValue(LiveEvent(Success(action))) // session.getSasVerificationService().beginKeyVerificationInDMs( From 3c4506cb584a74c344f15c90283584bf2c1707b2 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 30 Dec 2019 19:52:48 +0100 Subject: [PATCH 03/12] merge madness ?? --- .../crypto/sas/SasVerificationService.kt | 10 +- .../crypto/sas/SasVerificationTransaction.kt | 3 + .../api/session/events/model/EventType.kt | 1 + .../android/api/session/room/RoomService.kt | 2 + .../MessageVerificationReadyContent.kt | 57 +++++++++++ .../crypto/model/rest/KeyVerificationReady.kt | 38 ++++++++ .../crypto/model/rest/KeyVerificationStart.kt | 1 + ...faultIncomingSASVerificationTransaction.kt | 7 +- .../DefaultSasVerificationService.kt | 95 ++++++++++++++++++- .../SASVerificationTransaction.kt | 5 + .../crypto/verification/SasTransport.kt | 3 + .../verification/SasTransportRoomMessage.kt | 11 +++ .../verification/SasTransportToDevice.kt | 8 ++ .../crypto/verification/VerificationInfo.kt | 2 +- .../verification/VerificationInfoReady.kt | 42 ++++++++ .../VerificationMessageLiveObserver.kt | 1 + .../session/room/DefaultRoomService.kt | 14 +++ .../src/main/res/values/strings_RiotX.xml | 17 ++++ .../im/vector/riotx/core/di/FragmentModule.kt | 5 +- .../vector/riotx/core/di/ScreenComponent.kt | 3 + .../SASVerificationCodeFragment.kt | 1 - .../VerificationChooseMethodFragment.kt | 2 +- .../VerificationConclusionFragment.kt | 6 +- .../VerificationRequestFragment.kt | 2 +- .../home/room/detail/RoomDetailAction.kt | 2 + .../home/room/detail/RoomDetailFragment.kt | 1 + .../res/layout/bottom_sheet_verification.xml | 55 +++++++++++ .../layout/fragment_verification_request.xml | 73 ++++++++++++++ 28 files changed, 453 insertions(+), 14 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationReadyContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoReady.kt create mode 100644 vector/src/main/res/layout/bottom_sheet_verification.xml create mode 100644 vector/src/main/res/layout/fragment_verification_request.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt index cc3b57da20..418b7ac508 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.crypto.sas import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest /** * https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework @@ -54,7 +55,7 @@ interface SasVerificationService { */ fun beginKeyVerification(method: String, userId: String, deviceID: String): String? - fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) + fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) : PendingVerificationRequest fun beginKeyVerificationInDMs(method: String, transactionId: String, @@ -63,11 +64,16 @@ interface SasVerificationService { otherDeviceId: String, callback: MatrixCallback?): String? + fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String) + // fun transactionUpdated(tx: SasVerificationTransaction) interface SasVerificationListener { fun transactionCreated(tx: SasVerificationTransaction) fun transactionUpdated(tx: SasVerificationTransaction) - fun markedAsManuallyVerified(userId: String, deviceId: String) + fun markedAsManuallyVerified(userId: String, deviceId: String) {} + + fun verificationRequestCreated(pr: PendingVerificationRequest) {} + fun verificationRequestUpdated(pr: PendingVerificationRequest) {} } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt index 9610daf294..b98c5c0167 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt @@ -47,4 +47,7 @@ interface SasVerificationTransaction { * both short codes do match */ fun userHasVerifiedShortCode() + + + fun shortCodeDoNotMatch() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt index 60d333ec96..1939b1f0e0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt @@ -73,6 +73,7 @@ object EventType { const val KEY_VERIFICATION_MAC = "m.key.verification.mac" const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel" const val KEY_VERIFICATION_DONE = "m.key.verification.done" + const val KEY_VERIFICATION_READY = "m.key.verification.ready" // Relation Events const val REACTION = "m.reaction" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index ba3b5ded78..fe110b7b9c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -89,4 +89,6 @@ interface RoomService { fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback>): Cancelable + + fun getExistingDirectRoomWithUser(otherUserId: String) : Room? } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationReadyContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationReadyContent.kt new file mode 100644 index 0000000000..af02118d65 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationReadyContent.kt @@ -0,0 +1,57 @@ +/* + * 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.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +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.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.verification.MessageVerificationReadyFactory +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoReady + +@JsonClass(generateAdapter = true) +internal data class MessageVerificationReadyContent( + @Json(name = "from_device") override val fromDevice: String? = null, + @Json(name = "methods") override val methods: List? = null, + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? +) : VerificationInfoReady { + + override val transactionID: String? + get() = relatesTo?.eventId + + override fun toEventContent() = this.toContent() + + override fun isValid(): Boolean { + if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) { + return false + } + return true + } + + companion object : MessageVerificationReadyFactory { + override fun create(tid: String, methods: List, fromDevice: String): VerificationInfoReady { + return MessageVerificationReadyContent( + fromDevice = fromDevice, + methods = methods, + relatesTo = RelationDefaultContent( + RelationType.REFERENCE, + tid + ) + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt new file mode 100644 index 0000000000..7df12b22c6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt @@ -0,0 +1,38 @@ +/* + * 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.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoReady + +/** + * Requests a key verification with another user's devices. + */ +@JsonClass(generateAdapter = true) +internal data class KeyVerificationReady( + @Json(name = "from_device") override val fromDevice: String?, + //TODO add qr? + @Json(name = "methods") override val methods: List? = listOf(KeyVerificationStart.VERIF_METHOD_SAS), + @Json(name = "transaction_id") override var transactionID: String? = null +) : SendToDeviceObject, VerificationInfoReady { + + override fun toSendToDeviceObject() = this + + override fun isValid(): Boolean { + return !transactionID.isNullOrBlank() && !fromDevice.isNullOrBlank() && !methods.isNullOrEmpty() + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt index e8c0334539..e25ed10a6a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt @@ -43,6 +43,7 @@ data class KeyVerificationStart( companion object { const val VERIF_METHOD_SAS = "m.sas.v1" + const val VERIF_METHOD_SCAN = "m.qr_code.scan.v1" } override fun isValid(): Boolean { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt index 5eff26a5bb..349d6a79ca 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt @@ -33,7 +33,8 @@ internal class DefaultIncomingSASVerificationTransaction( private val cryptoStore: IMXCryptoStore, deviceFingerprint: String, transactionId: String, - otherUserID: String + otherUserID: String, + val autoAccept: Boolean = false ) : SASVerificationTransaction( setDeviceVerificationAction, credentials, @@ -76,6 +77,10 @@ internal class DefaultIncomingSASVerificationTransaction( this.startReq = startReq state = SasVerificationTxState.OnStarted this.otherDeviceId = startReq.fromDevice + + if (autoAccept) { + performAccept() + } } override fun performAccept() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 050dcea9b5..64f1b4b308 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -75,6 +75,12 @@ internal class DefaultSasVerificationService @Inject constructor( // map [sender : [transaction]] private val txMap = HashMap>() + /** + * Map [sender: [PendingVerificationRequest]] + */ + private val pendingRequests = HashMap>() + + // Event received from the sync fun onToDeviceEvent(event: Event) { GlobalScope.launch(coroutineDispatchers.crypto) { @@ -120,6 +126,9 @@ internal class DefaultSasVerificationService @Inject constructor( EventType.KEY_VERIFICATION_MAC -> { onRoomMacReceived(event) } + EventType.KEY_VERIFICATION_READY -> { + onRoomReadyReceived(event) + } EventType.KEY_VERIFICATION_DONE -> { // TODO? } @@ -175,6 +184,31 @@ internal class DefaultSasVerificationService @Inject constructor( } } + + private fun dispatchRequestAdded(tx: PendingVerificationRequest) { + uiHandler.post { + listeners.forEach { + try { + it.verificationRequestCreated(tx) + } catch (e: Throwable) { + Timber.e(e, "## Error while notifying listeners") + } + } + } + } + + private fun dispatchRequestUpdated(tx: PendingVerificationRequest) { + uiHandler.post { + listeners.forEach { + try { + it.verificationRequestUpdated(tx) + } catch (e: Throwable) { + Timber.e(e, "## Error while notifying listeners") + } + } + } + } + override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) { setDeviceVerificationAction.handle(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED, deviceID, @@ -326,6 +360,10 @@ internal class DefaultSasVerificationService @Inject constructor( // Ok we can create if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) { Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}") + // If there is a corresponding request, we can auto accept + // as we are the one requesting in first place (or we accepted the request) + val autoAccept = getExistingVerificationRequest(otherUserId)?.any { it.transactionId == startReq.transactionID } + ?: false val tx = DefaultIncomingSASVerificationTransaction( // this, setDeviceVerificationAction, @@ -333,7 +371,8 @@ internal class DefaultSasVerificationService @Inject constructor( cryptoStore, myDeviceInfoHolder.get().myDevice.fingerprint()!!, startReq.transactionID!!, - otherUserId).also { txConfigure(it) } + otherUserId, + autoAccept).also { txConfigure(it) } addTransaction(tx) tx.acceptVerificationEvent(otherUserId, startReq) } else { @@ -546,6 +585,15 @@ internal class DefaultSasVerificationService @Inject constructor( } } + private fun handleReadyReceived(senderId: String, readyReq: VerificationInfoReady) { + val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionID } + if (existingRequest == null) { + Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionID} fromDevice ${readyReq.fromDevice}") + return + } + updateOutgoingPendingRequest(existingRequest.copy(readyInfo = readyReq)) + } + override fun getExistingTransaction(otherUser: String, tid: String): VerificationTransaction? { synchronized(lock = txMap) { return txMap[otherUser]?.get(tid) @@ -647,6 +695,12 @@ internal class DefaultSasVerificationService @Inject constructor( ) { this.callback = object : MatrixCallback { override fun onSuccess(data: SendResponse) { + params.event.getClearContent().toModel()?.let { + updateOutgoingPendingRequest(verificationRequest.copy( + transactionId = data.eventId, + requestInfo = it + )) + } callback?.onSuccess(data.eventId) } @@ -657,6 +711,24 @@ internal class DefaultSasVerificationService @Inject constructor( constraints = TaskConstraints(true) retryCount = 3 }.executeBy(taskExecutor) + + return verificationRequest + } + + private fun updateOutgoingPendingRequest(updated: PendingVerificationRequest) { + val requestsForUser = pendingRequests[updated.otherUserId] + ?: ArrayList().also { + pendingRequests[updated.otherUserId] = it + } + val index = requestsForUser.indexOfFirst { + it.transactionId == updated.transactionId + || it.transactionId == null && it.localID == updated.localID + } + if (index != -1) { + requestsForUser.removeAt(index) + } + requestsForUser.add(updated) + dispatchRequestUpdated(updated) } override fun beginKeyVerificationInDMs(method: String, transactionId: String, roomId: String, @@ -681,6 +753,27 @@ internal class DefaultSasVerificationService @Inject constructor( } } + override fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String) { + // 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 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") + return@let + } + //TODO this is not yet related to a transaction, maybe we should use another method like for cancel? + val readyMsg = transport.createReady(transactionId, credentials.deviceId ?: "", methods) + transport.sendToOther(EventType.KEY_VERIFICATION_READY, readyMsg, + SasVerificationTxState.None, + CancelCode.User, + null // TODO handle error? + ) + updateOutgoingPendingRequest(it.copy(readyInfo = readyMsg)) + } + } + /** * This string must be unique for the pair of users performing verification for the duration that the transaction is valid */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt index c0e3d292c8..9df9248993 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt @@ -169,6 +169,11 @@ internal abstract class SASVerificationTransaction( } // if not wait for it } + override fun shortCodeDoNotMatch() { + Timber.v("## SAS short code do not match for id:$transactionId") + cancel(CancelCode.MismatchedSas) + } + override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) { when (info) { is VerificationInfoStart -> onVerificationStart(info) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt index ae5f55b662..1befc74525 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt @@ -58,4 +58,7 @@ internal interface SasTransport { shortAuthenticationStrings: List) : VerificationInfoStart fun createMac(tid: String, mac: Map, keys: String): VerificationInfoMac + + + fun createReady(tid: String, fromDevice: String, methods: List): VerificationInfoReady } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index 91adbbd705..fa4c370a90 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -167,6 +167,17 @@ internal class SasTransportRoomMessage( ) ) } + + override fun createReady(tid: String, fromDevice: String, methods: List): VerificationInfoReady { + return MessageVerificationReadyContent( + fromDevice = fromDevice, + relatesTo = RelationDefaultContent( + type = RelationType.REFERENCE, + eventId = tid + ), + methods = methods + ) + } } internal class SasTransportRoomMessageFactory @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt index 85e9099972..7a69f212a3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -126,6 +126,14 @@ internal class SasTransportToDevice( messageAuthenticationCodes, shortAuthenticationStrings) } + + override fun createReady(tid: String, fromDevice: String, methods: List): VerificationInfoReady { + return KeyVerificationReady( + transactionID = tid, + fromDevice = fromDevice, + methods = methods + ) + } } internal class SasTransportToDeviceFactory @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt index 44a65aa926..5fe5c62edd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt @@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.crypto.verification import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject -internal interface VerificationInfo { +interface VerificationInfo { fun toEventContent(): Content? = null fun toSendToDeviceObject(): SendToDeviceObject? = null fun isValid() : Boolean diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoReady.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoReady.kt new file mode 100644 index 0000000000..87436f5686 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoReady.kt @@ -0,0 +1,42 @@ +/* + * 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.verification + +/** + * A new event type is added to the key verification framework: m.key.verification.ready, + * which may be sent by the target of the m.key.verification.request message, upon receipt of the m.key.verification.request event. + * + * The m.key.verification.ready event is optional; the recipient of the m.key.verification.request event may respond directly + * with a m.key.verification.start event instead. + */ +interface VerificationInfoReady : VerificationInfo { + + val transactionID: String? + + /** + * The ID of the device that sent the m.key.verification.ready message + */ + val fromDevice: String? + + /** + * An array of verification methods that the device supports + */ + val methods: List? +} + +internal interface MessageVerificationReadyFactory { + fun create(tid: String, methods: List, fromDevice: String): VerificationInfoReady +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt index 1c9849a29c..ede5c42ad6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt @@ -49,6 +49,7 @@ internal class VerificationMessageLiveObserver @Inject constructor( EventType.KEY_VERIFICATION_MAC, EventType.KEY_VERIFICATION_CANCEL, EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_READY, EventType.MESSAGE, EventType.ENCRYPTED) ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index b53fa3ce33..dae9ba3bfd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -71,6 +71,20 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona } } + override fun getExistingDirectRoomWithUser(otherUserId: String): Room? { + Realm.getInstance(monarchy.realmConfiguration).use { realm -> + val roomId = RoomSummaryEntity.where(realm) + .equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + .findAll()?.let { dms -> + dms.firstOrNull { + it.otherMemberIds.contains(otherUserId) + } + } + ?.roomId ?: return null + return RoomEntity.where(realm, roomId).findFirst()?.let { roomFactory.create(roomId) } + } + } + override fun getRoomSummary(roomIdOrAlias: String): RoomSummary? { return monarchy .fetchCopyMap({ diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index 2edfd0ba03..7964b1d1b8 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -21,4 +21,21 @@ %s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys. + + You + + Verify by scanning + + Ask the other user to scan this code, or %s to scan theirs + + open your camera + + Verify by Emoji + If you can’t scan the code above, verify by comparing a short, unique selection of emoji. + + QR code image + + Verify %s + Waiting for %s… + For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person. diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index e563989368..956e7d3a65 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -23,10 +23,7 @@ import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment -import im.vector.riotx.features.crypto.verification.SASVerificationIncomingFragment -import im.vector.riotx.features.crypto.verification.SASVerificationShortCodeFragment -import im.vector.riotx.features.crypto.verification.SASVerificationStartFragment -import im.vector.riotx.features.crypto.verification.SASVerificationVerifiedFragment +import im.vector.riotx.features.crypto.verification.* import im.vector.riotx.features.home.HomeDetailFragment import im.vector.riotx.features.home.HomeDrawerFragment import im.vector.riotx.features.home.LoadingFragment diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt index e0b14af9d0..3ac9e7c7e9 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt @@ -25,6 +25,7 @@ import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.preference.UserAvatarPreference import im.vector.riotx.features.MainActivity import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity +import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import im.vector.riotx.features.home.HomeActivity import im.vector.riotx.features.home.HomeModule import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity @@ -133,6 +134,8 @@ interface ScreenComponent { fun inject(activity: SoftLogoutActivity) + fun inject(verificationBottomSheet: VerificationBottomSheet) + fun inject(permalinkHandlerActivity: PermalinkHandlerActivity) @Component.Factory diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt index 0e145c0553..1cb9d2ab94 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt @@ -24,7 +24,6 @@ import butterknife.OnClick import com.airbnb.mvrx.* import im.vector.riotx.R import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.platform.parentFragmentViewModel import kotlinx.android.synthetic.main.fragment_bottom_sas_verification_code.* import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt index bea49089eb..69abc77b6f 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt @@ -21,10 +21,10 @@ import androidx.core.text.toSpannable import androidx.core.view.isVisible import butterknife.OnClick import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.platform.parentFragmentViewModel import im.vector.riotx.core.utils.tappableMatchingText import kotlinx.android.synthetic.main.fragment_verification_choose_method.* import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt index 0f0eaf8b10..57bb80976e 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt @@ -19,11 +19,11 @@ import android.os.Parcelable import androidx.core.content.ContextCompat import butterknife.OnClick import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.platform.parentFragmentViewModel import io.noties.markwon.Markwon import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_verification_conclusion.* @@ -56,7 +56,9 @@ class VerificationConclusionFragment @Inject constructor() : VectorBaseFragment( verifyConclusionDescription.setTextOrHide(null) verifyConclusionImageView.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_shield_warning)) - verifyConclusionBottomDescription.text = Markwon.builder(requireContext()).build().toMarkdown(getString(R.string.verification_conclusion_compromised)) + verifyConclusionBottomDescription.text = Markwon.builder(requireContext()) + .build() + .toMarkdown(getString(R.string.verification_conclusion_compromised)) } ConclusionState.CANCELLED -> { // Just dismiss in this case diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt index 1d499aa2ed..d6e4105fdd 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt @@ -22,10 +22,10 @@ import androidx.core.view.isVisible import butterknife.OnClick import com.airbnb.mvrx.Loading import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.platform.parentFragmentViewModel import im.vector.riotx.core.utils.colorizeMatchingText import im.vector.riotx.core.utils.styleMatchingText import im.vector.riotx.features.home.AvatarRenderer diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt index 5d00b09204..013c908f16 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt @@ -66,4 +66,6 @@ sealed class RoomDetailAction : VectorViewModelAction { data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction() data class DeclineVerificationRequest(val transactionId: String) : RoomDetailAction() + + data class RequestVerification(val userId: String) : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index e689f46b0a..75b24e26b4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -90,6 +90,7 @@ import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotx.features.command.Command +import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.getColorFromUserId import im.vector.riotx.features.home.room.detail.composer.TextComposerAction diff --git a/vector/src/main/res/layout/bottom_sheet_verification.xml b/vector/src/main/res/layout/bottom_sheet_verification.xml new file mode 100644 index 0000000000..7293434f0d --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_verification.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_verification_request.xml b/vector/src/main/res/layout/fragment_verification_request.xml new file mode 100644 index 0000000000..7ae82c6a78 --- /dev/null +++ b/vector/src/main/res/layout/fragment_verification_request.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 935b3d7f3f9126951a25934b88ed57984189cb19 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 30 Dec 2019 20:18:08 +0100 Subject: [PATCH 04/12] cleaning --- .../api/session/crypto/sas/SasVerificationTransaction.kt | 1 - .../internal/crypto/model/rest/KeyVerificationReady.kt | 2 +- .../crypto/verification/DefaultSasVerificationService.kt | 6 ++---- .../android/internal/crypto/verification/SasTransport.kt | 1 - .../features/crypto/verification/VerificationBottomSheet.kt | 2 +- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt index b98c5c0167..af47b54a00 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt @@ -48,6 +48,5 @@ interface SasVerificationTransaction { */ fun userHasVerifiedShortCode() - fun shortCodeDoNotMatch() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt index 7df12b22c6..ca3b1e0075 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt @@ -25,7 +25,7 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRea @JsonClass(generateAdapter = true) internal data class KeyVerificationReady( @Json(name = "from_device") override val fromDevice: String?, - //TODO add qr? + // TODO add qr? @Json(name = "methods") override val methods: List? = listOf(KeyVerificationStart.VERIF_METHOD_SAS), @Json(name = "transaction_id") override var transactionID: String? = null ) : SendToDeviceObject, VerificationInfoReady { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 64f1b4b308..a5ac169729 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -80,7 +80,6 @@ internal class DefaultSasVerificationService @Inject constructor( */ private val pendingRequests = HashMap>() - // Event received from the sync fun onToDeviceEvent(event: Event) { GlobalScope.launch(coroutineDispatchers.crypto) { @@ -184,7 +183,6 @@ internal class DefaultSasVerificationService @Inject constructor( } } - private fun dispatchRequestAdded(tx: PendingVerificationRequest) { uiHandler.post { listeners.forEach { @@ -756,14 +754,14 @@ internal class DefaultSasVerificationService @Inject constructor( override fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String) { // Let's find the related request 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 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") return@let } - //TODO this is not yet related to a transaction, maybe we should use another method like for cancel? + // TODO this is not yet related to a transaction, maybe we should use another method like for cancel? val readyMsg = transport.createReady(transactionId, credentials.deviceId ?: "", methods) transport.sendToOther(EventType.KEY_VERIFICATION_READY, readyMsg, SasVerificationTxState.None, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt index 1befc74525..b4d5a9ee76 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt @@ -59,6 +59,5 @@ internal interface SasTransport { fun createMac(tid: String, mac: Map, keys: String): VerificationInfoMac - fun createReady(tid: String, fromDevice: String, methods: List): VerificationInfoReady } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index 73a604e431..f4c4d6bd6e 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -84,7 +84,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.requestLiveData.observe(this, Observer { + viewModel.requestLiveData.observe(viewLifecycleOwner, Observer { it.peekContent().let { va -> when (va) { is Success -> { From 5b210df7c514e39027f7237a3d7dfa085a1767ec Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 31 Dec 2019 10:35:50 +0100 Subject: [PATCH 05/12] Manage done states + cleaning --- .../crypto/sas/SasVerificationService.kt | 15 +++- .../message/MessageVerificationDoneContent.kt | 10 ++- .../model/rest/KeyVerificationRequest.kt | 2 +- .../tasks/RoomVerificationUpdateTask.kt | 22 ++--- .../DefaultSasVerificationService.kt | 83 +++++++++++++------ .../PendingVerificationRequest.kt | 1 + .../crypto/verification/VerificationInfo.kt | 1 + .../verification/VerificationInfoAccept.kt | 2 +- .../verification/VerificationInfoCancel.kt | 2 +- .../verification/VerificationInfoKey.kt | 2 +- .../verification/VerificationInfoMac.kt | 2 +- .../verification/VerificationInfoReady.kt | 2 +- .../verification/VerificationInfoStart.kt | 2 +- .../verification/SASVerificationActivity.kt | 1 + .../SASVerificationIncomingFragment.kt | 1 + .../SASVerificationShortCodeFragment.kt | 1 + .../SASVerificationStartFragment.kt | 1 + .../SASVerificationVerifiedFragment.kt | 1 + .../verification/SasVerificationViewModel.kt | 1 + .../timeline/factory/EncryptionItemFactory.kt | 1 + .../helper/MessageInformationDataFactory.kt | 1 + .../timeline/item/MessageInformationData.kt | 1 + .../timeline/item/VerificationRequestItem.kt | 14 ++-- 23 files changed, 115 insertions(+), 54 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt index 418b7ac508..83c130571d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt @@ -55,7 +55,7 @@ interface SasVerificationService { */ fun beginKeyVerification(method: String, userId: String, deviceID: String): String? - fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) : PendingVerificationRequest + fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?): 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) + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt index 965fcb79bb..3f4bf1c59f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt @@ -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() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt index 51eed84412..4511db324a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt @@ -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 { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt index 61eff8cf61..ec25c0dd04 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt @@ -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()?.type) { params.sasVerificationService.onRoomRequestReceived(event) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index a5ac169729..7691427d11 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -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>() @@ -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()?.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() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.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()!! @@ -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 { override fun onSuccess(data: SendResponse) { params.event.getClearContent().toModel()?.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().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)) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt index 6447b8668b..74b8c95a80 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt @@ -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, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt index 5fe5c62edd..5a4838831c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt @@ -22,4 +22,5 @@ interface VerificationInfo { fun toEventContent(): Content? = null fun toSendToDeviceObject(): SendToDeviceObject? = null fun isValid() : Boolean + val transactionID: String? } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoAccept.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoAccept.kt index 7f639c3bc7..2774a44d49 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoAccept.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoAccept.kt @@ -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 Bob’s device has selected to use, out of the list proposed by Alice’s device diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoCancel.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoCancel.kt index 2970358a15..b9bd2cebee 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoCancel.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoCancel.kt @@ -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] */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoKey.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoKey.kt index e5deb63b56..99edb53e79 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoKey.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoKey.kt @@ -20,7 +20,7 @@ package im.vector.matrix.android.internal.crypto.verification */ internal interface VerificationInfoKey : VerificationInfo { - val transactionID: String? + override val transactionID: String? /** * The device’s ephemeral public key, as an unpadded base64 string */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoMac.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoMac.kt index 01fc865250..ace1210986 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoMac.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoMac.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoReady.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoReady.kt index 87436f5686..0c5312987e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoReady.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoReady.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt index 2248a239fb..d0cfe06815 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt @@ -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. * Alice’s 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 Alice’s client understands. diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationActivity.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationActivity.kt index cf80bf98fc..222891eb3d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationActivity.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationIncomingFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationIncomingFragment.kt index 61f5c5f9fe..cb96900ba7 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationIncomingFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationIncomingFragment.kt @@ -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() { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationShortCodeFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationShortCodeFragment.kt index ec9a943449..64c1c4b1f0 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationShortCodeFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationShortCodeFragment.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationStartFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationStartFragment.kt index d33167518f..4e9699e853 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationStartFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationStartFragment.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationVerifiedFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationVerifiedFragment.kt index 17beb21aff..fe7f9861b0 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationVerifiedFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationVerifiedFragment.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt index f14a85c516..ca283e449d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt index 29b01120d1..696cc7d904 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt @@ -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, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 049998f093..96a90f82c4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -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, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt index 835a789107..fe5d0d03ca 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -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, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt index db7c4008c0..d00e90760a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt @@ -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 { + null -> { holder.buttonBar.isVisible = !attributes.informationData.sentByMe holder.statusTextView.text = null holder.statusTextView.isVisible = false @@ -82,17 +83,17 @@ abstract class VerificationRequestItem : AbsBaseMessageItem { + 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 { + 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 From f5416610594d23a9aa9cb80a1831da9a98fec5bc Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 2 Jan 2020 11:37:33 +0100 Subject: [PATCH 06/12] Use workers to send verification messages --- matrix-sdk-android/build.gradle | 1 + .../crypto/sas/SasVerificationService.kt | 2 +- .../android/internal/crypto/CryptoModule.kt | 3 + .../crypto/tasks/RequestVerificationDMTask.kt | 95 ------ .../tasks/SendVerificationMessageTask.kt | 41 +-- .../DefaultSasVerificationService.kt | 111 +++---- .../crypto/verification/SasTransport.kt | 3 + .../verification/SasTransportRoomMessage.kt | 286 ++++++++++++------ .../verification/SasTransportToDevice.kt | 5 + .../SendVerificationMessageWorker.kt | 61 ++++ .../internal/session/SessionComponent.kt | 3 + .../VerificationBottomSheetViewModel.kt | 2 +- .../home/room/detail/RoomDetailViewModel.kt | 2 +- 13 files changed, 311 insertions(+), 304 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index ab5f122dbc..c7493ae283 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -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' diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt index 83c130571d..fab9351dea 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt @@ -55,7 +55,7 @@ interface SasVerificationService { */ fun beginKeyVerification(method: String, userId: String, deviceID: String): String? - fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?): PendingVerificationRequest + fun requestKeyVerificationInDMs(userId: String, roomId: String): PendingVerificationRequest fun beginKeyVerificationInDMs(method: String, transactionId: String, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index 4243c6a464..65e47473d0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt deleted file mode 100644 index 00b7d7320a..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt +++ /dev/null @@ -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 { - data class Params( - val event: Event, - val cryptoService: CryptoService - ) - - fun createParamsAndLocalEcho(roomId: String, from: String, methods: List, 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, 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 { - 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 - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt index cf1b2d233a..eacdc977d8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt @@ -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 { +internal interface SendVerificationMessageTask : Task { 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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 7691427d11..8592e21db3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -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, 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,58 +692,38 @@ internal class DefaultSasVerificationService @Inject constructor( } } - override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) + override fun requestKeyVerificationInDMs(userId: String, roomId: String) : PendingVerificationRequest { Timber.i("## SAS Requesting verification to user: $userId in room $roomId") + val requestsForUser = pendingRequests[userId] ?: ArrayList().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 ) + + transport.sendVerificationRequest(localID, userId, roomId) { syncedId, info -> + // We need to update with the syncedID + updatePendingRequest(verificationRequest.copy( + transactionId = syncedId, + requestInfo = info + )) + } + 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 { - override fun onSuccess(data: SendResponse) { - params.event.getClearContent().toModel()?.let { - updatePendingRequest(verificationRequest.copy( - transactionId = data.eventId, - requestInfo = it - )) - } - callback?.onSuccess(data.eventId) - } - - override fun onFailure(failure: Throwable) { - callback?.onFailure(failure) - } - } - constraints = TaskConstraints(true) - retryCount = 3 - }.executeBy(taskExecutor) - 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() -// contentMap.setObject(userId, userDevice, cancelMessage) -// -// if (roomId != null) { -// -// } else { -// sendToDeviceTask -// .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { -// this.callback = object : MatrixCallback { -// 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) -// } -// } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt index b4d5a9ee76..43c1762a89 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt @@ -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) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index fa4c370a90..e3b5afd8e1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -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 - ) - ) { - callbackThread = TaskThread.DM_VERIF - executionThread = TaskThread.DM_VERIF - constraints = TaskConstraints(true) - callback = object : MatrixCallback { - override fun onSuccess(data: SendResponse) { - if (onDone != null) { - onDone() - } else { - tx?.state = nextState - } - } + val event = createEventAndLocalEcho( + type = type, + roomId = roomId, + content = verificationInfo.toEventContent()!! + ) - override fun onFailure(failure: Throwable) { - Timber.e("## SAS verification [${tx?.transactionId}] failed to send toDevice in state : ${tx?.state}") - tx?.cancel(onErrorReason) - } + 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 { +// 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> { + override fun onChanged(workInfoList: List?) { + 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) + } } - retryCount = 3 } - .executeBy(taskExecutor) + + // 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() + .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> { + override fun onChanged(workInfoList: List?) { + 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) + } + } + } + + // 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 - ) - ) { - callbackThread = TaskThread.DM_VERIF - executionThread = TaskThread.DM_VERIF - constraints = TaskConstraints(true) - retryCount = 3 - callback = object : MatrixCallback { - 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 event = createEventAndLocalEcho( + type = EventType.KEY_VERIFICATION_CANCEL, + roomId = roomId, + content = MessageVerificationCancelContent.create(transactionId, code).toContent() + ) + 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( - relatesTo = RelationDefaultContent( - RelationType.REFERENCE, - transactionId - ) - ).toContent(), - cryptoService - ) - ) { - callbackThread = TaskThread.DM_VERIF - executionThread = TaskThread.DM_VERIF - constraints = TaskConstraints(true) - retryCount = 3 - } - .executeBy(taskExecutor) + val event = createEventAndLocalEcho( + type = EventType.KEY_VERIFICATION_DONE, + roomId = roomId, + content = MessageVerificationDoneContent( + relatesTo = RelationDefaultContent( + RelationType.REFERENCE, + transactionId + ) + ).toContent() + ) + val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( + userId = userId, + event = event + )) + enqueueSendWork(workerParams) + } + + private fun enqueueSendWork(workerParams: Data): Pair { + val workRequest = WorkManagerUtil.matrixOneTimeWorkRequestBuilder() + .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, @@ -171,23 +263,35 @@ internal class SasTransportRoomMessage( override fun createReady(tid: String, fromDevice: String, methods: List): VerificationInfoReady { return MessageVerificationReadyContent( fromDevice = fromDevice, - relatesTo = RelationDefaultContent( + relatesTo = RelationDefaultContent( type = RelationType.REFERENCE, eventId = tid ), - 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( - 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) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt index 7a69f212a3..8e945e6cab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -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, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt new file mode 100644 index 0000000000..656641706a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt @@ -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(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) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt index a208a7a720..6ad46e594a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt @@ -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( diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index 21ae4a776d..9b98ffa3b8 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -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 -> { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 3889402ec2..689e4ba269 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -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() } From b26318f15c6f87e6b2dddbe8b7edd982dd58529d Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 2 Jan 2020 12:51:12 +0100 Subject: [PATCH 07/12] Fix / Cancel messages was not sent --- .../internal/crypto/verification/SasTransport.kt | 2 +- .../crypto/verification/SasTransportRoomMessage.kt | 4 ++-- .../crypto/verification/SasTransportToDevice.kt | 4 ++-- .../verification/SendVerificationMessageWorker.kt | 11 ++++++++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt index 43c1762a89..60e0204800 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt @@ -36,7 +36,7 @@ internal interface SasTransport { 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, otherUserId: String, otherUserDevice: String, code: CancelCode) fun done(transactionId: String) /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index e3b5afd8e1..9beee96e93 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -177,7 +177,7 @@ internal class SasTransportRoomMessage( } } - override fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) { + override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDevice: String, code: CancelCode) { Timber.d("## SAS canceling transaction $transactionId for reason $code") val event = createEventAndLocalEcho( type = EventType.KEY_VERIFICATION_CANCEL, @@ -185,7 +185,7 @@ internal class SasTransportRoomMessage( content = MessageVerificationCancelContent.create(transactionId, code).toContent() ) val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( - userId = userId, + userId = this.userId, event = event )) enqueueSendWork(workerParams) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt index 8e945e6cab..20cd603679 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -77,11 +77,11 @@ internal class SasTransportToDevice( // To device do not do anything here } - override fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) { + override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDevice: String, code: CancelCode) { Timber.d("## SAS canceling transaction $transactionId for reason $code") val cancelMessage = KeyVerificationCancel.create(transactionId, code) val contentMap = MXUsersDevicesMap() - contentMap.setObject(userId, userDevice, cancelMessage) + contentMap.setObject(otherUserId, otherUserDevice, cancelMessage) sendToDeviceTask .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { this.callback = object : MatrixCallback { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt index 656641706a..c0eaace7ca 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt @@ -12,6 +12,7 @@ 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 timber.log.Timber import javax.inject.Inject internal class SendVerificationMessageWorker constructor(context: Context, params: WorkerParameters) @@ -30,10 +31,14 @@ internal class SendVerificationMessageWorker constructor(context: Context, param lateinit var cryptoService: CryptoService override suspend fun doWork(): Result { + val errorOutputData = Data.Builder().putBoolean("failed", true).build() val params = WorkerParamsFactory.fromData(inputData) - ?: return Result.success(Data.Builder().putBoolean("failed", true).build()) + ?: return Result.success(errorOutputData) - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.userId) ?: return Result.success(errorOutputData).also { + // TODO, can this happen? should I update local echo? + Timber.e("Unknown Session, cannot send message, userId:${params.userId}") + } sessionComponent.inject(this) val localId = params.event.eventId ?: "" return try { @@ -49,7 +54,7 @@ internal class SendVerificationMessageWorker constructor(context: Context, param if (exception.shouldBeRetried()) { Result.retry() } else { - Result.success(Data.Builder().putBoolean("failed", true).build()) + Result.success(errorOutputData) } } } From 52c25b803f0bb8bd644fd3e4c1dea1214fc846dd Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 2 Jan 2020 15:16:45 +0100 Subject: [PATCH 08/12] cleaning --- .../internal/crypto/verification/SasTransportRoomMessage.kt | 4 ++-- .../internal/crypto/verification/SasTransportToDevice.kt | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index 9beee96e93..38e8a23fdc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -114,10 +114,10 @@ internal class SasTransportRoomMessage( GlobalScope.launch(Dispatchers.Main) { workLiveData.observeForever(observer) } - } - override fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, callback: (String?, MessageVerificationRequestContent?) -> Unit) { + 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, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt index 20cd603679..8d73695f4a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -34,7 +34,8 @@ internal class SasTransportToDevice( private var taskExecutor: TaskExecutor ) : SasTransport { - override fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, callback: (String?, MessageVerificationRequestContent?) -> Unit) { + override fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, + callback: (String?, MessageVerificationRequestContent?) -> Unit) { // TODO "not implemented" } From bf28f14b8b3a75d8d787df5a63b5115e8423d508 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 2 Jan 2020 16:12:20 +0100 Subject: [PATCH 09/12] Fix / Decline request was not implemented --- .../crypto/sas/SasVerificationService.kt | 2 ++ .../DefaultSasVerificationService.kt | 21 +++++++++++++++++++ .../PendingVerificationRequest.kt | 2 ++ .../verification/VerificationBottomSheet.kt | 6 +++++- .../home/room/detail/RoomDetailAction.kt | 2 +- .../home/room/detail/RoomDetailViewModel.kt | 17 ++++++--------- .../timeline/item/VerificationRequestItem.kt | 2 +- 7 files changed, 38 insertions(+), 14 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt index fab9351dea..0e35b58a6c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt @@ -57,6 +57,8 @@ interface SasVerificationService { fun requestKeyVerificationInDMs(userId: String, roomId: String): PendingVerificationRequest + fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) + fun beginKeyVerificationInDMs(method: String, transactionId: String, roomId: String, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 8592e21db3..ee33b96734 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -704,6 +704,16 @@ internal class DefaultSasVerificationService @Inject constructor( val transport = sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId ?: "", roomId, null) + // Cancel existing pending requests? + requestsForUser.forEach { existingRequest -> + existingRequest.transactionId?.let { tid -> + if (!existingRequest.isFinished) { + Timber.d("## SAS, cancelling pending requests to start a new one") + transport.cancelTransaction(tid, existingRequest.otherUserId, "", CancelCode.User) + } + } + } + val localID = LocalEcho.createLocalEchoId() val verificationRequest = PendingVerificationRequest( @@ -727,6 +737,17 @@ internal class DefaultSasVerificationService @Inject constructor( return verificationRequest } + override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) { + sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId + ?: "", roomId, null).cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User) + + getExistingVerificationRequest(otherUserId, transactionId)?.let { + updatePendingRequest(it.copy( + cancelConclusion = CancelCode.User + )) + } + } + private fun updatePendingRequest(updated: PendingVerificationRequest) { val requestsForUser = pendingRequests[updated.otherUserId] ?: ArrayList().also { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt index 74b8c95a80..c416bd3be3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt @@ -37,4 +37,6 @@ data class PendingVerificationRequest( val isReady: Boolean = readyInfo != null val isSent: Boolean = transactionId != null + + val isFinished: Boolean = isSuccessful || cancelConclusion != null } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index f4c4d6bd6e..fe324b430a 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -146,7 +146,11 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } // Transaction has not yet started - if (it.pendingRequest == null || !it.pendingRequest.isReady) { + if (it.pendingRequest?.cancelConclusion != null) { + // The request has been declined, we should dismiss + dismiss() + } else if (it.pendingRequest == null || !it.pendingRequest.isReady) { + // We are waiting for other party to reply with ready showFragment(VerificationRequestFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id ?: "")) }) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt index 013c908f16..768f188d7e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt @@ -65,7 +65,7 @@ sealed class RoomDetailAction : VectorViewModelAction { object ResendAll : RoomDetailAction() data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction() - data class DeclineVerificationRequest(val transactionId: String) : RoomDetailAction() + data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction() data class RequestVerification(val userId: String) : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 689e4ba269..ef1b72a402 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -27,7 +27,6 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixPatterns -import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.isImageMessage @@ -185,7 +184,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) - is RoomDetailAction.RequestVerification -> handleRequestVerification(action) + is RoomDetailAction.RequestVerification -> handleRequestVerification(action) } } @@ -799,18 +798,14 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro session.getSasVerificationService().readyPendingVerificationInDMs(action.otherUserId, room.roomId, action.transactionId) _requestLiveData.postValue(LiveEvent(Success(action))) -// session.getSasVerificationService().beginKeyVerificationInDMs( -// KeyVerificationStart.VERIF_METHOD_SAS, -// action.transactionId, -// room.roomId, -// action.otherUserMxItem, -// action.otherdDeviceId, -// null -// ) } private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) { - Timber.e("TODO implement $action") + session.getSasVerificationService().declineVerificationRequestInDMs( + action.otherUserId, + action.otherdDeviceId, + action.transactionId, + room.roomId) } private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt index d00e90760a..3022c7974a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt @@ -137,7 +137,7 @@ abstract class VerificationRequestItem : AbsBaseMessageItem Date: Thu, 2 Jan 2020 17:04:21 +0100 Subject: [PATCH 10/12] Fix / tap on accept shows request button instead of start --- .../verification/SasTransportRoomMessage.kt | 3 --- .../verification/VerificationBottomSheet.kt | 7 ++++-- .../VerificationBottomSheetViewModel.kt | 7 +++--- .../VerificationRequestViewModel.kt | 25 ++++++++----------- 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index 38e8a23fdc..c75bb02bd4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -34,7 +34,6 @@ 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 @@ -48,8 +47,6 @@ internal class SasTransportRoomMessage( private val tx: SASVerificationTransaction? ) : SasTransport { - private val listenerExecutor = Executors.newSingleThreadExecutor() - override fun sendToOther(type: String, verificationInfo: VerificationInfo, nextState: SasVerificationTxState, diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index fe324b430a..cd0a60667c 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -124,7 +124,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { SasVerificationTxState.ShortCodeAccepted, SasVerificationTxState.SendingMac, SasVerificationTxState.MacSent, - SasVerificationTxState.Verifying -> { + SasVerificationTxState.Verifying -> { showFragment(SASVerificationCodeFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationArgs( it.otherUserMxItem?.id ?: "", @@ -152,7 +152,10 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } else if (it.pendingRequest == null || !it.pendingRequest.isReady) { // We are waiting for other party to reply with ready showFragment(VerificationRequestFragment::class, Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id ?: "")) + putParcelable(MvRx.KEY_ARG, VerificationArgs( + it.otherUserMxItem?.id ?: "", + it.pendingRequest?.transactionId, + it.roomId)) }) } else if (it.pendingRequest.isReady) { showFragment(VerificationChooseMethodFragment::class, Bundle().apply { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index 9b98ffa3b8..43420ea0f8 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -84,13 +84,12 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini val userItem = session.getUser(args.otherUserId) - val sasTx = state.pendingRequest?.transactionId?.let { + val pr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId) + + val sasTx = pr?.transactionId?.let { session.getSasVerificationService().getExistingTransaction(args.otherUserId, it) } - val pr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId) - ?.firstOrNull { it.transactionId == args.verificationId } - return fragment.verificationViewModelFactory.create(VerificationBottomSheetViewState( otherUserMxItem = userItem?.toMatrixItem(), sasTransactionState = sasTx?.state, diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt index 611d7a9b2a..d0b7fe4dd9 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt @@ -44,18 +44,6 @@ class VerificationRequestViewModel @AssistedInject constructor( } init { - withState { - val pr = session.getSasVerificationService() - .getExistingVerificationRequest(it.matrixItem.id) - ?.firstOrNull() - setState { - copy( - started = Success(false).takeIf { pr == null } - ?: Success(true).takeIf { pr?.isReady == true } - ?: Loading() - ) - } - } session.getSasVerificationService().addListener(this) } @@ -71,11 +59,18 @@ class VerificationRequestViewModel @AssistedInject constructor( } override fun initialState(viewModelContext: ViewModelContext): VerificationRequestViewState? { - val otherUserId = viewModelContext.args().otherUserId + val args = viewModelContext.args() val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() - return session.getUser(otherUserId)?.let { - VerificationRequestViewState(matrixItem = it.toMatrixItem()) + val pr = session.getSasVerificationService() + .getExistingVerificationRequest(args.otherUserId, args.verificationId) + return session.getUser(args.otherUserId)?.let { + VerificationRequestViewState( + started = Success(false).takeIf { pr == null } + ?: Success(true).takeIf { pr?.isReady == true } + ?: Loading(), + matrixItem = it.toMatrixItem() + ) } } } From 08ed8d4fa7b44a4546338f5e0f6a605abea2776c Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 3 Jan 2020 17:36:45 +0100 Subject: [PATCH 11/12] Code review --- matrix-sdk-android/build.gradle | 1 - .../matrix/android/api/failure/Extensions.kt | 5 + .../crypto/sas/SasVerificationService.kt | 11 +- .../crypto/sas/SasVerificationTransaction.kt | 2 +- .../MessageVerificationAcceptContent.kt | 2 +- .../MessageVerificationCancelContent.kt | 2 +- .../message/MessageVerificationDoneContent.kt | 2 +- .../message/MessageVerificationKeyContent.kt | 2 +- .../message/MessageVerificationMacContent.kt | 2 +- .../MessageVerificationReadyContent.kt | 2 +- .../MessageVerificationStartContent.kt | 2 +- .../DefaultSasVerificationService.kt | 38 +- .../SASVerificationTransaction.kt | 2 +- .../verification/SasTransportRoomMessage.kt | 3 +- .../SendVerificationMessageWorker.kt | 32 +- .../session/room/send/SendEventWorker.kt | 8 +- .../src/main/res/values/strings_RiotX.xml | 17 - .../vector/riotx/core/utils/SpannableUtils.kt | 2 +- .../SASVerificationCodeFragment.kt | 12 +- .../SASVerificationCodeViewModel.kt | 50 +-- .../verification/VerificationBottomSheet.kt | 25 +- .../VerificationBottomSheetViewModel.kt | 5 +- .../VerificationChooseMethodViewModel.kt | 27 +- .../VerificationConclusionFragment.kt | 3 +- .../VerificationRequestViewModel.kt | 6 +- .../home/room/detail/RoomDetailFragment.kt | 24 +- .../timeline/item/VerificationRequestItem.kt | 14 +- .../fragment_bottom_sas_verification_code.xml | 343 +++++++++--------- .../fragment_verification_conclusion.xml | 18 +- .../layout/fragment_verification_request.xml | 34 +- vector/src/main/res/values/strings_riotX.xml | 20 +- 31 files changed, 356 insertions(+), 360 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index c7493ae283..ab5f122dbc 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -106,7 +106,6 @@ 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' diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Extensions.kt index a3b5ce39eb..5dfb0eab9b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Extensions.kt @@ -26,3 +26,8 @@ fun Throwable.is401() = fun Throwable.isTokenError() = this is Failure.ServerError && (error.code == MatrixError.M_UNKNOWN_TOKEN || error.code == MatrixError.M_MISSING_TOKEN) + +fun Throwable.shouldBeRetried(): Boolean { + return this is Failure.NetworkConnection + || (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED) +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt index 0e35b58a6c..ca6259e7ec 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt @@ -81,14 +81,15 @@ interface SasVerificationService { companion object { + private const val TEN_MINTUTES_IN_MILLIS = 10 * 60 * 1000 + private const val FIVE_MINTUTES_IN_MILLIS = 5 * 60 * 1000 + 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) + val tooInThePast = now - TEN_MINTUTES_IN_MILLIS + val tooInTheFuture = now + FIVE_MINTUTES_IN_MILLIS + return age in tooInThePast..tooInTheFuture } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt index af47b54a00..1f7bef558f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt @@ -48,5 +48,5 @@ interface SasVerificationTransaction { */ fun userHasVerifiedShortCode() - fun shortCodeDoNotMatch() + fun shortCodeDoesNotMatch() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt index 54403ab67f..d4fb3a44fd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt @@ -50,7 +50,7 @@ internal data class MessageVerificationAcceptContent( return true } - override fun toEventContent() = this.toContent() + override fun toEventContent() = toContent() companion object : VerificationInfoAcceptFactory { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt index fe2c958703..651a25e175 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt @@ -34,7 +34,7 @@ data class MessageVerificationCancelContent( override val transactionID: String? get() = relatesTo?.eventId - override fun toEventContent() = this.toContent() + override fun toEventContent() = toContent() override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || code.isNullOrBlank()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt index 3f4bf1c59f..224c5c52d3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt @@ -32,5 +32,5 @@ internal data class MessageVerificationDoneContent( override fun isValid() = transactionID?.isNotEmpty() == true - override fun toEventContent(): Content? = this.toContent() + override fun toEventContent(): Content? = toContent() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt index 2dacb19871..777e0e7318 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt @@ -44,7 +44,7 @@ internal data class MessageVerificationKeyContent( return true } - override fun toEventContent() = this.toContent() + override fun toEventContent() = toContent() companion object : VerificationInfoKeyFactory { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt index f08625791f..05e614dee9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt @@ -33,7 +33,7 @@ internal data class MessageVerificationMacContent( override val transactionID: String? get() = relatesTo?.eventId - override fun toEventContent() = this.toContent() + override fun toEventContent() = toContent() override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationReadyContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationReadyContent.kt index af02118d65..4e237ba1c9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationReadyContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationReadyContent.kt @@ -33,7 +33,7 @@ internal data class MessageVerificationReadyContent( override val transactionID: String? get() = relatesTo?.eventId - override fun toEventContent() = this.toContent() + override fun toEventContent() = toContent() override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt index f928e21a82..a33decc821 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt @@ -62,5 +62,5 @@ internal data class MessageVerificationStartContent( return true } - override fun toEventContent() = this.toContent() + override fun toEventContent() = toContent() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index ee33b96734..c4645f42cd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.crypto.verification -import android.content.Context import android.os.Handler import android.os.Looper import dagger.Lazy @@ -52,7 +51,6 @@ 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, @@ -81,7 +79,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 -> { @@ -90,13 +88,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 } } @@ -106,7 +104,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 -> { @@ -116,24 +114,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 -> { + EventType.KEY_VERIFICATION_DONE -> { onRoomDoneReceived(event) } - EventType.MESSAGE -> { + EventType.MESSAGE -> { if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.type) { onRoomRequestReceived(event) } } - else -> { + else -> { // ignore } } @@ -275,7 +273,7 @@ internal class DefaultSasVerificationService @Inject constructor( if (startReq?.isValid()?.not() == true) { Timber.e("## received invalid verification request") if (startReq.transactionID != null) { - sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId + sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId ?: "", event.roomId ?: "", null).cancelTransaction( startReq.transactionID ?: "", @@ -288,11 +286,11 @@ internal class DefaultSasVerificationService @Inject constructor( } handleStart(otherUserId, startReq as VerificationInfoStart) { - it.transport = sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId + it.transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId ?: "", event.roomId ?: "", it) }?.let { - sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId + sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId ?: "", event.roomId ?: "", null).cancelTransaction( startReq.transactionID ?: "", @@ -701,7 +699,7 @@ internal class DefaultSasVerificationService @Inject constructor( pendingRequests[userId] = it } - val transport = sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId + val transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId ?: "", roomId, null) // Cancel existing pending requests? @@ -738,7 +736,7 @@ internal class DefaultSasVerificationService @Inject constructor( } override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) { - sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId + sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId ?: "", roomId, null).cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User) getExistingVerificationRequest(otherUserId, transactionId)?.let { @@ -776,7 +774,7 @@ internal class DefaultSasVerificationService @Inject constructor( transactionId, otherUserId, otherDeviceId) - tx.transport = sasTransportRoomMessageFactory.createTransport(context, credentials.userId, credentials.deviceId + tx.transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId ?: "", roomId, tx) addTransaction(tx) @@ -791,7 +789,7 @@ 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(context, credentials.userId, credentials.deviceId + val transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId ?: "", roomId, null) val methods = it.requestInfo?.methods?.intersect(listOf(KeyVerificationStart.VERIF_METHOD_SAS))?.toList() if (methods.isNullOrEmpty()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt index 9df9248993..4a12b05b49 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt @@ -169,7 +169,7 @@ internal abstract class SASVerificationTransaction( } // if not wait for it } - override fun shortCodeDoNotMatch() { + override fun shortCodeDoesNotMatch() { Timber.v("## SAS short code do not match for id:$transactionId") cancel(CancelCode.MismatchedSas) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index c75bb02bd4..20c59def18 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -284,10 +284,11 @@ internal class SasTransportRoomMessage( } internal class SasTransportRoomMessageFactory @Inject constructor( + private val context: Context, private val monarchy: Monarchy, private val localEchoEventFactory: LocalEchoEventFactory) { - fun createTransport(context: Context, userId: String, userDevice: String, roomId: String, tx: SASVerificationTransaction? + fun createTransport(userId: String, userDevice: String, roomId: String, tx: SASVerificationTransaction? ): SasTransportRoomMessage { return SasTransportRoomMessage(context, userId, userDevice, roomId, monarchy, localEchoEventFactory, tx) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt index c0eaace7ca..55f57d80d3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2020 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.verification import android.content.Context @@ -5,8 +20,7 @@ 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.failure.shouldBeRetried 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 @@ -35,10 +49,11 @@ internal class SendVerificationMessageWorker constructor(context: Context, param val params = WorkerParamsFactory.fromData(inputData) ?: return Result.success(errorOutputData) - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success(errorOutputData).also { - // TODO, can this happen? should I update local echo? - Timber.e("Unknown Session, cannot send message, userId:${params.userId}") - } + val sessionComponent = getSessionComponent(params.userId) + ?: return Result.success(errorOutputData).also { + // TODO, can this happen? should I update local echo? + Timber.e("Unknown Session, cannot send message, userId:${params.userId}") + } sessionComponent.inject(this) val localId = params.event.eventId ?: "" return try { @@ -58,9 +73,4 @@ internal class SendVerificationMessageWorker constructor(context: Context, param } } } - - private fun Throwable.shouldBeRetried(): Boolean { - return this is Failure.NetworkConnection - || (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED) - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt index 6527113054..026c5e9df5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt @@ -20,8 +20,7 @@ import android.content.Context import androidx.work.CoroutineWorker 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.failure.shouldBeRetried 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.room.send.SendState @@ -77,11 +76,6 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam } } - private fun Throwable.shouldBeRetried(): Boolean { - return this is Failure.NetworkConnection - || (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED) - } - private suspend fun sendEvent(eventId: String, eventType: String, content: Content?, roomId: String) { localEchoUpdater.updateSendState(eventId, SendState.SENDING) executeRequest { diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index 7964b1d1b8..2edfd0ba03 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -21,21 +21,4 @@ %s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys. - - You - - Verify by scanning - - Ask the other user to scan this code, or %s to scan theirs - - open your camera - - Verify by Emoji - If you can’t scan the code above, verify by comparing a short, unique selection of emoji. - - QR code image - - Verify %s - Waiting for %s… - For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person. diff --git a/vector/src/main/java/im/vector/riotx/core/utils/SpannableUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/SpannableUtils.kt index 1b56fc2a57..6536da73f5 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/SpannableUtils.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/SpannableUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018 New Vector Ltd + * 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. diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt index 1cb9d2ab94..122af8b625 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt @@ -109,13 +109,13 @@ class SASVerificationCodeFragment @Inject constructor( sasCodeWaitingPartnerText.isVisible = false } } - is Fail -> { + is Fail -> { sasLoadingProgress.isVisible = false emojiGrid.isInvisible = true ButtonsVisibilityGroup.isInvisible = true // TODO? } - else -> { + else -> { sasLoadingProgress.isVisible = true emojiGrid.isInvisible = true ButtonsVisibilityGroup.isInvisible = true @@ -142,17 +142,21 @@ class SASVerificationCodeFragment @Inject constructor( @OnClick(R.id.sas_request_continue_button) fun onMatchButtonTapped() = withState(viewModel) { state -> + val otherUserId = state.otherUser?.id ?: return@withState + val txId = state.transactionId ?: return@withState // UX echo ButtonsVisibilityGroup.isInvisible = true sasCodeWaitingPartnerText.isVisible = true - sharedViewModel.handle(VerificationAction.SASMatchAction(state.otherUserId, state.transactionId)) + sharedViewModel.handle(VerificationAction.SASMatchAction(otherUserId, txId)) } @OnClick(R.id.sas_request_cancel_button) fun onDoNotMatchButtonTapped() = withState(viewModel) { state -> + val otherUserId = state.otherUser?.id ?: return@withState + val txId = state.transactionId ?: return@withState // UX echo ButtonsVisibilityGroup.isInvisible = true sasCodeWaitingPartnerText.isVisible = true - sharedViewModel.handle(VerificationAction.SASDoNotMatchAction(state.otherUserId, state.transactionId)) + sharedViewModel.handle(VerificationAction.SASDoNotMatchAction(otherUserId, txId)) } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt index 0b39ac373d..cf3a542430 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt @@ -25,12 +25,12 @@ import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransactio import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.VectorViewModel data class SASVerificationCodeViewState( - val transactionId: String, - val otherUserId: String, + val transactionId: String?, val otherUser: MatrixItem? = null, val supportsEmoji: Boolean = true, val emojiDescription: Async> = Uninitialized, @@ -45,23 +45,9 @@ class SASVerificationCodeViewModel @AssistedInject constructor( init { withState { state -> - val matrixItem = session.getUser(state.otherUserId)?.toMatrixItem() - setState { - copy(otherUser = matrixItem) - } - val sasTx = session.getSasVerificationService() - .getExistingTransaction(state.otherUserId, state.transactionId) - if (sasTx == null) { - setState { - copy( - isWaitingFromOther = false, - emojiDescription = Fail(Throwable("Unknown Transaction")), - decimalDescription = Fail(Throwable("Unknown Transaction")) - ) - } - } else { - refreshStateFromTx(sasTx) - } + refreshStateFromTx(session.getSasVerificationService() + .getExistingTransaction(state.otherUser?.id ?: "", state.transactionId + ?: "")) } session.getSasVerificationService().addListener(this) @@ -72,8 +58,8 @@ class SASVerificationCodeViewModel @AssistedInject constructor( super.onCleared() } - private fun refreshStateFromTx(sasTx: SasVerificationTransaction) { - when (sasTx.state) { + private fun refreshStateFromTx(sasTx: SasVerificationTransaction?) { + when (sasTx?.state) { SasVerificationTxState.None, SasVerificationTxState.SendingStart, SasVerificationTxState.Started, @@ -131,6 +117,15 @@ class SASVerificationCodeViewModel @AssistedInject constructor( ) } } + null -> { + setState { + copy( + isWaitingFromOther = false, + emojiDescription = Fail(Throwable("Unknown Transaction")), + decimalDescription = Fail(Throwable("Unknown Transaction")) + ) + } + } } } @@ -138,8 +133,10 @@ class SASVerificationCodeViewModel @AssistedInject constructor( transactionUpdated(tx) } - override fun transactionUpdated(tx: SasVerificationTransaction) { - refreshStateFromTx(tx) + override fun transactionUpdated(tx: SasVerificationTransaction) = withState { state -> + if (tx.transactionId == state.transactionId) { + refreshStateFromTx(tx) + } } @AssistedInject.Factory @@ -156,9 +153,12 @@ class SASVerificationCodeViewModel @AssistedInject constructor( override fun initialState(viewModelContext: ViewModelContext): SASVerificationCodeViewState? { val args = viewModelContext.args() + val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() + val matrixItem = session.getUser(args.otherUserId)?.toMatrixItem() + return SASVerificationCodeViewState( - transactionId = args.verificationId ?: "", - otherUserId = args.otherUserId + transactionId = args.verificationId, + otherUser = matrixItem ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index cd0a60667c..051b7a4e17 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -30,6 +30,7 @@ import androidx.transition.AutoTransition import androidx.transition.TransitionManager import butterknife.BindView import butterknife.ButterKnife +import butterknife.Unbinder import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.Success import com.airbnb.mvrx.fragmentViewModel @@ -75,12 +76,20 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { @BindView(R.id.verificationRequestAvatar) lateinit var otherUserAvatarImageView: ImageView + private var unBinder: Unbinder? = null + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.bottom_sheet_verification, container, false) - ButterKnife.bind(this, view) + unBinder = ButterKnife.bind(this, view) return view } + override fun onDestroyView() { + super.onDestroyView() + unBinder?.unbind() + unBinder = null + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -183,6 +192,20 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } } } + + companion object { + fun withArgs(roomId: String, otherUserId: String, transactionId: String? = null): VerificationBottomSheet { + return VerificationBottomSheet().apply { + arguments = Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs( + otherUserId = otherUserId, + roomId = roomId, + verificationId = transactionId + )) + } + } + } + } } fun View.getParentCoordinatorLayout(): CoordinatorLayout? { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index 43420ea0f8..e25a22e668 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -112,8 +112,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini } } is VerificationAction.StartSASVerification -> { - val request = session.getSasVerificationService().getExistingVerificationRequest(otherUserId) - ?.firstOrNull { it.transactionId == action.pendingRequestTransactionId } + val request = session.getSasVerificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId) ?: return@withState val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice @@ -134,7 +133,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini is VerificationAction.SASDoNotMatchAction -> { session.getSasVerificationService() .getExistingTransaction(action.userID, action.sasTransactionId) - ?.shortCodeDoNotMatch() + ?.shortCodeDoesNotMatch() } is VerificationAction.GotItConclusion -> { _requestLiveData.postValue(LiveEvent(Success(action))) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt index 96a1cafdb1..bed63730bd 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt @@ -23,6 +23,7 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.VectorViewModel @@ -38,19 +39,6 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( private val session: Session ) : VectorViewModel(initialState) { - init { - withState { state -> - val pvr = session.getSasVerificationService().getExistingVerificationRequest(state.otherUserId)?.first { - it.transactionId == state.transactionId - } - val qrAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SCAN) ?: false - val emojiAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SAS) ?: false - setState { - copy(QRModeAvailable = qrAvailable, SASMOdeAvailable = emojiAvailable) - } - } - } - @AssistedInject.Factory interface Factory { fun create(initialState: VerificationChooseMethodViewState): VerificationChooseMethodViewModel @@ -64,7 +52,18 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( override fun initialState(viewModelContext: ViewModelContext): VerificationChooseMethodViewState? { val args: VerificationBottomSheet.VerificationArgs = viewModelContext.args() - return VerificationChooseMethodViewState(otherUserId = args.otherUserId, transactionId = args.verificationId ?: "") + val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() + val pvr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId) + val qrAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SCAN) + ?: false + val emojiAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SAS) + ?: false + + return VerificationChooseMethodViewState(otherUserId = args.otherUserId, + transactionId = args.verificationId ?: "", + QRModeAvailable = qrAvailable, + SASMOdeAvailable = emojiAvailable + ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt index 57bb80976e..fdf4b0cbd4 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.crypto.verification import android.os.Parcelable import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import butterknife.OnClick import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel @@ -53,7 +54,7 @@ class VerificationConclusionFragment @Inject constructor() : VectorBaseFragment( } ConclusionState.WARNING -> { verificationConclusionTitle.text = getString(R.string.verification_conclusion_not_secure) - verifyConclusionDescription.setTextOrHide(null) + verifyConclusionDescription.isVisible = false verifyConclusionImageView.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_shield_warning)) verifyConclusionBottomDescription.text = Markwon.builder(requireContext()) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt index d0b7fe4dd9..b21ce4e3a5 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.riotx.core.di.HasScreenInjector +import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.VectorViewModel data class VerificationRequestViewState( @@ -36,7 +37,7 @@ data class VerificationRequestViewState( class VerificationRequestViewModel @AssistedInject constructor( @Assisted initialState: VerificationRequestViewState, private val session: Session -) : VectorViewModel(initialState), SasVerificationService.SasVerificationListener { +) : VectorViewModel(initialState), SasVerificationService.SasVerificationListener { @AssistedInject.Factory interface Factory { @@ -75,8 +76,7 @@ class VerificationRequestViewModel @AssistedInject constructor( } } - override fun handle(action: VerificationAction) { - } + override fun handle(action: EmptyAction) {} override fun transactionCreated(tx: SasVerificationTransaction) {} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 75b24e26b4..712c57125b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -432,7 +432,8 @@ class RoomDetailFragment @Inject constructor( composerLayout.sendButton.setContentDescription(getString(descriptionRes)) avatarRenderer.render( - MatrixItem.UserItem(event.root.senderId ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar), + MatrixItem.UserItem(event.root.senderId + ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar), composerLayout.composerRelatedMessageAvatar ) composerLayout.expand { @@ -962,20 +963,17 @@ class RoomDetailFragment @Inject constructor( } } is RoomDetailAction.RequestVerification -> { - VerificationBottomSheet().apply { - arguments = Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs(data.userId, roomId = roomDetailArgs.roomId)) - } -// setArguments() - }.show(parentFragmentManager, "REQ") + VerificationBottomSheet.withArgs( + roomDetailArgs.roomId, + data.userId + ).show(parentFragmentManager, "REQ") } is RoomDetailAction.AcceptVerificationRequest -> { - VerificationBottomSheet().apply { - arguments = Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs( - data.otherUserId, data.transactionId, roomId = roomDetailArgs.roomId)) - } - }.show(parentFragmentManager, "REQ") + VerificationBottomSheet.withArgs( + roomDetailArgs.roomId, + data.otherUserId, + data.transactionId + ).show(parentFragmentManager, "REQ") } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt index 3022c7974a..a0ef876f1a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt @@ -73,7 +73,7 @@ abstract class VerificationRequestItem : AbsBaseMessageItem { + null -> { holder.buttonBar.isVisible = !attributes.informationData.sentByMe holder.statusTextView.text = null holder.statusTextView.isVisible = false @@ -83,17 +83,17 @@ abstract class VerificationRequestItem : AbsBaseMessageItem { + 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) @@ -102,7 +102,7 @@ abstract class VerificationRequestItem : AbsBaseMessageItem { + else -> { holder.buttonBar.isVisible = false holder.statusTextView.text = null holder.statusTextView.isVisible = false @@ -110,7 +110,9 @@ abstract class VerificationRequestItem : AbsBaseMessageItem - + + + + + + + + + android:id="@+id/sas_emoji_grid" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="@dimen/layout_vertical_margin" + android:visibility="invisible" + app:layout_constrainedWidth="true" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/sas_emoji_description_2" + tools:visibility="visible"> - + - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:constraint_referenced_ids="emoji0,emoji1,emoji2,emoji3,emoji4,emoji5,emoji6" + app:flow_horizontalBias="0.5" + app:flow_horizontalGap="16dp" + app:flow_horizontalStyle="packed" + app:flow_verticalBias="0" + app:flow_verticalGap="8dp" + app:flow_wrapMode="chain" + tools:ignore="MissingConstraints" /> - \ No newline at end of file + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_verification_conclusion.xml b/vector/src/main/res/layout/fragment_verification_conclusion.xml index 099297b936..0c5ab7e6c2 100644 --- a/vector/src/main/res/layout/fragment_verification_conclusion.xml +++ b/vector/src/main/res/layout/fragment_verification_conclusion.xml @@ -14,21 +14,21 @@ android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:text="@string/sas_verified" - tools:text="@string/sas_verified" android:textColor="?riotx_text_primary" android:textSize="20sp" android:textStyle="bold" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + tools:text="@string/sas_verified" /> + app:layout_constraintTop_toBottomOf="@id/verificationConclusionTitle" + tools:text="@string/sas_verified_successful_description" /> + app:layout_constraintTop_toBottomOf="@id/verifyConclusionDescription" + tools:background="@drawable/ic_shield_trusted" /> + app:layout_constraintTop_toBottomOf="@id/verifyConclusionImageView" + tools:text="@string/verification_green_shield" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@id/verificationRequestText" /> diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index e9db6a4f39..6c3833a782 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -10,7 +10,7 @@ Untrusted sign in They match - They don‘t match + They don\'t match Verify this user by confirming the following unique emoji appear on their screen, in the same order." For ultimate security, use another trusted means of communication or do this in person. Look for the green shield to ensure a user is trusted. Trust all users in a room to ensure the room is secure. @@ -31,4 +31,22 @@ You accepted Verification Sent Verification Request + + + You + + Verify by scanning + + Ask the other user to scan this code, or %s to scan theirs + + open your camera + + Verify by Emoji + If you can’t scan the code above, verify by comparing a short, unique selection of emoji. + + QR code image + + Verify %s + Waiting for %s… + For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person. From c2cd1492995c3cc5f2cf565568aa1cd6342375c8 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 3 Jan 2020 19:06:00 +0100 Subject: [PATCH 12/12] Fix / accept button was not starting the verify sheet Was launching start sheet, because request was not known by VerificationService. Due to message observer blocked trying to download keys.. --- .../crypto/sas/SasVerificationService.kt | 5 ++- .../tasks/RoomVerificationUpdateTask.kt | 1 + .../DefaultSasVerificationService.kt | 42 ++++++++++++------- .../verification/VerificationBottomSheet.kt | 37 +++++++++++----- .../VerificationChooseMethodViewModel.kt | 33 ++++++++++++++- .../home/room/detail/RoomDetailFragment.kt | 2 + .../home/room/detail/RoomDetailViewModel.kt | 8 ++-- 7 files changed, 99 insertions(+), 29 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt index ca6259e7ec..c2a847dd0b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt @@ -66,7 +66,10 @@ interface SasVerificationService { otherDeviceId: String, callback: MatrixCallback?): String? - fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String) + /** + * Returns false if the request is unknwown + */ + fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String): Boolean // fun transactionUpdated(tx: SasVerificationTransaction) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt index ec25c0dd04..2167940aaa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt @@ -120,6 +120,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( } } + Timber.v("## SAS Verification ignoring message sent by me: ${event.eventId} type: ${event.getClearType()}") return@forEach } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index c4645f42cd..f5075cbb23 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -216,7 +216,7 @@ internal class DefaultSasVerificationService @Inject constructor( } } - suspend fun onRoomRequestReceived(event: Event) { + fun onRoomRequestReceived(event: Event) { Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}") val requestInfo = event.getClearContent().toModel() ?: return @@ -228,11 +228,11 @@ internal class DefaultSasVerificationService @Inject constructor( return } - if (checkKeysAreDownloaded(senderId, requestInfo.fromDevice) == null) { - // I should ignore this, it's not for me - Timber.e("## SAS Verification device ${requestInfo.fromDevice} is not knwon") - // TODO cancel? - return + // 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") + } } // Remember this request @@ -249,6 +249,7 @@ internal class DefaultSasVerificationService @Inject constructor( requestInfo = requestInfo ) requestsForUser.add(pendingVerificationRequest) + dispatchRequestAdded(pendingVerificationRequest) /* * After the m.key.verification.ready event is sent, either party can send an m.key.verification.start event @@ -389,9 +390,14 @@ internal class DefaultSasVerificationService @Inject constructor( private suspend fun checkKeysAreDownloaded(otherUserId: String, fromDevice: String): MXUsersDevicesMap? { return try { - val keys = deviceListManager.downloadKeys(listOf(otherUserId), true) - val deviceIds = keys.getUserDeviceIds(otherUserId) ?: return null - keys.takeIf { deviceIds.contains(fromDevice) } + var keys = deviceListManager.downloadKeys(listOf(otherUserId), false) + if (keys.getUserDeviceIds(otherUserId)?.contains(fromDevice) == true) { + return keys + } else { + // force download + keys = deviceListManager.downloadKeys(listOf(otherUserId), true) + return keys.takeIf { keys.getUserDeviceIds(otherUserId)?.contains(fromDevice) == true } + } } catch (e: Exception) { null } @@ -785,16 +791,19 @@ internal class DefaultSasVerificationService @Inject constructor( } } - override fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String) { + override fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String): Boolean { + Timber.v("## SAS readyPendingVerificationInDMs $otherUserId room:$roomId tx:$transactionId") // Let's find the related request - getExistingVerificationRequest(otherUserId)?.find { it.transactionId == transactionId }?.let { + val existingRequest = getExistingVerificationRequest(otherUserId, transactionId) + if (existingRequest != null) { // we need to send a ready event, with matching methods val transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId ?: "", roomId, null) - val methods = it.requestInfo?.methods?.intersect(listOf(KeyVerificationStart.VERIF_METHOD_SAS))?.toList() + val methods = existingRequest.requestInfo?.methods?.intersect(listOf(KeyVerificationStart.VERIF_METHOD_SAS))?.toList() if (methods.isNullOrEmpty()) { Timber.i("Cannot ready this request, no common methods found txId:$transactionId") - return@let + // TODO buttons should not be shown in this case? + return false } // TODO this is not yet related to a transaction, maybe we should use another method like for cancel? val readyMsg = transport.createReady(transactionId, credentials.deviceId ?: "", methods) @@ -803,7 +812,12 @@ internal class DefaultSasVerificationService @Inject constructor( CancelCode.User, null // TODO handle error? ) - updatePendingRequest(it.copy(readyInfo = readyMsg)) + updatePendingRequest(existingRequest.copy(readyInfo = readyMsg)) + return true + } else { + Timber.e("## SAS readyPendingVerificationInDMs Verification not found") + // :/ should not be possible... unless live observer very slow + return false } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index 051b7a4e17..61598bd5fc 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -45,6 +45,7 @@ import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.themes.ThemeUtils import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.bottom_sheet_verification.* +import timber.log.Timber import javax.inject.Inject import kotlin.reflect.KClass @@ -154,25 +155,41 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { return@withState } + // At this point there is no transaction for this request + // Transaction has not yet started if (it.pendingRequest?.cancelConclusion != null) { // The request has been declined, we should dismiss dismiss() - } else if (it.pendingRequest == null || !it.pendingRequest.isReady) { - // We are waiting for other party to reply with ready - showFragment(VerificationRequestFragment::class, Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationArgs( - it.otherUserMxItem?.id ?: "", - it.pendingRequest?.transactionId, - it.roomId)) - }) - } else if (it.pendingRequest.isReady) { + } + + // If it's an outgoing + if (it.pendingRequest == null || !it.pendingRequest.isIncoming) { + Timber.v("## SAS show bottom sheet for outgoing request") + if (it.pendingRequest?.isReady == true) { + Timber.v("## SAS show bottom sheet for outgoing and ready request") + // Show choose method fragment with waiting + showFragment(VerificationChooseMethodFragment::class, Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id + ?: "", it.pendingRequest.transactionId)) + }) + } else { + // Stay on the start fragment + showFragment(VerificationRequestFragment::class, Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationArgs( + it.otherUserMxItem?.id ?: "", + it.pendingRequest?.transactionId, + it.roomId)) + }) + } + } else if (it.pendingRequest.isIncoming) { + Timber.v("## SAS show bottom sheet for Incoming request") + // For incoming we can switch to choose method because ready is being sent or already sent showFragment(VerificationChooseMethodFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id ?: "", it.pendingRequest.transactionId)) }) } - super.invalidate() } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt index bed63730bd..2fcf94a036 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt @@ -22,7 +22,10 @@ import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.VectorViewModel @@ -37,13 +40,41 @@ data class VerificationChooseMethodViewState( class VerificationChooseMethodViewModel @AssistedInject constructor( @Assisted initialState: VerificationChooseMethodViewState, private val session: Session -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState), SasVerificationService.SasVerificationListener { + + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) {} + + override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> + val pvr = session.getSasVerificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId) + val qrAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SCAN) + ?: false + val emojiAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SAS) + ?: false + + setState { + copy( + QRModeAvailable = qrAvailable, + SASMOdeAvailable = emojiAvailable + ) + } + } @AssistedInject.Factory interface Factory { fun create(initialState: VerificationChooseMethodViewState): VerificationChooseMethodViewModel } + init { + session.getSasVerificationService().addListener(this) + } + + override fun onCleared() { + super.onCleared() + session.getSasVerificationService().removeListener(this) + } + companion object : MvRxViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: VerificationChooseMethodViewState): VerificationChooseMethodViewModel? { val fragment: VerificationChooseMethodFragment = (viewModelContext as FragmentViewModelContext).fragment() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 712c57125b..0efbe255fe 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -963,12 +963,14 @@ class RoomDetailFragment @Inject constructor( } } is RoomDetailAction.RequestVerification -> { + Timber.v("## SAS RequestVerification action") VerificationBottomSheet.withArgs( roomDetailArgs.roomId, data.userId ).show(parentFragmentManager, "REQ") } is RoomDetailAction.AcceptVerificationRequest -> { + Timber.v("## SAS AcceptVerificationRequest action") VerificationBottomSheet.withArgs( roomDetailArgs.roomId, data.otherUserId, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index ef1b72a402..6168a2bd5a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -795,9 +795,11 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) { - session.getSasVerificationService().readyPendingVerificationInDMs(action.otherUserId, room.roomId, - action.transactionId) - _requestLiveData.postValue(LiveEvent(Success(action))) + Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}") + if (session.getSasVerificationService().readyPendingVerificationInDMs(action.otherUserId, room.roomId, + action.transactionId)) { + _requestLiveData.postValue(LiveEvent(Success(action))) + } } private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) {