Merge pull request #6726 from vector-im/feature/bca/block_unverified
Per room block unverified devices
This commit is contained in:
commit
ec7c8c8b31
|
@ -0,0 +1 @@
|
|||
Add option to only send to verified devices per room (web parity)
|
|
@ -1234,6 +1234,9 @@
|
|||
<string name="encryption_import_import">Import</string>
|
||||
<string name="encryption_never_send_to_unverified_devices_title">Encrypt to verified sessions only</string>
|
||||
<string name="encryption_never_send_to_unverified_devices_summary">Never send encrypted messages to unverified sessions from this session.</string>
|
||||
<string name="encryption_never_send_to_unverified_devices_in_room">Never send encrypted messages to unverified sessions in this room.</string>
|
||||
<string name="some_devices_will_not_be_able_to_decrypt">⚠ There are unverified devices in this room, they won’t be able to decrypt messages you send.</string>
|
||||
<string name="room_settings_global_block_unverified_info_text">🔒 You have enabled encrypt to verified sessions only for all rooms in Security Settings.</string>
|
||||
<plurals name="encryption_import_room_keys_success">
|
||||
<item quantity="one">%1$d/%2$d key imported with success.</item>
|
||||
<item quantity="other">%1$d/%2$d keys imported with success.</item>
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import androidx.test.filters.LargeTest
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
@LargeTest
|
||||
class E2eeConfigTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun testBlacklistUnverifiedDefault() = runCryptoTest(context()) { cryptoTestHelper, _ ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
|
||||
cryptoTestData.firstSession.cryptoService().getGlobalBlacklistUnverifiedDevices() shouldBe false
|
||||
cryptoTestData.firstSession.cryptoService().isRoomBlacklistUnverifiedDevices(cryptoTestData.roomId) shouldBe false
|
||||
cryptoTestData.secondSession!!.cryptoService().getGlobalBlacklistUnverifiedDevices() shouldBe false
|
||||
cryptoTestData.secondSession!!.cryptoService().isRoomBlacklistUnverifiedDevices(cryptoTestData.roomId) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCantDecryptIfGlobalUnverified() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
|
||||
cryptoTestData.firstSession.cryptoService().setGlobalBlacklistUnverifiedDevices(true)
|
||||
|
||||
val roomAlicePOV = cryptoTestData.firstSession.roomService().getRoom(cryptoTestData.roomId)!!
|
||||
|
||||
val sentMessage = testHelper.sendTextMessage(roomAlicePOV, "you are blocked", 1).first()
|
||||
|
||||
val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!!
|
||||
// ensure other received
|
||||
testHelper.retryPeriodically {
|
||||
roomBobPOV.timelineService().getTimelineEvent(sentMessage.eventId) != null
|
||||
}
|
||||
|
||||
cryptoTestHelper.ensureCannotDecrypt(listOf(sentMessage.eventId), cryptoTestData.secondSession!!, cryptoTestData.roomId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCanDecryptIfGlobalUnverifiedAndUserTrusted() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
|
||||
cryptoTestHelper.initializeCrossSigning(cryptoTestData.firstSession)
|
||||
cryptoTestHelper.initializeCrossSigning(cryptoTestData.secondSession!!)
|
||||
|
||||
cryptoTestHelper.verifySASCrossSign(cryptoTestData.firstSession, cryptoTestData.secondSession!!, cryptoTestData.roomId)
|
||||
|
||||
cryptoTestData.firstSession.cryptoService().setGlobalBlacklistUnverifiedDevices(true)
|
||||
|
||||
val roomAlicePOV = cryptoTestData.firstSession.roomService().getRoom(cryptoTestData.roomId)!!
|
||||
|
||||
val sentMessage = testHelper.sendTextMessage(roomAlicePOV, "you can read", 1).first()
|
||||
|
||||
val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!!
|
||||
// ensure other received
|
||||
testHelper.retryPeriodically {
|
||||
roomBobPOV.timelineService().getTimelineEvent(sentMessage.eventId) != null
|
||||
}
|
||||
|
||||
cryptoTestHelper.ensureCanDecrypt(
|
||||
listOf(sentMessage.eventId),
|
||||
cryptoTestData.secondSession!!,
|
||||
cryptoTestData.roomId,
|
||||
listOf(sentMessage.getLastMessageContent()!!.body)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCantDecryptIfPerRoomUnverified() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
|
||||
val roomAlicePOV = cryptoTestData.firstSession.roomService().getRoom(cryptoTestData.roomId)!!
|
||||
|
||||
val beforeMessage = testHelper.sendTextMessage(roomAlicePOV, "you can read", 1).first()
|
||||
|
||||
val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!!
|
||||
// ensure other received
|
||||
testHelper.retryPeriodically {
|
||||
roomBobPOV.timelineService().getTimelineEvent(beforeMessage.eventId) != null
|
||||
}
|
||||
|
||||
cryptoTestHelper.ensureCanDecrypt(
|
||||
listOf(beforeMessage.eventId),
|
||||
cryptoTestData.secondSession!!,
|
||||
cryptoTestData.roomId,
|
||||
listOf(beforeMessage.getLastMessageContent()!!.body)
|
||||
)
|
||||
|
||||
cryptoTestData.firstSession.cryptoService().setRoomBlockUnverifiedDevices(cryptoTestData.roomId, true)
|
||||
|
||||
val afterMessage = testHelper.sendTextMessage(roomAlicePOV, "you are blocked", 1).first()
|
||||
|
||||
// ensure received
|
||||
testHelper.retryPeriodically {
|
||||
cryptoTestData.secondSession?.getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(afterMessage.eventId)?.root != null
|
||||
}
|
||||
|
||||
cryptoTestHelper.ensureCannotDecrypt(
|
||||
listOf(afterMessage.eventId),
|
||||
cryptoTestData.secondSession!!,
|
||||
cryptoTestData.roomId,
|
||||
MXCryptoError.ErrorType.KEYS_WITHHELD
|
||||
)
|
||||
}
|
||||
}
|
|
@ -61,6 +61,8 @@ interface CryptoService {
|
|||
|
||||
fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean
|
||||
|
||||
fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean>
|
||||
|
||||
fun setWarnOnUnknownDevices(warn: Boolean)
|
||||
|
||||
fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String)
|
||||
|
@ -77,6 +79,8 @@ interface CryptoService {
|
|||
|
||||
fun setGlobalBlacklistUnverifiedDevices(block: Boolean)
|
||||
|
||||
fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig>
|
||||
|
||||
/**
|
||||
* Enable or disable key gossiping.
|
||||
* Default is true.
|
||||
|
@ -100,7 +104,7 @@ interface CryptoService {
|
|||
*/
|
||||
fun isShareKeysOnInviteEnabled(): Boolean
|
||||
|
||||
fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
|
||||
fun setRoomUnBlockUnverifiedDevices(roomId: String)
|
||||
|
||||
fun getDeviceTrackingStatus(userId: String): Int
|
||||
|
||||
|
@ -112,7 +116,7 @@ interface CryptoService {
|
|||
|
||||
suspend fun exportRoomKeys(password: String): ByteArray
|
||||
|
||||
fun setRoomBlacklistUnverifiedDevices(roomId: String)
|
||||
fun setRoomBlockUnverifiedDevices(roomId: String, block: Boolean)
|
||||
|
||||
fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.api.session.crypto
|
||||
|
||||
data class GlobalCryptoConfig(
|
||||
val globalBlockUnverifiedDevices: Boolean,
|
||||
val globalEnableKeyGossiping: Boolean,
|
||||
val enableKeyForwardingOnInvite: Boolean,
|
||||
)
|
|
@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.failure.Failure
|
|||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.crypto.NewSessionListener
|
||||
import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
|
||||
|
@ -1163,6 +1164,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
return cryptoStore.getGlobalBlacklistUnverifiedDevices()
|
||||
}
|
||||
|
||||
override fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig> {
|
||||
return cryptoStore.getLiveGlobalCryptoConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the client should encrypt messages only for the verified devices
|
||||
* in this room.
|
||||
|
@ -1171,39 +1176,28 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
* @param roomId the room id
|
||||
* @return true if the client should encrypt messages only for the verified devices.
|
||||
*/
|
||||
// TODO add this info in CryptoRoomEntity?
|
||||
override fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean {
|
||||
return roomId?.let { cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(it) }
|
||||
return roomId?.let { cryptoStore.getBlockUnverifiedDevices(roomId) }
|
||||
?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the room black-listing for unverified devices.
|
||||
* A live status regarding sharing keys for unverified devices in this room.
|
||||
*
|
||||
* @param roomId the room id
|
||||
* @param add true to add the room id to the list, false to remove it.
|
||||
* @return Live status
|
||||
*/
|
||||
private fun setRoomBlacklistUnverifiedDevices(roomId: String, add: Boolean) {
|
||||
val roomIds = cryptoStore.getRoomsListBlacklistUnverifiedDevices().toMutableList()
|
||||
|
||||
if (add) {
|
||||
if (roomId !in roomIds) {
|
||||
roomIds.add(roomId)
|
||||
}
|
||||
} else {
|
||||
roomIds.remove(roomId)
|
||||
}
|
||||
|
||||
cryptoStore.setRoomsListBlacklistUnverifiedDevices(roomIds)
|
||||
override fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean> {
|
||||
return cryptoStore.getLiveBlockUnverifiedDevices(roomId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this room to the ones which don't encrypt messages to unverified devices.
|
||||
*
|
||||
* @param roomId the room id
|
||||
* @param block if true will block sending keys to unverified devices
|
||||
*/
|
||||
override fun setRoomBlacklistUnverifiedDevices(roomId: String) {
|
||||
setRoomBlacklistUnverifiedDevices(roomId, true)
|
||||
override fun setRoomBlockUnverifiedDevices(roomId: String, block: Boolean) {
|
||||
cryptoStore.blockUnverifiedDevicesInRoom(roomId, block)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1211,8 +1205,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
*
|
||||
* @param roomId the room id
|
||||
*/
|
||||
override fun setRoomUnBlacklistUnverifiedDevices(roomId: String) {
|
||||
setRoomBlacklistUnverifiedDevices(roomId, false)
|
||||
override fun setRoomUnBlockUnverifiedDevices(roomId: String) {
|
||||
setRoomBlockUnverifiedDevices(roomId, false)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -424,7 +424,7 @@ internal class MXMegolmEncryption(
|
|||
// an m.new_device.
|
||||
val keys = deviceListManager.downloadKeys(userIds, false)
|
||||
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices() ||
|
||||
cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
|
||||
cryptoStore.getBlockUnverifiedDevices(roomId)
|
||||
|
||||
val devicesInRoom = DeviceInRoomInfo()
|
||||
val unknownDevices = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.store
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
|
||||
import org.matrix.android.sdk.api.session.crypto.NewSessionListener
|
||||
import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
|
||||
import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
|
||||
|
@ -120,11 +121,26 @@ internal interface IMXCryptoStore {
|
|||
fun getRoomsListBlacklistUnverifiedDevices(): List<String>
|
||||
|
||||
/**
|
||||
* Updates the rooms ids list in which the messages are not encrypted for the unverified devices.
|
||||
* A live status regarding sharing keys for unverified devices in this room.
|
||||
*
|
||||
* @param roomIds the room ids list
|
||||
* @return Live status
|
||||
*/
|
||||
fun setRoomsListBlacklistUnverifiedDevices(roomIds: List<String>)
|
||||
fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean>
|
||||
|
||||
/**
|
||||
* Tell if unverified devices should be blacklisted when sending keys.
|
||||
*
|
||||
* @return true if should not send keys to unverified devices
|
||||
*/
|
||||
fun getBlockUnverifiedDevices(roomId: String): Boolean
|
||||
|
||||
/**
|
||||
* Define if encryption keys should be sent to unverified devices in this room.
|
||||
*
|
||||
* @param roomId the roomId
|
||||
* @param block if true will not send keys to unverified devices
|
||||
*/
|
||||
fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean)
|
||||
|
||||
/**
|
||||
* Get the current keys backup version.
|
||||
|
@ -516,6 +532,9 @@ internal interface IMXCryptoStore {
|
|||
fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
|
||||
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
|
||||
|
||||
fun getGlobalCryptoConfig(): GlobalCryptoConfig
|
||||
fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig>
|
||||
|
||||
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
|
||||
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import io.realm.kotlin.where
|
|||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||
import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
|
||||
import org.matrix.android.sdk.api.session.crypto.NewSessionListener
|
||||
import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
|
||||
import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
|
||||
|
@ -445,6 +446,38 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getGlobalCryptoConfig(): GlobalCryptoConfig {
|
||||
return doWithRealm(realmConfiguration) { realm ->
|
||||
realm.where<CryptoMetadataEntity>().findFirst()
|
||||
?.let {
|
||||
GlobalCryptoConfig(
|
||||
globalBlockUnverifiedDevices = it.globalBlacklistUnverifiedDevices,
|
||||
globalEnableKeyGossiping = it.globalEnableKeyGossiping,
|
||||
enableKeyForwardingOnInvite = it.enableKeyForwardingOnInvite
|
||||
)
|
||||
} ?: GlobalCryptoConfig(false, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ realm: Realm ->
|
||||
realm
|
||||
.where<CryptoMetadataEntity>()
|
||||
},
|
||||
{
|
||||
GlobalCryptoConfig(
|
||||
globalBlockUnverifiedDevices = it.globalBlacklistUnverifiedDevices,
|
||||
globalEnableKeyGossiping = it.globalEnableKeyGossiping,
|
||||
enableKeyForwardingOnInvite = it.enableKeyForwardingOnInvite
|
||||
)
|
||||
}
|
||||
)
|
||||
return Transformations.map(liveData) {
|
||||
it.firstOrNull() ?: GlobalCryptoConfig(false, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) {
|
||||
Timber.v("## CRYPTO | *** storePrivateKeysInfo ${msk != null}, ${usk != null}, ${ssk != null}")
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
|
@ -1053,25 +1086,6 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
} ?: false
|
||||
}
|
||||
|
||||
override fun setRoomsListBlacklistUnverifiedDevices(roomIds: List<String>) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
// Reset all
|
||||
it.where<CryptoRoomEntity>()
|
||||
.findAll()
|
||||
.forEach { room ->
|
||||
room.blacklistUnverifiedDevices = false
|
||||
}
|
||||
|
||||
// Enable those in the list
|
||||
it.where<CryptoRoomEntity>()
|
||||
.`in`(CryptoRoomEntityFields.ROOM_ID, roomIds.toTypedArray())
|
||||
.findAll()
|
||||
.forEach { room ->
|
||||
room.blacklistUnverifiedDevices = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRoomsListBlacklistUnverifiedDevices(): List<String> {
|
||||
return doWithRealm(realmConfiguration) {
|
||||
it.where<CryptoRoomEntity>()
|
||||
|
@ -1083,6 +1097,37 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ realm: Realm ->
|
||||
realm.where<CryptoRoomEntity>()
|
||||
.equalTo(CryptoRoomEntityFields.ROOM_ID, roomId)
|
||||
},
|
||||
{
|
||||
it.blacklistUnverifiedDevices
|
||||
}
|
||||
)
|
||||
return Transformations.map(liveData) {
|
||||
it.firstOrNull() ?: false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBlockUnverifiedDevices(roomId: String): Boolean {
|
||||
return doWithRealm(realmConfiguration) { realm ->
|
||||
realm.where<CryptoRoomEntity>()
|
||||
.equalTo(CryptoRoomEntityFields.ROOM_ID, roomId)
|
||||
.findFirst()
|
||||
?.blacklistUnverifiedDevices ?: false
|
||||
}
|
||||
}
|
||||
|
||||
override fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
CryptoRoomEntity.getById(realm, roomId)
|
||||
?.blacklistUnverifiedDevices = block
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDeviceTrackingStatuses(): Map<String, Int> {
|
||||
return doWithRealm(realmConfiguration) {
|
||||
it.where<UserEntity>()
|
||||
|
|
|
@ -27,4 +27,5 @@ sealed class RoomProfileAction : VectorViewModelAction {
|
|||
object ShareRoomProfile : RoomProfileAction()
|
||||
object CreateShortcut : RoomProfileAction()
|
||||
object RestoreEncryptionState : RoomProfileAction()
|
||||
data class SetEncryptToVerifiedDeviceOnly(val enabled: Boolean) : RoomProfileAction()
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.app.core.resources.DrawableProvider
|
|||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.ui.list.genericFooterItem
|
||||
import im.vector.app.core.ui.list.genericPositiveButtonItem
|
||||
import im.vector.app.features.form.formSwitchItem
|
||||
import im.vector.app.features.home.ShortcutCreator
|
||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
|
||||
|
@ -66,6 +67,8 @@ class RoomProfileController @Inject constructor(
|
|||
fun onUrlInTopicLongClicked(url: String)
|
||||
fun doMigrateToVersion(newVersion: String)
|
||||
fun restoreEncryptionState()
|
||||
fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean)
|
||||
fun openGlobalBlockSettings()
|
||||
}
|
||||
|
||||
override fun buildModels(data: RoomProfileViewState?) {
|
||||
|
@ -175,6 +178,53 @@ class RoomProfileController @Inject constructor(
|
|||
}
|
||||
buildEncryptionAction(data.actionPermissions, roomSummary)
|
||||
|
||||
if (roomSummary.isEncrypted && !encryptionMisconfigured) {
|
||||
data.globalCryptoConfig.invoke()?.let { globalConfig ->
|
||||
if (globalConfig.globalBlockUnverifiedDevices) {
|
||||
genericFooterItem {
|
||||
id("globalConfig")
|
||||
centered(false)
|
||||
text(
|
||||
span {
|
||||
+host.stringProvider.getString(R.string.room_settings_global_block_unverified_info_text)
|
||||
apply {
|
||||
if (data.unverifiedDevicesInTheRoom.invoke() == true) {
|
||||
+"\n"
|
||||
+host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt)
|
||||
}
|
||||
}
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
itemClickAction {
|
||||
host.callback?.openGlobalBlockSettings()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// per room setting is available
|
||||
val shouldBlockUnverified = data.encryptToVerifiedDeviceOnly.invoke()
|
||||
formSwitchItem {
|
||||
id("send_to_unverified")
|
||||
enabled(shouldBlockUnverified != null)
|
||||
title(host.stringProvider.getString(R.string.encryption_never_send_to_unverified_devices_in_room))
|
||||
|
||||
switchChecked(shouldBlockUnverified ?: false)
|
||||
|
||||
apply {
|
||||
if (shouldBlockUnverified == true && data.unverifiedDevicesInTheRoom.invoke() == true) {
|
||||
summary(
|
||||
host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt)
|
||||
)
|
||||
} else {
|
||||
summary(null)
|
||||
}
|
||||
}
|
||||
listener { value ->
|
||||
host.callback?.setEncryptedToVerifiedDevicesOnly(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// More
|
||||
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
|
||||
buildProfileAction(
|
||||
|
|
|
@ -53,6 +53,7 @@ import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
|
|||
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||
import im.vector.app.features.navigation.SettingsActivityPayload
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
@ -346,6 +347,14 @@ class RoomProfileFragment :
|
|||
)
|
||||
}
|
||||
|
||||
override fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean) {
|
||||
roomProfileViewModel.handle(RoomProfileAction.SetEncryptToVerifiedDeviceOnly(enabled))
|
||||
}
|
||||
|
||||
override fun openGlobalBlockSettings() {
|
||||
navigator.openSettings(requireContext(), SettingsActivityPayload.SecurityPrivacy)
|
||||
}
|
||||
|
||||
private fun onAvatarClicked(view: View) = withState(roomProfileViewModel) { state ->
|
||||
state.roomSummary()?.toMatrixItem()?.let { matrixItem ->
|
||||
navigator.openBigImageViewer(requireActivity(), view, matrixItem)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package im.vector.app.features.roomprofile
|
||||
|
||||
import androidx.lifecycle.asFlow
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
@ -32,7 +33,11 @@ import im.vector.app.features.home.ShortcutCreator
|
|||
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
|
@ -76,6 +81,45 @@ class RoomProfileViewModel @AssistedInject constructor(
|
|||
observeBannedRoomMembers(flowRoom)
|
||||
observePermissions()
|
||||
observePowerLevels()
|
||||
observeCryptoSettings(flowRoom)
|
||||
}
|
||||
|
||||
private fun observeCryptoSettings(flowRoom: FlowRoom) {
|
||||
val perRoomBlockStatus = session.cryptoService().getLiveBlockUnverifiedDevices(initialState.roomId)
|
||||
.asFlow()
|
||||
|
||||
perRoomBlockStatus
|
||||
.execute {
|
||||
copy(encryptToVerifiedDeviceOnly = it)
|
||||
}
|
||||
|
||||
val globalBlockStatus = session.cryptoService().getLiveGlobalCryptoConfig()
|
||||
.asFlow()
|
||||
|
||||
globalBlockStatus
|
||||
.execute {
|
||||
copy(globalCryptoConfig = it)
|
||||
}
|
||||
|
||||
perRoomBlockStatus.combine(globalBlockStatus) { perRoom, global ->
|
||||
perRoom || global.globalBlockUnverifiedDevices
|
||||
}.flatMapLatest {
|
||||
if (it) {
|
||||
flowRoom.liveRoomMembers(roomMemberQueryParams { memberships = Membership.activeMemberships() })
|
||||
.map { it.map { it.userId } }
|
||||
.flatMapLatest {
|
||||
session.cryptoService().getLiveCryptoDeviceInfo(it).asFlow()
|
||||
}
|
||||
} else {
|
||||
flowOf(emptyList())
|
||||
}
|
||||
}.map {
|
||||
it.isNotEmpty()
|
||||
}.execute {
|
||||
copy(
|
||||
unverifiedDevicesInTheRoom = it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observePowerLevels() {
|
||||
|
@ -141,6 +185,7 @@ class RoomProfileViewModel @AssistedInject constructor(
|
|||
is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile()
|
||||
RoomProfileAction.CreateShortcut -> handleCreateShortcut()
|
||||
RoomProfileAction.RestoreEncryptionState -> restoreEncryptionState()
|
||||
is RoomProfileAction.SetEncryptToVerifiedDeviceOnly -> setEncryptToVerifiedDeviceOnly(action.enabled)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,6 +257,12 @@ class RoomProfileViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun setEncryptToVerifiedDeviceOnly(enabled: Boolean) {
|
||||
session.coroutineScope.launch {
|
||||
session.cryptoService().setRoomBlockUnverifiedDevices(room.roomId, enabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreEncryptionState() {
|
||||
_viewEvents.post(RoomProfileViewEvents.Loading())
|
||||
session.coroutineScope.launch {
|
||||
|
|
|
@ -20,6 +20,7 @@ package im.vector.app.features.roomprofile
|
|||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
|
@ -35,7 +36,10 @@ data class RoomProfileViewState(
|
|||
val recommendedRoomVersion: String? = null,
|
||||
val canUpgradeRoom: Boolean = false,
|
||||
val isTombstoned: Boolean = false,
|
||||
val canUpdateRoomState: Boolean = false
|
||||
val canUpdateRoomState: Boolean = false,
|
||||
val encryptToVerifiedDeviceOnly: Async<Boolean> = Uninitialized,
|
||||
val globalCryptoConfig: Async<GlobalCryptoConfig> = Uninitialized,
|
||||
val unverifiedDevicesInTheRoom: Async<Boolean> = Uninitialized,
|
||||
) : MavericksState {
|
||||
|
||||
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
||||
|
|
|
@ -45,7 +45,7 @@ data class RoomSettingsViewState(
|
|||
val showSaveAction: Boolean = false,
|
||||
val actionPermissions: ActionPermissions = ActionPermissions(),
|
||||
val supportsRestricted: Boolean = false,
|
||||
val canUpgradeToRestricted: Boolean = false
|
||||
val canUpgradeToRestricted: Boolean = false,
|
||||
) : MavericksState {
|
||||
|
||||
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
android:background="?android:colorBackground"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="@dimen/item_form_min_height"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp"
|
||||
tools:viewBindingIgnore="true">
|
||||
|
||||
<TextView
|
||||
|
|
Loading…
Reference in New Issue