From 674354b1a69257ad6e6f70d6d31cd0158df3c50e Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 29 Jul 2026 11:17:41 +0200 Subject: [PATCH] Enforce HS disabling E2EE in DM creation too (Element Pro) (#7292) This part was missing from the implementation of this Element Pro feature, which incorrectly only applied to rooms. --- features/startchat/impl/build.gradle.kts | 2 ++ .../startchat/impl/DefaultStartDMAction.kt | 8 ++++- .../impl/DefaultStartDMActionTest.kt | 31 +++++++++++++++++++ .../libraries/matrix/api/MatrixClient.kt | 2 +- .../libraries/matrix/api/room/StartDM.kt | 3 +- .../libraries/matrix/impl/RustMatrixClient.kt | 4 +-- .../matrix/impl/RustMatrixClientTest.kt | 2 +- .../libraries/matrix/test/FakeMatrixClient.kt | 8 ++--- 8 files changed, 50 insertions(+), 10 deletions(-) diff --git a/features/startchat/impl/build.gradle.kts b/features/startchat/impl/build.gradle.kts index 805dcc742b..fe7bfa9ae2 100644 --- a/features/startchat/impl/build.gradle.kts +++ b/features/startchat/impl/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(libs.coil.compose) implementation(projects.libraries.featureflag.api) implementation(projects.features.createroom.api) + implementation(projects.features.enterprise.api) api(projects.features.startchat.api) testCommonDependencies(libs, true) @@ -55,4 +56,5 @@ dependencies { testImplementation(projects.features.createroom.test) testImplementation(projects.features.startchat.test) testImplementation(projects.libraries.featureflag.test) + testImplementation(projects.features.enterprise.test) } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartDMAction.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartDMAction.kt index 6821005c67..0e6b3faf1c 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartDMAction.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartDMAction.kt @@ -11,6 +11,7 @@ package io.element.android.features.startchat.impl import androidx.compose.runtime.MutableState import dev.zacsweers.metro.ContributesBinding import im.vector.app.features.analytics.plan.CreatedRoom +import io.element.android.features.enterprise.api.SessionEnterpriseService import io.element.android.features.startchat.api.ConfirmingStartDmWithMatrixUser import io.element.android.features.startchat.api.StartDMAction import io.element.android.libraries.architecture.AsyncAction @@ -26,6 +27,7 @@ import io.element.android.services.analytics.api.AnalyticsService class DefaultStartDMAction( private val matrixClient: MatrixClient, private val analyticsService: AnalyticsService, + private val sessionEnterpriseService: SessionEnterpriseService, ) : StartDMAction { override suspend fun execute( matrixUser: MatrixUser, @@ -33,7 +35,11 @@ class DefaultStartDMAction( actionState: MutableState>, ) { actionState.value = AsyncAction.Loading - when (val result = matrixClient.startDM(matrixUser.userId, createIfDmDoesNotExist)) { + when (val result = matrixClient.startDM( + userId = matrixUser.userId, + createIfDmDoesNotExist = createIfDmDoesNotExist, + isEncryped = !sessionEnterpriseService.isEncryptionDisabledByHomeserver(), + )) { is StartDMResult.Success -> { if (result.isNew) { analyticsService.capture(CreatedRoom(isDM = true)) diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartDMActionTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartDMActionTest.kt index 2c1fd1aa2b..16b59f35df 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartDMActionTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartDMActionTest.kt @@ -11,6 +11,8 @@ package io.element.android.features.startchat.impl import androidx.compose.runtime.mutableStateOf import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.CreatedRoom +import io.element.android.features.enterprise.api.SessionEnterpriseService +import io.element.android.features.enterprise.test.FakeSessionEnterpriseService import io.element.android.features.startchat.api.ConfirmingStartDmWithMatrixUser import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.MatrixClient @@ -25,6 +27,7 @@ import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value import kotlinx.coroutines.test.runTest import org.junit.Test @@ -122,13 +125,41 @@ class DefaultStartDMActionTest { assertThat(state.value).isEqualTo(ConfirmingStartDmWithMatrixUser(aMatrixUser(), isUserIdentityUnknown = true)) } + @Test + fun `when SessionEnterpriseService disables encryption the DM is created without encryption`() = runTest { + val createDmLambda = lambdaRecorder> { _, _ -> Result.success(A_ROOM_ID) } + val matrixClient = FakeMatrixClient().apply { + givenFindDmResult(Result.success(null)) + createDmResult = createDmLambda + } + + val action = createStartDMAction( + matrixClient = matrixClient, + sessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { true }) + ) + val state = mutableStateOf>(AsyncAction.Uninitialized) + + val matrixUser = aMatrixUser() + action.execute(matrixUser = matrixUser, createIfDmDoesNotExist = true, actionState = state) + + createDmLambda.assertions().isCalledOnce().with( + value(matrixUser.userId), + // isEncrypted should be false because encryption is disabled + value(false), + ) + + assertThat(state.value).isEqualTo(AsyncAction.Success(A_ROOM_ID)) + } + private fun createStartDMAction( matrixClient: MatrixClient = FakeMatrixClient(), analyticsService: AnalyticsService = FakeAnalyticsService(), + sessionEnterpriseService: SessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { false }), ): DefaultStartDMAction { return DefaultStartDMAction( matrixClient = matrixClient, analyticsService = analyticsService, + sessionEnterpriseService = sessionEnterpriseService, ) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index f38b175b7e..0c26d87acb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -84,7 +84,7 @@ interface MatrixClient { suspend fun ignoreUser(userId: UserId): Result suspend fun unignoreUser(userId: UserId): Result suspend fun createRoom(createRoomParams: CreateRoomParameters): Result - suspend fun createDM(userId: UserId): Result + suspend fun createDM(userId: UserId, isEncrypted: Boolean): Result suspend fun getProfile(userId: UserId): Result suspend fun searchUsers(searchTerm: String, limit: Long): Result suspend fun setDisplayName(displayName: String): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt index 057c5838e5..59a6aaa2fe 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt @@ -18,6 +18,7 @@ import io.element.android.libraries.matrix.api.core.UserId suspend fun MatrixClient.startDM( userId: UserId, createIfDmDoesNotExist: Boolean, + isEncryped: Boolean, ): StartDMResult { return findDM(userId) .fold( @@ -25,7 +26,7 @@ suspend fun MatrixClient.startDM( if (existingDM != null) { StartDMResult.Success(existingDM, isNew = false) } else if (createIfDmDoesNotExist) { - createDM(userId).fold( + createDM(userId, isEncryped).fold( { StartDMResult.Success(it, isNew = true) }, { StartDMResult.Failure(it) } ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 0a59547631..33ed6610d3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -430,10 +430,10 @@ class RustMatrixClient( } } - override suspend fun createDM(userId: UserId): Result { + override suspend fun createDM(userId: UserId, isEncrypted: Boolean): Result { val createRoomParams = CreateRoomParameters( name = null, - isEncrypted = true, + isEncrypted = isEncrypted, isDirect = true, visibility = RoomVisibility.Private, preset = RoomPreset.TRUSTED_PRIVATE_CHAT, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt index 49cf59a954..0039f16ed9 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt @@ -132,7 +132,7 @@ class RustMatrixClientTest { client = FakeFfiClient(createRoomResult = createRoomLambda) ) - client.createDM(A_USER_ID) + client.createDM(userId = A_USER_ID, isEncrypted = true) createRoomLambda.assertions().isCalledOnce() assertThat(createParameters?.historyVisibilityOverride).isEqualTo(RoomHistoryVisibility.Invited) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 1c9fcfa482..0933239c93 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -145,7 +145,7 @@ class FakeMatrixClient( override val userProfile: StateFlow = _userProfile var createRoomResult: (CreateRoomParameters) -> Result = { Result.success(A_ROOM_ID) } - private var createDmResult: Result = Result.success(A_ROOM_ID) + var createDmResult: (UserId, Boolean) -> Result = { _, _ -> Result.success(A_ROOM_ID) } private var findDmResult: Result = Result.success(A_ROOM_ID) private val getRoomResults = mutableMapOf() private val searchUserResults = mutableMapOf>() @@ -196,8 +196,8 @@ class FakeMatrixClient( return createRoomResult(createRoomParams) } - override suspend fun createDM(userId: UserId): Result = simulateLongTask { - return createDmResult + override suspend fun createDM(userId: UserId, isEncrypted: Boolean): Result = simulateLongTask { + return createDmResult(userId, isEncrypted) } override suspend fun getProfile(userId: UserId): Result { @@ -301,7 +301,7 @@ class FakeMatrixClient( } fun givenCreateDmResult(result: Result) { - createDmResult = result + createDmResult = { _, _ -> result } } fun givenFindDmResult(result: Result) {