Merge pull request #3461 from element-hq/feature/fga/send_failure_identity_changes

Require acknowledgement to send to a verified user if their identity changed or if a device is unverified.
This commit is contained in:
ganfra 2024-09-16 16:00:18 +02:00 committed by GitHub
commit 60365e46a5
50 changed files with 1355 additions and 87 deletions

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.api.room
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias
@ -349,5 +350,24 @@ interface MatrixRoom : Closeable {
*/
suspend fun clearComposerDraft(): Result<Unit>
/**
* Ignore the local trust for the given devices and resend messages that failed to send because said devices are unverified.
*
* @param devices The map of users identifiers to device identifiers received in the error
* @param transactionId The send queue transaction identifier of the local echo the send error applies to.
*
*/
suspend fun ignoreDeviceTrustAndResend(devices: Map<UserId, List<DeviceId>>, transactionId: TransactionId): Result<Unit>
/**
* Remove verification requirements for the given users and
* resend messages that failed to send because their identities were no longer verified.
*
* @param userIds The list of users identifiers received in the error.
* @param transactionId The send queue transaction identifier of the local echo the send error applies to.
*
*/
suspend fun withdrawVerificationAndResend(userIds: List<UserId>, transactionId: TransactionId): Result<Unit>
override fun close() = destroy()
}

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.api.timeline.item.event
import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
@ -18,21 +19,24 @@ sealed interface LocalEventSendState {
data class Unknown(val error: String) : Failed
data object CrossSigningNotSetup : Failed
data object SendingFromUnverifiedDevice : Failed
sealed interface VerifiedUser : Failed
data class VerifiedUserHasUnsignedDevice(
/**
* The unsigned devices belonging to verified users. A map from user ID
* to a list of device IDs.
*/
val devices: Map<UserId, List<String>>
) : Failed
val devices: Map<UserId, List<DeviceId>>
) : VerifiedUser
data class VerifiedUserChangedIdentity(
/**
* The users that were previously verified but are no longer.
*/
val users: List<UserId>
) : Failed
) : VerifiedUser
}
data class Sent(
val eventId: EventId
) : LocalEventSendState

View file

@ -100,7 +100,7 @@ class RustMatrixClientFactory @Inject constructor(
strategy = if (featureFlagService.isFeatureEnabled(FeatureFlags.InvisibleCrypto)) {
CollectStrategy.IdentityBasedStrategy
} else {
CollectStrategy.DeviceBasedStrategy(onlyAllowTrustedDevices = false, errorOnVerifiedUserProblem = false)
CollectStrategy.DeviceBasedStrategy(onlyAllowTrustedDevices = false, errorOnVerifiedUserProblem = true)
}
)
.run {

View file

@ -459,8 +459,8 @@ class RustMatrixRoom(
return liveTimeline.forwardEvent(eventId, roomIds)
}
override suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit> {
return Result.failure(UnsupportedOperationException("Not supported"))
override suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit> = runCatching {
innerRoom.tryResend(transactionId.value)
}
override suspend fun cancelSend(transactionId: TransactionId): Result<Boolean> {
@ -645,6 +645,22 @@ class RustMatrixRoom(
innerRoom.clearComposerDraft()
}
override suspend fun ignoreDeviceTrustAndResend(devices: Map<UserId, List<DeviceId>>, transactionId: TransactionId) = runCatching {
innerRoom.ignoreDeviceTrustAndResend(
devices = devices.entries.associate { entry ->
entry.key.value to entry.value.map { it.value }
},
transactionId = transactionId.value
)
}
override suspend fun withdrawVerificationAndResend(userIds: List<UserId>, transactionId: TransactionId) = runCatching {
innerRoom.withdrawVerificationAndResend(
userIds = userIds.map { it.value },
transactionId = transactionId.value
)
}
private fun createTimeline(
timeline: InnerTimeline,
mode: Timeline.Mode,

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.impl.timeline.item.event
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.core.UserId
@ -87,7 +88,9 @@ fun RustEventSendState?.map(): LocalEventSendState? {
}
is RustEventSendState.VerifiedUserHasUnsignedDevice -> {
LocalEventSendState.Failed.VerifiedUserHasUnsignedDevice(
devices = devices.mapKeys { UserId(it.key) }
devices = devices.entries.associate { entry ->
UserId(entry.key) to entry.value.map { DeviceId(it) }
}
)
}
EventSendState.CrossSigningNotSetup -> LocalEventSendState.Failed.CrossSigningNotSetup

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias
@ -79,7 +80,7 @@ class FakeMatrixRoom(
private var roomPermalinkResult: () -> Result<String> = { lambdaError() },
private var eventPermalinkResult: (EventId) -> Result<String> = { lambdaError() },
private val sendCallNotificationIfNeededResult: () -> Result<Unit> = { lambdaError() },
private val userDisplayNameResult: () -> Result<String?> = { lambdaError() },
private val userDisplayNameResult: (UserId) -> Result<String?> = { lambdaError() },
private val userAvatarUrlResult: () -> Result<String?> = { lambdaError() },
private val userRoleResult: () -> Result<RoomMember.Role> = { lambdaError() },
private val getUpdatedMemberResult: (UserId) -> Result<RoomMember> = { lambdaError() },
@ -134,7 +135,9 @@ class FakeMatrixRoom(
private val loadComposerDraftLambda: () -> Result<ComposerDraft?> = { Result.success<ComposerDraft?>(null) },
private val clearComposerDraftLambda: () -> Result<Unit> = { Result.success(Unit) },
private val subscribeToSyncLambda: () -> Unit = { lambdaError() },
) : MatrixRoom {
private val ignoreDeviceTrustAndResendResult: (Map<UserId, List<DeviceId>>, TransactionId) -> Result<Unit> = { _, _ -> lambdaError() },
private val withdrawVerificationAndResendResult: (List<UserId>, TransactionId) -> Result<Unit> = { _, _ -> lambdaError() },
) : MatrixRoom {
private val _roomInfoFlow: MutableSharedFlow<MatrixRoomInfo> = MutableSharedFlow(replay = 1)
override val roomInfoFlow: Flow<MatrixRoomInfo> = _roomInfoFlow
@ -199,7 +202,7 @@ class FakeMatrixRoom(
override fun destroy() = Unit
override suspend fun userDisplayName(userId: UserId): Result<String?> = simulateLongTask {
userDisplayNameResult()
userDisplayNameResult(userId)
}
override suspend fun userAvatarUrl(userId: UserId): Result<String?> = simulateLongTask {
@ -226,7 +229,7 @@ class FakeMatrixRoom(
return toggleReactionResult(emoji, uniqueId)
}
override suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit> {
override suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit> = simulateLongTask {
return retrySendMessageResult(transactionId)
}
@ -492,6 +495,14 @@ class FakeMatrixRoom(
return getWidgetDriverResult(widgetSettings)
}
override suspend fun ignoreDeviceTrustAndResend(devices: Map<UserId, List<DeviceId>>, transactionId: TransactionId): Result<Unit> = simulateLongTask {
return ignoreDeviceTrustAndResendResult(devices, transactionId)
}
override suspend fun withdrawVerificationAndResend(userIds: List<UserId>, transactionId: TransactionId): Result<Unit> = simulateLongTask {
return withdrawVerificationAndResendResult(userIds, transactionId)
}
fun givenRoomMembersState(state: MatrixRoomMembersState) {
membersStateFlow.value = state
}