Add Session Verification flow (#197)
This commit is contained in:
parent
1795a844a1
commit
dcb98f06aa
76 changed files with 2347 additions and 35 deletions
|
|
@ -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(":", "_")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue