Merge pull request #2999 from vector-im/feature/bma/shields

Shields optimization
This commit is contained in:
Benoit Marty 2021-03-16 13:31:56 +01:00 committed by GitHub
commit 4ad0eb3db2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 232 additions and 205 deletions

View file

@ -5,10 +5,10 @@ Features ✨:
- -
Improvements 🙌: Improvements 🙌:
- - Do not load room members in e2e after init sync
Bugfix 🐛: Bugfix 🐛:
- - Ensure message are decrypted in the room list after a clear cache
Translations 🗣: Translations 🗣:
- -

View file

@ -38,7 +38,7 @@ internal class CryptoSessionInfoProvider @Inject constructor(
val encryptionEvent = monarchy.fetchCopied { realm -> val encryptionEvent = monarchy.fetchCopied { realm ->
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
.isNotNull(EventEntityFields.STATE_KEY) // should be an empty key .isEmpty(EventEntityFields.STATE_KEY)
.findFirst() .findFirst()
} }
return encryptionEvent != null return encryptionEvent != null

View file

@ -856,15 +856,8 @@ internal class DefaultCryptoService @Inject constructor(
return return
} }
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val params = LoadRoomMembersTask.Params(roomId) val userIds = getRoomUserIds(roomId)
try { setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
loadRoomMembersTask.execute(params)
} catch (throwable: Throwable) {
Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
} finally {
val userIds = getRoomUserIds(roomId)
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
}
} }
} }

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
@ -28,7 +29,7 @@ import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.launch import org.matrix.android.sdk.internal.util.logLimit
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -39,8 +40,9 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
private val syncTokenStore: SyncTokenStore, private val syncTokenStore: SyncTokenStore,
private val credentials: Credentials, private val credentials: Credentials,
private val downloadKeysForUsersTask: DownloadKeysForUsersTask, private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
coroutineDispatchers: MatrixCoroutineDispatchers, coroutineDispatchers: MatrixCoroutineDispatchers,
taskExecutor: TaskExecutor) { private val taskExecutor: TaskExecutor) {
interface UserDevicesUpdateListener { interface UserDevicesUpdateListener {
fun onUsersDeviceUpdate(userIds: List<String>) fun onUsersDeviceUpdate(userIds: List<String>)
@ -75,8 +77,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
// HS not ready for retry // HS not ready for retry
private val notReadyToRetryHS = mutableSetOf<String>() private val notReadyToRetryHS = mutableSetOf<String>()
private val cryptoCoroutineContext = coroutineDispatchers.crypto
init { init {
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) { taskExecutor.executorScope.launch(cryptoCoroutineContext) {
var isUpdated = false var isUpdated = false
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
for ((userId, status) in deviceTrackingStatuses) { for ((userId, status) in deviceTrackingStatuses) {
@ -123,28 +127,37 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
} }
} }
fun onRoomMembersLoadedFor(roomId: String) {
taskExecutor.executorScope.launch(cryptoCoroutineContext) {
if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) {
// It's OK to track also device for invited users
val userIds = cryptoSessionInfoProvider.getRoomUserIds(roomId, true)
startTrackingDeviceList(userIds)
refreshOutdatedDeviceLists()
}
}
}
/** /**
* Mark the cached device list for the given user outdated * Mark the cached device list for the given user outdated
* flag the given user for device-list tracking, if they are not already. * flag the given user for device-list tracking, if they are not already.
* *
* @param userIds the user ids list * @param userIds the user ids list
*/ */
fun startTrackingDeviceList(userIds: List<String>?) { fun startTrackingDeviceList(userIds: List<String>) {
if (null != userIds) { var isUpdated = false
var isUpdated = false val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
for (userId in userIds) { for (userId in userIds) {
if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) { if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) {
Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId") Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId")
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
isUpdated = true isUpdated = true
}
} }
}
if (isUpdated) { if (isUpdated) {
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
}
} }
} }
@ -307,7 +320,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* @param downloadUsers the user ids list * @param downloadUsers the user ids list
*/ */
private suspend fun doKeyDownloadForUsers(downloadUsers: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> { private suspend fun doKeyDownloadForUsers(downloadUsers: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers") Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers ${downloadUsers.logLimit()}")
// get the user ids which did not already trigger a keys download // get the user ids which did not already trigger a keys download
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) } val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
if (filteredUsers.isEmpty()) { if (filteredUsers.isEmpty()) {

View file

@ -312,7 +312,7 @@ internal class MXOlmDevice @Inject constructor(
* @param theirDeviceIdentityKey the Curve25519 identity key for the remote device. * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device.
* @return a list of known session ids for the device. * @return a list of known session ids for the device.
*/ */
fun getSessionIds(theirDeviceIdentityKey: String): Set<String>? { fun getSessionIds(theirDeviceIdentityKey: String): List<String>? {
return store.getDeviceSessionIds(theirDeviceIdentityKey) return store.getDeviceSessionIds(theirDeviceIdentityKey)
} }

View file

@ -154,7 +154,7 @@ internal class MXOlmDecryption(
* @return payload, if decrypted successfully. * @return payload, if decrypted successfully.
*/ */
private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? { private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) ?: emptySet() val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey).orEmpty()
val messageBody = message["body"] as? String ?: return null val messageBody = message["body"] as? String ?: return null
val messageType = when (val typeAsVoid = message["type"]) { val messageType = when (val typeAsVoid = message["type"]) {

View file

@ -33,15 +33,18 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.database.awaitTransaction
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
import org.matrix.android.sdk.internal.util.logLimit
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import timber.log.Timber import timber.log.Timber
@ -65,11 +68,16 @@ internal class UpdateTrustWorker(context: Context,
@Inject lateinit var crossSigningService: DefaultCrossSigningService @Inject lateinit var crossSigningService: DefaultCrossSigningService
// It breaks the crypto store contract, but we need to batch things :/ // It breaks the crypto store contract, but we need to batch things :/
@CryptoDatabase @Inject lateinit var realmConfiguration: RealmConfiguration @CryptoDatabase
@UserId @Inject lateinit var myUserId: String @Inject lateinit var cryptoRealmConfiguration: RealmConfiguration
@SessionDatabase
@Inject lateinit var sessionRealmConfiguration: RealmConfiguration
@UserId
@Inject lateinit var myUserId: String
@Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper @Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper
@Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository @Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository
@SessionDatabase @Inject lateinit var sessionRealmConfiguration: RealmConfiguration
// @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater // @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater
@Inject lateinit var cryptoStore: IMXCryptoStore @Inject lateinit var cryptoStore: IMXCryptoStore
@ -79,118 +87,114 @@ internal class UpdateTrustWorker(context: Context,
} }
override suspend fun doSafeWork(params: Params): Result { override suspend fun doSafeWork(params: Params): Result {
var userList = params.filename val userList = params.filename
?.let { updateTrustWorkerDataRepository.getParam(it) } ?.let { updateTrustWorkerDataRepository.getParam(it) }
?.userIds ?.userIds
?: params.updatedUserIds.orEmpty() ?: params.updatedUserIds.orEmpty()
if (userList.isEmpty()) { // List should not be empty, but let's avoid go further in case of empty list
// This should not happen, but let's avoid go further in case of empty list if (userList.isNotEmpty()) {
cleanup(params) // Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user,
return Result.success() // or a new device?) So we check all again :/
Timber.d("## CrossSigning - Updating trust for users: ${userList.logLimit()}")
Realm.getInstance(cryptoRealmConfiguration).use { cryptoRealm ->
Realm.getInstance(sessionRealmConfiguration).use {
updateTrust(userList, cryptoRealm)
}
}
} }
// Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user, cleanup(params)
// or a new device?) So we check all again :/ return Result.success()
}
Timber.d("## CrossSigning - Updating trust for $userList")
private suspend fun updateTrust(userListParam: List<String>,
cRealm: Realm) {
var userList = userListParam
var myCrossSigningInfo: MXCrossSigningInfo? = null
// First we check that the users MSK are trusted by mine // First we check that the users MSK are trusted by mine
// After that we check the trust chain for each devices of each users // After that we check the trust chain for each devices of each users
Realm.getInstance(realmConfiguration).use { realm -> awaitTransaction(cryptoRealmConfiguration) { cryptoRealm ->
realm.executeTransaction { // By mapping here to model, this object is not live
// By mapping here to model, this object is not live // I should update it if needed
// I should update it if needed myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId)
var myCrossSigningInfo = realm.where(CrossSigningInfoEntity::class.java)
.equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId)
.findFirst()?.let { mapCrossSigningInfoEntity(it) }
var myTrustResult: UserTrustResult? = null var myTrustResult: UserTrustResult? = null
if (userList.contains(myUserId)) { if (userList.contains(myUserId)) {
Timber.d("## CrossSigning - Clear all trust as a change on my user was detected") Timber.d("## CrossSigning - Clear all trust as a change on my user was detected")
// i am in the list.. but i don't know exactly the delta of change :/ // i am in the list.. but i don't know exactly the delta of change :/
// If it's my cross signing keys we should refresh all trust // If it's my cross signing keys we should refresh all trust
// do it anyway ? // do it anyway ?
userList = realm.where(CrossSigningInfoEntity::class.java) userList = cryptoRealm.where(CrossSigningInfoEntity::class.java)
.findAll().mapNotNull { it.userId } .findAll()
Timber.d("## CrossSigning - Updating trust for all $userList") .mapNotNull { it.userId }
// check right now my keys and mark it as trusted as other trust depends on it // check right now my keys and mark it as trusted as other trust depends on it
val myDevices = realm.where<UserEntity>() val myDevices = cryptoRealm.where<UserEntity>()
.equalTo(UserEntityFields.USER_ID, myUserId) .equalTo(UserEntityFields.USER_ID, myUserId)
.findFirst() .findFirst()
?.devices ?.devices
?.map { deviceInfo -> ?.map { CryptoMapper.mapToModel(it) }
CryptoMapper.mapToModel(deviceInfo)
}
myTrustResult = crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices).also {
updateCrossSigningKeysTrust(realm, myUserId, it.isVerified())
// update model reference
myCrossSigningInfo = realm.where(CrossSigningInfoEntity::class.java)
.equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId)
.findFirst()?.let { mapCrossSigningInfoEntity(it) }
}
}
val otherInfos = userList.map { myTrustResult = crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices)
it to realm.where(CrossSigningInfoEntity::class.java) updateCrossSigningKeysTrust(cryptoRealm, myUserId, myTrustResult.isVerified())
.equalTo(CrossSigningInfoEntityFields.USER_ID, it) // update model reference
.findFirst()?.let { mapCrossSigningInfoEntity(it) } myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId)
} }
.toMap()
val trusts = otherInfos.map { infoEntry -> val otherInfos = userList.associateWith { userId ->
infoEntry.key to when (infoEntry.key) { getCrossSigningInfo(cryptoRealm, userId)
myUserId -> myTrustResult }
else -> {
crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, infoEntry.value).also { val trusts = otherInfos.mapValues { entry ->
Timber.d("## CrossSigning - user:${infoEntry.key} result:$it") when (entry.key) {
} myUserId -> myTrustResult
else -> {
crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, entry.value).also {
Timber.d("## CrossSigning - user:${entry.key} result:$it")
} }
} }
}.toMap() }
}
// TODO! if it's me and my keys has changed... I have to reset trust for everyone! // TODO! if it's me and my keys has changed... I have to reset trust for everyone!
// i have all the new trusts, update DB // i have all the new trusts, update DB
trusts.forEach { trusts.forEach {
val verified = it.value?.isVerified() == true val verified = it.value?.isVerified() == true
updateCrossSigningKeysTrust(realm, it.key, verified) updateCrossSigningKeysTrust(cryptoRealm, it.key, verified)
}
// Ok so now we have to check device trust for all these users..
Timber.v("## CrossSigning - Updating devices cross trust users: ${trusts.keys.logLimit()}")
trusts.keys.forEach { userId ->
val devicesEntities = cryptoRealm.where<UserEntity>()
.equalTo(UserEntityFields.USER_ID, userId)
.findFirst()
?.devices
val trustMap = devicesEntities?.associateWith { device ->
// get up to date from DB has could have been updated
val otherInfo = getCrossSigningInfo(cryptoRealm, userId)
crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device))
} }
// Ok so now we have to check device trust for all these users.. // Update trust if needed
Timber.v("## CrossSigning - Updating devices cross trust users ${trusts.keys}") devicesEntities?.forEach { device ->
trusts.keys.forEach { val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified()
val devicesEntities = realm.where<UserEntity>() Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}")
.equalTo(UserEntityFields.USER_ID, it) if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) {
.findFirst() Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified")
?.devices // need to save
val trustEntity = device.trustLevelEntity
val trustMap = devicesEntities?.map { device -> if (trustEntity == null) {
// get up to date from DB has could have been updated device.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also {
val otherInfo = realm.where(CrossSigningInfoEntity::class.java) it.locallyVerified = false
.equalTo(CrossSigningInfoEntityFields.USER_ID, it) it.crossSignedVerified = crossSignedVerified
.findFirst()?.let { mapCrossSigningInfoEntity(it) }
device to crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device))
}?.toMap()
// Update trust if needed
devicesEntities?.forEach { device ->
val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified()
Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}")
if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) {
Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified")
// need to save
val trustEntity = device.trustLevelEntity
if (trustEntity == null) {
realm.createObject(TrustLevelEntity::class.java).let {
it.locallyVerified = false
it.crossSignedVerified = crossSignedVerified
device.trustLevelEntity = it
}
} else {
trustEntity.crossSignedVerified = crossSignedVerified
} }
} else {
trustEntity.crossSignedVerified = crossSignedVerified
} }
} }
} }
@ -201,35 +205,44 @@ internal class UpdateTrustWorker(context: Context,
// We can now update room shields? in the session DB? // We can now update room shields? in the session DB?
Timber.d("## CrossSigning - Updating shields for impacted rooms...") Timber.d("## CrossSigning - Updating shields for impacted rooms...")
Realm.getInstance(sessionRealmConfiguration).use { it -> awaitTransaction(sessionRealmConfiguration) { sessionRealm ->
it.executeTransaction { realm -> sessionRealm.where(RoomMemberSummaryEntity::class.java)
val distinctRoomIds = realm.where(RoomMemberSummaryEntity::class.java) .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray())
.`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) .distinct(RoomMemberSummaryEntityFields.ROOM_ID)
.distinct(RoomMemberSummaryEntityFields.ROOM_ID) .findAll()
.findAll() .map { it.roomId }
.map { it.roomId } .also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") }
Timber.d("## CrossSigning - ... impacted rooms $distinctRoomIds") .forEach { roomId ->
distinctRoomIds.forEach { roomId -> RoomSummaryEntity.where(sessionRealm, roomId)
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() .equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true)
if (roomSummary?.isEncrypted == true) { .findFirst()
Timber.d("## CrossSigning - Check shield state for room $roomId") ?.let { roomSummary ->
val allActiveRoomMembers = RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() Timber.d("## CrossSigning - Check shield state for room $roomId")
try { val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds()
val updatedTrust = computeRoomShield(allActiveRoomMembers, roomSummary) try {
if (roomSummary.roomEncryptionTrustLevel != updatedTrust) { val updatedTrust = computeRoomShield(
Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust") myCrossSigningInfo,
roomSummary.roomEncryptionTrustLevel = updatedTrust cRealm,
} allActiveRoomMembers,
} catch (failure: Throwable) { roomSummary
Timber.e(failure) )
} if (roomSummary.roomEncryptionTrustLevel != updatedTrust) {
Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust")
roomSummary.roomEncryptionTrustLevel = updatedTrust
}
} catch (failure: Throwable) {
Timber.e(failure)
}
}
} }
}
}
} }
}
cleanup(params) private fun getCrossSigningInfo(cryptoRealm: Realm, userId: String): MXCrossSigningInfo? {
return Result.success() return cryptoRealm.where(CrossSigningInfoEntity::class.java)
.equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
.findFirst()
?.let { mapCrossSigningInfoEntity(it) }
} }
private fun cleanup(params: Params) { private fun cleanup(params: Params) {
@ -237,30 +250,34 @@ internal class UpdateTrustWorker(context: Context,
?.let { updateTrustWorkerDataRepository.delete(it) } ?.let { updateTrustWorkerDataRepository.delete(it) }
} }
private fun updateCrossSigningKeysTrust(realm: Realm, userId: String, verified: Boolean) { private fun updateCrossSigningKeysTrust(cryptoRealm: Realm, userId: String, verified: Boolean) {
val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java) cryptoRealm.where(CrossSigningInfoEntity::class.java)
.equalTo(CrossSigningInfoEntityFields.USER_ID, userId) .equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
.findFirst() .findFirst()
xInfoEntity?.crossSigningKeys?.forEach { info -> ?.crossSigningKeys
// optimization to avoid trigger updates when there is no change.. ?.forEach { info ->
if (info.trustLevelEntity?.isVerified() != verified) { // optimization to avoid trigger updates when there is no change..
Timber.d("## CrossSigning - Trust change for $userId : $verified") if (info.trustLevelEntity?.isVerified() != verified) {
val level = info.trustLevelEntity Timber.d("## CrossSigning - Trust change for $userId : $verified")
if (level == null) { val level = info.trustLevelEntity
val newLevel = realm.createObject(TrustLevelEntity::class.java) if (level == null) {
newLevel.locallyVerified = verified info.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also {
newLevel.crossSignedVerified = verified it.locallyVerified = verified
info.trustLevelEntity = newLevel it.crossSignedVerified = verified
} else { }
level.locallyVerified = verified } else {
level.crossSignedVerified = verified level.locallyVerified = verified
level.crossSignedVerified = verified
}
}
} }
}
}
} }
private fun computeRoomShield(activeMemberUserIds: List<String>, roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { private fun computeRoomShield(myCrossSigningInfo: MXCrossSigningInfo?,
Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> $activeMemberUserIds") cryptoRealm: Realm,
activeMemberUserIds: List<String>,
roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel {
Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> ${activeMemberUserIds.logLimit()}")
// The set of “all users” depends on the type of room: // The set of “all users” depends on the type of room:
// For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room // For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room
// For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room
@ -272,17 +289,8 @@ internal class UpdateTrustWorker(context: Context,
val allTrustedUserIds = listToCheck val allTrustedUserIds = listToCheck
.filter { userId -> .filter { userId ->
Realm.getInstance(realmConfiguration).use { getCrossSigningInfo(cryptoRealm, userId)?.isTrusted() == true
it.where(CrossSigningInfoEntity::class.java)
.equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
.findFirst()?.let { mapCrossSigningInfoEntity(it) }?.isTrusted() == true
}
} }
val myCrossKeys = Realm.getInstance(realmConfiguration).use {
it.where(CrossSigningInfoEntity::class.java)
.equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId)
.findFirst()?.let { mapCrossSigningInfoEntity(it) }
}
return if (allTrustedUserIds.isEmpty()) { return if (allTrustedUserIds.isEmpty()) {
RoomEncryptionTrustLevel.Default RoomEncryptionTrustLevel.Default
@ -291,21 +299,17 @@ internal class UpdateTrustWorker(context: Context,
// If all devices of all verified users are trusted -> green // If all devices of all verified users are trusted -> green
// else -> black // else -> black
allTrustedUserIds allTrustedUserIds
.mapNotNull { uid -> .mapNotNull { userId ->
Realm.getInstance(realmConfiguration).use { cryptoRealm.where<UserEntity>()
it.where<UserEntity>() .equalTo(UserEntityFields.USER_ID, userId)
.equalTo(UserEntityFields.USER_ID, uid) .findFirst()
.findFirst() ?.devices
?.devices ?.map { CryptoMapper.mapToModel(it) }
?.map {
CryptoMapper.mapToModel(it)
}
}
} }
.flatten() .flatten()
.let { allDevices -> .let { allDevices ->
Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { it.deviceId }}") Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { it.deviceId }.logLimit()}")
if (myCrossKeys != null) { if (myCrossSigningInfo != null) {
allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() } allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() }
} else { } else {
// Legacy method // Legacy method

View file

@ -259,7 +259,7 @@ internal interface IMXCryptoStore {
* @param deviceKey the public key of the other device. * @param deviceKey the public key of the other device.
* @return A set of sessionId, or null if device is not known * @return A set of sessionId, or null if device is not known
*/ */
fun getDeviceSessionIds(deviceKey: String): Set<String>? fun getDeviceSessionIds(deviceKey: String): List<String>?
/** /**
* Retrieve an end-to-end session between the logged-in user and another * Retrieve an end-to-end session between the logged-in user and another

View file

@ -692,7 +692,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
} }
override fun getDeviceSessionIds(deviceKey: String): MutableSet<String> { override fun getDeviceSessionIds(deviceKey: String): List<String> {
return doWithRealm(realmConfiguration) { return doWithRealm(realmConfiguration) {
it.where<OlmSessionEntity>() it.where<OlmSessionEntity>()
.equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey) .equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey)
@ -701,7 +701,6 @@ internal class RealmCryptoStore @Inject constructor(
sessionEntity.sessionId sessionEntity.sessionId
} }
} }
.toMutableSet()
} }
override fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>) { override fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>) {
@ -801,7 +800,7 @@ internal class RealmCryptoStore @Inject constructor(
* Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2, * Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2,
* so there is no need to use or update `inboundGroupSessionToRelease` for native memory management * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management
*/ */
override fun getInboundGroupSessions(): MutableList<OlmInboundGroupSessionWrapper2> { override fun getInboundGroupSessions(): List<OlmInboundGroupSessionWrapper2> {
return doWithRealm(realmConfiguration) { return doWithRealm(realmConfiguration) {
it.where<OlmInboundGroupSessionEntity>() it.where<OlmInboundGroupSessionEntity>()
.findAll() .findAll()
@ -809,7 +808,6 @@ internal class RealmCryptoStore @Inject constructor(
inboundGroupSessionEntity.getInboundGroupSession() inboundGroupSessionEntity.getInboundGroupSession()
} }
} }
.toMutableList()
} }
override fun removeInboundGroupSession(sessionId: String, senderKey: String) { override fun removeInboundGroupSession(sessionId: String, senderKey: String) {
@ -964,7 +962,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
} }
override fun getRoomsListBlacklistUnverifiedDevices(): MutableList<String> { override fun getRoomsListBlacklistUnverifiedDevices(): List<String> {
return doWithRealm(realmConfiguration) { return doWithRealm(realmConfiguration) {
it.where<CryptoRoomEntity>() it.where<CryptoRoomEntity>()
.equalTo(CryptoRoomEntityFields.BLACKLIST_UNVERIFIED_DEVICES, true) .equalTo(CryptoRoomEntityFields.BLACKLIST_UNVERIFIED_DEVICES, true)
@ -973,10 +971,9 @@ internal class RealmCryptoStore @Inject constructor(
cryptoRoom.roomId cryptoRoom.roomId
} }
} }
.toMutableList()
} }
override fun getDeviceTrackingStatuses(): MutableMap<String, Int> { override fun getDeviceTrackingStatuses(): Map<String, Int> {
return doWithRealm(realmConfiguration) { return doWithRealm(realmConfiguration) {
it.where<UserEntity>() it.where<UserEntity>()
.findAll() .findAll()
@ -987,7 +984,6 @@ internal class RealmCryptoStore @Inject constructor(
entry.value.deviceTrackingStatus entry.value.deviceTrackingStatus
} }
} }
.toMutableMap()
} }
override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map<String, Int>) { override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map<String, Int>) {

View file

@ -22,6 +22,8 @@ import io.realm.kotlin.createObject
import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.TimeoutCancellationException
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
import org.matrix.android.sdk.internal.crypto.DeviceListManager
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.mapper.toEntity
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
@ -57,6 +59,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
private val syncTokenStore: SyncTokenStore, private val syncTokenStore: SyncTokenStore,
private val roomSummaryUpdater: RoomSummaryUpdater, private val roomSummaryUpdater: RoomSummaryUpdater,
private val roomMemberEventHandler: RoomMemberEventHandler, private val roomMemberEventHandler: RoomMemberEventHandler,
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
private val deviceListManager: DeviceListManager,
private val globalErrorReceiver: GlobalErrorReceiver private val globalErrorReceiver: GlobalErrorReceiver
) : LoadRoomMembersTask { ) : LoadRoomMembersTask {
@ -124,6 +128,10 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED
roomSummaryUpdater.update(realm, roomId, updateMembers = true) roomSummaryUpdater.update(realm, roomId, updateMembers = true)
} }
if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) {
deviceListManager.onRoomMembersLoadedFor(roomId)
}
} }
private fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType { private fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType {

View file

@ -131,8 +131,8 @@ internal class RoomSummaryUpdater @Inject constructor(
// mmm i want to decrypt now or is it ok to do it async? // mmm i want to decrypt now or is it ok to do it async?
tryOrNull { tryOrNull {
eventDecryptor.decryptEvent(root.asDomain(), "") eventDecryptor.decryptEvent(root.asDomain(), "")
// eventDecryptor.decryptEventAsync(root.asDomain(), "", NoOpMatrixCallback())
} }
?.let { root.setDecryptionResult(it) }
} }
if (updateMembers) { if (updateMembers) {
@ -144,7 +144,7 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.clear()
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers) roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
if (roomSummaryEntity.isEncrypted) { if (roomSummaryEntity.isEncrypted && otherRoomMembers.isNotEmpty()) {
// mmm maybe we could only refresh shield instead of checking trust also? // mmm maybe we could only refresh shield instead of checking trust also?
crossSigningService.onUsersDeviceUpdate(otherRoomMembers) crossSigningService.onUsersDeviceUpdate(otherRoomMembers)
} }

View file

@ -19,6 +19,18 @@ package org.matrix.android.sdk.internal.util
import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.BuildConfig
import timber.log.Timber import timber.log.Timber
internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
return buildString {
append(size)
append(" item(s)")
if (size > maxQuantity) {
append(", first $maxQuantity items")
}
append(": ")
append(this@logLimit.take(maxQuantity))
}
}
internal suspend fun <T> logDuration(message: String, internal suspend fun <T> logDuration(message: String,
block: suspend () -> T): T { block: suspend () -> T): T {
Timber.v("$message -- BEGIN") Timber.v("$message -- BEGIN")

View file

@ -92,7 +92,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
return RoomSummaryItem_() return RoomSummaryItem_()
.id(roomSummary.roomId) .id(roomSummary.roomId)
.avatarRenderer(avatarRenderer) .avatarRenderer(avatarRenderer)
.encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel) // We do not display shield in the room list anymore
// .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel)
.matrixItem(roomSummary.toMatrixItem()) .matrixItem(roomSummary.toMatrixItem())
.lastEventTime(latestEventTime) .lastEventTime(latestEventTime)
.typingMessage(typingMessage) .typingMessage(typingMessage)