Add feature flag IdentityPinningViolationNotifications.
Disabled in production build, enabled by default on other builds.
This commit is contained in:
parent
e44b5ad98d
commit
88c617d13e
3 changed files with 68 additions and 3 deletions
|
|
@ -15,6 +15,8 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
|
@ -31,7 +33,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -40,6 +41,7 @@ import javax.inject.Inject
|
||||||
class IdentityChangeStatePresenter @Inject constructor(
|
class IdentityChangeStatePresenter @Inject constructor(
|
||||||
private val room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
private val encryptionService: EncryptionService,
|
private val encryptionService: EncryptionService,
|
||||||
|
private val featureFlagService: FeatureFlagService,
|
||||||
) : Presenter<IdentityChangeState> {
|
) : Presenter<IdentityChangeState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): IdentityChangeState {
|
override fun present(): IdentityChangeState {
|
||||||
|
|
@ -62,14 +64,18 @@ class IdentityChangeStatePresenter @Inject constructor(
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private fun ProduceStateScope<PersistentList<RoomMemberIdentityStateChange>>.observeRoomMemberIdentityStateChange() {
|
private fun ProduceStateScope<PersistentList<RoomMemberIdentityStateChange>>.observeRoomMemberIdentityStateChange() {
|
||||||
room.syncUpdateFlow
|
featureFlagService.isFeatureEnabledFlow(FeatureFlags.IdentityPinningViolationNotifications)
|
||||||
|
.filter { it }
|
||||||
|
.flatMapLatest {
|
||||||
|
room.syncUpdateFlow
|
||||||
|
}
|
||||||
.filter {
|
.filter {
|
||||||
// Room cannot become unencrypted, so we can just apply a filter here.
|
// Room cannot become unencrypted, so we can just apply a filter here.
|
||||||
room.isEncrypted
|
room.isEncrypted
|
||||||
}
|
}
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.flatMapLatest {
|
.flatMapLatest {
|
||||||
combine(room.identityStateChangesFlow, room.membersStateFlow,) { identityStateChanges, membersState ->
|
combine(room.identityStateChangesFlow, room.membersStateFlow) { identityStateChanges, membersState ->
|
||||||
identityStateChanges.map { identityStateChange ->
|
identityStateChanges.map { identityStateChange ->
|
||||||
val member = membersState.roomMembers()
|
val member = membersState.roomMembers()
|
||||||
?.firstOrNull { roomMember -> roomMember.userId == identityStateChange.userId }
|
?.firstOrNull { roomMember -> roomMember.userId == identityStateChange.userId }
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ package io.element.android.features.messages.impl.crypto.identity
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
|
|
@ -65,6 +68,43 @@ class IdentityChangeStatePresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - when the room emits identity change, but the feature is disabled, the presenter emits new state`() = runTest {
|
||||||
|
val room = FakeMatrixRoom(
|
||||||
|
isEncrypted = true,
|
||||||
|
)
|
||||||
|
val featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(
|
||||||
|
FeatureFlags.IdentityPinningViolationNotifications.key to false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val presenter = createIdentityChangeStatePresenter(
|
||||||
|
room = room,
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.roomMemberIdentityStateChanges).isEmpty()
|
||||||
|
room.emitIdentityStateChanges(
|
||||||
|
listOf(
|
||||||
|
IdentityStateChange(
|
||||||
|
userId = A_USER_ID_2,
|
||||||
|
identityState = IdentityState.PinViolation,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// No item emitted.
|
||||||
|
expectNoEvents()
|
||||||
|
// Enable the feature
|
||||||
|
featureFlagService.setFeatureEnabled(FeatureFlags.IdentityPinningViolationNotifications, true)
|
||||||
|
val finalItem = awaitItem()
|
||||||
|
assertThat(finalItem.roomMemberIdentityStateChanges).hasSize(1)
|
||||||
|
val value = finalItem.roomMemberIdentityStateChanges.first()
|
||||||
|
assertThat(value.identityRoomMember.userId).isEqualTo(A_USER_ID_2)
|
||||||
|
assertThat(value.identityState).isEqualTo(IdentityState.PinViolation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - when the clear room emits identity change, the presenter does not emits new state`() = runTest {
|
fun `present - when the clear room emits identity change, the presenter does not emits new state`() = runTest {
|
||||||
val room = FakeMatrixRoom(isEncrypted = false)
|
val room = FakeMatrixRoom(isEncrypted = false)
|
||||||
|
|
@ -147,10 +187,16 @@ class IdentityChangeStatePresenterTest {
|
||||||
private fun createIdentityChangeStatePresenter(
|
private fun createIdentityChangeStatePresenter(
|
||||||
room: MatrixRoom = FakeMatrixRoom(),
|
room: MatrixRoom = FakeMatrixRoom(),
|
||||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||||
|
featureFlagService: FeatureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(
|
||||||
|
FeatureFlags.IdentityPinningViolationNotifications.key to true,
|
||||||
|
)
|
||||||
|
),
|
||||||
): IdentityChangeStatePresenter {
|
): IdentityChangeStatePresenter {
|
||||||
return IdentityChangeStatePresenter(
|
return IdentityChangeStatePresenter(
|
||||||
room = room,
|
room = room,
|
||||||
encryptionService = encryptionService,
|
encryptionService = encryptionService,
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,4 +132,17 @@ enum class FeatureFlags(
|
||||||
defaultValue = { false },
|
defaultValue = { false },
|
||||||
isFinished = false,
|
isFinished = false,
|
||||||
),
|
),
|
||||||
|
IdentityPinningViolationNotifications(
|
||||||
|
key = "feature.identityPinningViolationNotifications",
|
||||||
|
title = "Identity pinning violation notifications",
|
||||||
|
description = null,
|
||||||
|
defaultValue = { buildMeta ->
|
||||||
|
when (buildMeta.buildType) {
|
||||||
|
// Do not enable this feature in release builds
|
||||||
|
BuildType.RELEASE -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isFinished = false,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue