Suspend API: handle verification service

This commit is contained in:
ganfra 2022-03-29 17:51:05 +02:00
parent e121007d20
commit 0590258d54
9 changed files with 214 additions and 189 deletions

View File

@ -36,7 +36,7 @@ interface VerificationService {
/**
* Mark this device as verified manually
*/
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction?
@ -46,7 +46,7 @@ interface VerificationService {
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
fun beginKeyVerification(method: VerificationMethod,
suspend fun beginKeyVerification(method: VerificationMethod,
otherUserId: String,
otherDeviceId: String,
transactionId: String?): String?
@ -54,27 +54,27 @@ interface VerificationService {
/**
* Request key verification with another user via room events (instead of the to-device API)
*/
fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
suspend fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
fun cancelVerificationRequest(request: PendingVerificationRequest)
suspend fun cancelVerificationRequest(request: PendingVerificationRequest)
/**
* Request a key verification from another user using toDevice events.
*/
fun requestKeyVerification(methods: List<VerificationMethod>,
suspend fun requestKeyVerification(methods: List<VerificationMethod>,
otherUserId: String,
otherDevices: List<String>?): PendingVerificationRequest
fun declineVerificationRequestInDMs(otherUserId: String,
suspend fun declineVerificationRequestInDMs(otherUserId: String,
transactionId: String,
roomId: String)
// Only SAS method is supported for the moment
// TODO Parameter otherDeviceId should be removed in this case
fun beginKeyVerificationInDMs(method: VerificationMethod,
suspend fun beginKeyVerificationInDMs(method: VerificationMethod,
transactionId: String,
roomId: String,
otherUserId: String,
@ -83,7 +83,7 @@ interface VerificationService {
/**
* Returns false if the request is unknown
*/
fun readyPendingVerificationInDMs(methods: List<VerificationMethod>,
suspend fun readyPendingVerificationInDMs(methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
transactionId: String): Boolean
@ -91,7 +91,7 @@ interface VerificationService {
/**
* Returns false if the request is unknown
*/
fun readyPendingVerification(methods: List<VerificationMethod>,
suspend fun readyPendingVerification(methods: List<VerificationMethod>,
otherUserId: String,
transactionId: String): Boolean

View File

@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.crypto.verification
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
@ -162,13 +161,11 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
this.dispatcher.removeListener(listener)
}
override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
override suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
// TODO this doesn't seem to be used anymore?
runBlocking {
val device = olmMachine.getDevice(userId, deviceID)
device?.markAsTrusted()
}
}
override fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event) {
// TODO This should be handled inside the rust-sdk decryption method
@ -224,13 +221,13 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
return null
}
override fun requestKeyVerification(
override suspend fun requestKeyVerification(
methods: List<VerificationMethod>,
otherUserId: String,
otherDevices: List<String>?
): PendingVerificationRequest {
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
is OwnUserIdentity -> runBlocking { identity.requestVerification(methods) }
val verification = when (val identity = olmMachine.getIdentity(otherUserId)) {
is OwnUserIdentity -> identity.requestVerification(methods)
is UserIdentity -> throw IllegalArgumentException("This method doesn't support verification of other users devices")
null -> throw IllegalArgumentException("Cross signing has not been bootstrapped for our own user")
}
@ -238,15 +235,15 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
return verification.toPendingVerificationRequest()
}
override fun requestKeyVerificationInDMs(
override suspend fun requestKeyVerificationInDMs(
methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
localId: String?
): PendingVerificationRequest {
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
is UserIdentity -> runBlocking { identity.requestVerification(methods, roomId, localId!!) }
val verification = when (val identity = olmMachine.getIdentity(otherUserId)) {
is UserIdentity -> identity.requestVerification(methods, roomId, localId!!)
is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user")
null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing")
}
@ -254,7 +251,7 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
return verification.toPendingVerificationRequest()
}
override fun readyPendingVerification(
override suspend fun readyPendingVerification(
methods: List<VerificationMethod>,
otherUserId: String,
transactionId: String
@ -262,7 +259,7 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
val request = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
return if (request != null) {
runBlocking { request.acceptWithMethods(methods) }
request.acceptWithMethods(methods)
if (request.isReady()) {
val qrcode = request.startQrVerification()
@ -280,7 +277,7 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
}
}
override fun readyPendingVerificationInDMs(
override suspend fun readyPendingVerificationInDMs(
methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
@ -289,7 +286,7 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
return readyPendingVerification(methods, otherUserId, transactionId)
}
override fun beginKeyVerification(
override suspend fun beginKeyVerification(
method: VerificationMethod,
otherUserId: String,
otherDeviceId: String,
@ -299,7 +296,6 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
if (transactionId != null) {
val request = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
runBlocking {
val sas = request?.startSasVerification()
if (sas != null) {
@ -308,14 +304,12 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
} else {
null
}
}
} else {
// This starts the short SAS flow, the one that doesn't start with
// a `m.key.verification.request`, Element web stopped doing this, might
// be wise do do so as well
// DeviceListBottomSheetViewModel triggers this, interestingly the method that
// triggers this is called `manuallyVerify()`
runBlocking {
val verification = olmMachine.getDevice(otherUserId, otherDeviceId)?.startVerification()
if (verification != null) {
dispatcher.dispatchTxAdded(verification)
@ -324,13 +318,12 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
null
}
}
}
} else {
throw IllegalArgumentException("Unknown verification method")
}
}
override fun beginKeyVerificationInDMs(
override suspend fun beginKeyVerificationInDMs(
method: VerificationMethod,
transactionId: String,
roomId: String,
@ -343,19 +336,19 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
return transactionId
}
override fun cancelVerificationRequest(request: PendingVerificationRequest) {
override suspend fun cancelVerificationRequest(request: PendingVerificationRequest) {
val verificationRequest = request.transactionId?.let {
this.olmMachine.getVerificationRequest(request.otherUserId, it)
}
runBlocking { verificationRequest?.cancel() }
verificationRequest?.cancel()
}
override fun declineVerificationRequestInDMs(
override suspend fun declineVerificationRequestInDMs(
otherUserId: String,
transactionId: String,
roomId: String
) {
val verificationRequest = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
runBlocking { verificationRequest?.cancel() }
verificationRequest?.cancel()
}
}

View File

@ -149,11 +149,13 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
// as we are going to reset, we'd better cancel all outgoing requests
// if not they could be accepted in the middle of the reset process
// and cause strange use cases
viewModelScope.launch {
session.cryptoService().verificationService().getExistingVerificationRequests(session.myUserId).forEach {
session.cryptoService().verificationService().cancelVerificationRequest(it)
}
_viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet)
}
}
private fun handleResetAll() {
setState {

View File

@ -24,6 +24,8 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.popup.VerificationVectorAlert
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
@ -42,7 +44,8 @@ import javax.inject.Singleton
class IncomingVerificationRequestHandler @Inject constructor(
private val context: Context,
private var avatarRenderer: Provider<AvatarRenderer>,
private val popupAlertManager: PopupAlertManager) : VerificationService.Listener {
private val popupAlertManager: PopupAlertManager,
private val coroutineScope: CoroutineScope) : VerificationService.Listener {
private var session: Session? = null
@ -161,11 +164,13 @@ class IncomingVerificationRequestHandler @Inject constructor(
}
}
dismissedAction = Runnable {
coroutineScope.launch {
session?.cryptoService()?.verificationService()?.declineVerificationRequestInDMs(pr.otherUserId,
pr.transactionId ?: "",
pr.roomId ?: ""
)
}
}
colorAttribute = R.attr.vctr_notice_secondary
// 5mn expiration
expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L)

View File

@ -152,6 +152,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
}
if (autoReady) {
viewModelScope.launch {
// TODO, can I be here in DM mode? in this case should test if roomID is null?
session.cryptoService().verificationService()
.readyPendingVerification(
@ -161,6 +162,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
)
}
}
}
override fun onCleared() {
session.cryptoService().verificationService().removeListener(this)
@ -192,6 +194,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
}
private fun cancelAllPendingVerifications(state: VerificationBottomSheetViewState) {
viewModelScope.launch {
session.cryptoService()
.verificationService().getExistingVerificationRequest(state.otherUserMxItem?.id ?: "", state.transactionId)?.let {
session.cryptoService().verificationService().cancelVerificationRequest(it)
@ -201,6 +204,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
.getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "")
?.cancel(CancelCode.User)
}
}
fun continueFromCancel() {
setState {
@ -232,59 +236,13 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
when (action) {
is VerificationAction.RequestVerificationByDM -> {
if (roomId == null) {
val localId = LocalEcho.createLocalEchoId()
setState {
copy(
pendingLocalId = localId,
pendingRequest = Loading()
)
}
viewModelScope.launch {
val result = runCatching { session.createDirectRoom(otherUserId) }
result.fold(
{ data ->
setState {
copy(
roomId = data,
pendingRequest = Success(
session
.cryptoService()
.verificationService()
.requestKeyVerificationInDMs(
supportedVerificationMethodsProvider.provide(),
otherUserId,
data,
pendingLocalId
)
)
)
}
},
{ failure ->
setState {
copy(pendingRequest = Fail(failure))
}
}
)
}
} else {
setState {
copy(
pendingRequest = Success(session
.cryptoService()
.verificationService()
.requestKeyVerificationInDMs(supportedVerificationMethodsProvider.provide(), otherUserId, roomId)
)
)
}
}
Unit
handleRequestVerificationByDM(roomId, otherUserId)
}
is VerificationAction.StartSASVerification -> {
val request = session.cryptoService().verificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId)
?: return@withState
val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice
viewModelScope.launch {
if (roomId == null) {
session.cryptoService().verificationService().beginKeyVerification(
VerificationMethod.SAS,
@ -301,6 +259,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
otherDeviceId = otherDevice ?: ""
)
}
}
Unit
}
is VerificationAction.RemoteQrCodeScanned -> {
@ -365,6 +324,50 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
}.exhaustive
}
private fun handleRequestVerificationByDM(roomId: String?, otherUserId: String) {
viewModelScope.launch {
if (roomId == null) {
val localId = LocalEcho.createLocalEchoId()
setState {
copy(
pendingLocalId = localId,
pendingRequest = Loading()
)
}
try {
val dmRoomId = session.createDirectRoom(otherUserId)
val pendingRequest = session
.cryptoService()
.verificationService()
.requestKeyVerificationInDMs(
supportedVerificationMethodsProvider.provide(),
otherUserId,
dmRoomId,
localId
)
setState {
copy(
roomId = dmRoomId,
pendingRequest = Success(pendingRequest)
)
}
} catch (failure: Throwable) {
setState {
copy(pendingRequest = Fail(failure))
}
}
} else {
val pendingRequest = session
.cryptoService()
.verificationService()
.requestKeyVerificationInDMs(supportedVerificationMethodsProvider.provide(), otherUserId, roomId)
setState {
copy(pendingRequest = Success(pendingRequest))
}
}
}
}
private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
viewModelScope.launch(Dispatchers.IO) {
try {
@ -514,6 +517,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
if (!pr.isReady) {
// auto ready in this case, as we are waiting
// TODO, can I be here in DM mode? in this case should test if roomID is null?
viewModelScope.launch {
session.cryptoService().verificationService()
.readyPendingVerification(
supportedVerificationMethodsProvider.provide(),
@ -521,6 +525,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
pr.transactionId ?: ""
)
}
}
// Use this one!
setState {

View File

@ -994,6 +994,7 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}")
viewModelScope.launch {
if (session.cryptoService().verificationService().readyPendingVerificationInDMs(
supportedVerificationMethodsProvider.provide(),
action.otherUserId,
@ -1004,13 +1005,16 @@ class TimelineViewModel @AssistedInject constructor(
// TODO
}
}
}
private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) {
viewModelScope.launch {
session.cryptoService().verificationService().declineVerificationRequestInDMs(
action.otherUserId,
action.transactionId,
room.roomId)
}
}
private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) {
if (action.userId == session.myUserId) return

View File

@ -100,6 +100,8 @@ import im.vector.app.features.terms.ReviewTermsActivity
import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.WidgetArgsBuilder
import im.vector.app.space
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
@ -116,7 +118,8 @@ class DefaultNavigator @Inject constructor(
private val widgetArgsBuilder: WidgetArgsBuilder,
private val appStateHandler: AppStateHandler,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val features: VectorFeatures
private val features: VectorFeatures,
private val coroutineScope: CoroutineScope
) : Navigator {
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) {
@ -210,7 +213,8 @@ class DefaultNavigator @Inject constructor(
}
override fun requestSessionVerification(context: Context, otherSessionId: String) {
val session = sessionHolder.getSafeActiveSession() ?: return
coroutineScope.launch {
val session = sessionHolder.getSafeActiveSession() ?: return@launch
val pr = session.cryptoService().verificationService().requestKeyVerification(
supportedVerificationMethodsProvider.provide(),
session.myUserId,
@ -224,9 +228,11 @@ class DefaultNavigator @Inject constructor(
).show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
}
}
}
override fun requestSelfSessionVerification(context: Context) {
val session = sessionHolder.getSafeActiveSession() ?: return
coroutineScope.launch {
val session = sessionHolder.getSafeActiveSession() ?: return@launch
val otherSessions = session.cryptoService()
.getCryptoDeviceInfo(session.myUserId)
.filter { it.deviceId != session.sessionParams.deviceId }
@ -245,6 +251,7 @@ class DefaultNavigator @Inject constructor(
}
}
}
}
override fun waitSessionVerification(context: Context) {
val session = sessionHolder.getSafeActiveSession() ?: return

View File

@ -30,6 +30,7 @@ import im.vector.app.core.di.SingletonEntryPoint
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
@ -124,8 +125,14 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva
private fun manuallyVerify(action: DeviceListAction.ManuallyVerify) {
if (!initialState.allowDeviceAction) return
session.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, initialState.userId, action.deviceId, null)?.let { txID ->
viewModelScope.launch {
session.cryptoService().verificationService().beginKeyVerification(
method = VerificationMethod.SAS,
otherUserId = initialState.userId,
otherDeviceId = action.deviceId,
transactionId = null)?.let { txID ->
_viewEvents.post(DeviceListBottomSheetViewEvents.Verify(initialState.userId, txID))
}
}
}
}

View File

@ -240,6 +240,7 @@ class DevicesViewModel @AssistedInject constructor(
}
private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) {
viewModelScope.launch {
val txID = session.cryptoService()
.verificationService()
.beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId, null)
@ -248,6 +249,7 @@ class DevicesViewModel @AssistedInject constructor(
txID
))
}
}
private fun handleShowDeviceCryptoInfo(action: DevicesAction.VerifyMyDeviceManually) = withState { state ->
state.devices.invoke()