Add Session Verification flow (#197)

This commit is contained in:
Jorge Martin Espinosa 2023-03-17 10:07:19 +01:00 committed by GitHub
parent 1795a844a1
commit dcb98f06aa
76 changed files with 2347 additions and 35 deletions

View file

@ -23,12 +23,17 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaResolver
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.impl.media.RustMediaResolver
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource
import io.element.android.libraries.matrix.impl.sync.SlidingSyncObserverProxy
import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService
import io.element.android.libraries.sessionstorage.api.SessionStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientDelegate
@ -53,6 +58,9 @@ class RustMatrixClient constructor(
override val sessionId: UserId = UserId(client.userId())
private val verificationService = RustSessionVerificationService()
private var slidingSyncUpdateJob: Job? = null
private val clientDelegate = object : ClientDelegate {
override fun didReceiveAuthError(isSoftLogout: Boolean) {
Timber.v("didReceiveAuthError()")
@ -131,6 +139,9 @@ class RustMatrixClient constructor(
client.setDelegate(clientDelegate)
rustRoomSummaryDataSource.init()
slidingSync.setObserver(slidingSyncObserverProxy)
slidingSyncUpdateJob = slidingSyncObserverProxy.updateSummaryFlow
.onEach { onSlidingSyncUpdate() }
.launchIn(coroutineScope)
}
private fun onRestartSync() {
@ -152,6 +163,8 @@ class RustMatrixClient constructor(
override fun mediaResolver(): MediaResolver = mediaResolver
override fun sessionVerificationService(): SessionVerificationService = verificationService
override fun startSync() {
if (client.isSoftLogout()) return
if (isSyncing.compareAndSet(false, true)) {
@ -166,12 +179,14 @@ class RustMatrixClient constructor(
}
private fun close() {
slidingSyncUpdateJob?.cancel()
stopSync()
slidingSync.setObserver(null)
rustRoomSummaryDataSource.close()
client.setDelegate(null)
visibleRoomsView.destroy()
slidingSync.destroy()
verificationService.destroy()
}
override suspend fun logout() = withContext(dispatchers.io) {
@ -226,6 +241,16 @@ class RustMatrixClient constructor(
}
}
override fun onSlidingSyncUpdate() {
if (!verificationService.isReady.value) {
try {
verificationService.verificationController = client.getSessionVerificationController()
} catch (e: Throwable) {
Timber.e(e, "Could not start verification service. Will try again on the next sliding sync update.")
}
}
}
private fun File.deleteSessionDirectory(userID: String): Boolean {
// Rust sanitises the user ID replacing invalid characters with an _
val sanitisedUserID = userID.replace(":", "_")

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.impl.di
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
@Module
@ContributesTo(SessionScope::class)
object SessionMatrixModule {
@Provides
@SingleIn(SessionScope::class)
fun providesRustSessionVerificationService(matrixClient: MatrixClient): SessionVerificationService {
return matrixClient.sessionVerificationService()
}
}

View file

@ -36,7 +36,6 @@ class SlidingSyncObserverProxy(
val updateSummaryFlow: SharedFlow<UpdateSummary> = updateSummaryMutableFlow.asSharedFlow()
override fun didReceiveSyncUpdate(summary: UpdateSummary) {
if (summary.rooms.isEmpty()) return
coroutineScope.launch {
updateSummaryMutableFlow.emit(summary)
}

View file

@ -0,0 +1,132 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.impl.verification
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.matrix.rustcomponents.sdk.SessionVerificationController
import org.matrix.rustcomponents.sdk.SessionVerificationControllerDelegate
import org.matrix.rustcomponents.sdk.SessionVerificationControllerInterface
import org.matrix.rustcomponents.sdk.SessionVerificationEmoji
import javax.inject.Inject
class RustSessionVerificationService @Inject constructor() : SessionVerificationService, SessionVerificationControllerDelegate {
var verificationController: SessionVerificationControllerInterface? = null
set(value) {
field = value
_isReady.value = value != null
// If status was 'Unknown', move it to either 'Verified' or 'NotVerified'
if (value != null) {
updateVerificationStatus(value.isVerified())
}
}
private val _verificationFlowState = MutableStateFlow<VerificationFlowState>(VerificationFlowState.Initial)
override val verificationFlowState = _verificationFlowState.asStateFlow()
private val _isReady = MutableStateFlow(false)
override val isReady = _isReady.asStateFlow()
private val _sessionVerifiedStatus = MutableStateFlow<SessionVerifiedStatus>(SessionVerifiedStatus.Unknown)
override val sessionVerifiedStatus: StateFlow<SessionVerifiedStatus> = _sessionVerifiedStatus.asStateFlow()
override fun requestVerification() = tryOrFail {
verificationController?.setDelegate(this)
verificationController?.requestVerification()
}
override fun cancelVerification() = tryOrFail { verificationController?.cancelVerification() }
override fun approveVerification() = tryOrFail { verificationController?.approveVerification() }
override fun declineVerification() = tryOrFail { verificationController?.declineVerification() }
override fun startVerification() = tryOrFail {
verificationController?.setDelegate(this)
verificationController?.startSasVerification()
}
private fun tryOrFail(block: () -> Unit) {
runCatching {
block()
}.onFailure { didFail() }
}
// region Delegate implementation
// When verification attempt is accepted by the other device
override fun didAcceptVerificationRequest() {
_verificationFlowState.value = VerificationFlowState.AcceptedVerificationRequest
}
override fun didCancel() {
_verificationFlowState.value = VerificationFlowState.Canceled
}
override fun didFail() {
_verificationFlowState.value = VerificationFlowState.Failed
}
override fun didFinish() {
_verificationFlowState.value = VerificationFlowState.Finished
// Ideally this should be `verificationController?.isVerified().orFalse()` but for some reason it always returns false
updateVerificationStatus(isVerified = true)
}
override fun didReceiveVerificationData(data: List<SessionVerificationEmoji>) {
val emojis = data.map { emoji ->
emoji.use { VerificationEmoji(it.symbol(), it.description()) }
}
_verificationFlowState.value = VerificationFlowState.ReceivedVerificationData(emojis)
}
// When the actual SAS verification starts
override fun didStartSasVerification() {
_verificationFlowState.value = VerificationFlowState.StartedSasVerification
}
// end-region
override fun reset() {
if (isReady.value) {
// Cancel any pending verification attempt
tryOrNull { verificationController?.cancelVerification() }
}
_verificationFlowState.value = VerificationFlowState.Initial
}
fun destroy() {
(verificationController as? SessionVerificationController)?.destroy()
verificationController = null
}
private fun updateVerificationStatus(isVerified: Boolean) {
val newValue = when {
!isReady.value -> SessionVerifiedStatus.Unknown
!isVerified -> SessionVerifiedStatus.NotVerified
else -> SessionVerifiedStatus.Verified
}
_sessionVerifiedStatus.value = newValue
}
}