Merge pull request #3757 from vector-im/feature/bma/trustLevelEntity

Cleanup TrustLevelEntity
This commit is contained in:
Benoit Marty 2021-07-30 11:01:01 +02:00 committed by GitHub
commit 25f25b0e95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 109 additions and 40 deletions

View File

@ -18,8 +18,6 @@ package org.matrix.android.sdk.internal.crypto.model
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity
data class CryptoDeviceInfo(
val deviceId: String,
@ -77,7 +75,3 @@ data class CryptoDeviceInfo(
internal fun CryptoDeviceInfo.toRest(): DeviceKeys {
return CryptoInfoMapper.map(this)
}
internal fun CryptoDeviceInfo.toEntity(): DeviceInfoEntity {
return CryptoMapper.mapToEntity(this)
}

View File

@ -51,7 +51,6 @@ import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
import org.matrix.android.sdk.internal.crypto.model.toEntity
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
@ -280,24 +279,34 @@ internal class RealmCryptoStore @Inject constructor(
override fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) {
doRealmTransaction(realmConfiguration) { realm ->
if (devices == null) {
Timber.d("Remove user $userId")
// Remove the user
UserEntity.delete(realm, userId)
} else {
UserEntity.getOrCreate(realm, userId)
.let { u ->
// Add the devices
val currentKnownDevices = u.devices.toList()
val new = devices.map { entry -> entry.value.toEntity() }
new.forEach { entity ->
// Maintain first time seen
val existing = currentKnownDevices.firstOrNull { it.deviceId == entity.deviceId && it.identityKey == entity.identityKey }
entity.firstTimeSeenLocalTs = existing?.firstTimeSeenLocalTs ?: System.currentTimeMillis()
realm.insertOrUpdate(entity)
}
// Ensure all other devices are deleted
u.devices.clearWith { it.deleteOnCascade() }
u.devices.addAll(new)
}
val userEntity = UserEntity.getOrCreate(realm, userId)
// First delete the removed devices
val deviceIds = devices.keys
userEntity.devices.iterator().forEach { deviceInfoEntity ->
if (deviceInfoEntity.deviceId !in deviceIds) {
Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId")
deviceInfoEntity.deleteOnCascade()
}
}
// Then update existing devices or add new one
devices.values.forEach { cryptoDeviceInfo ->
val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId }
if (existingDeviceInfoEntity == null) {
// Add the device
Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId")
val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo)
newEntity.firstTimeSeenLocalTs = System.currentTimeMillis()
userEntity.devices.add(newEntity)
} else {
// Update the device
Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId")
CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo)
}
}
}
}
}

View File

@ -55,7 +55,7 @@ internal object RealmCryptoStoreMigration : RealmMigration {
// 0, 1, 2: legacy Riot-Android
// 3: migrate to RiotX schema
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
const val CRYPTO_STORE_SCHEMA_VERSION = 12L
const val CRYPTO_STORE_SCHEMA_VERSION = 13L
private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
if (!hasField(fieldName)) {
@ -93,6 +93,7 @@ internal object RealmCryptoStoreMigration : RealmMigration {
if (oldVersion <= 9) migrateTo10(realm)
if (oldVersion <= 10) migrateTo11(realm)
if (oldVersion <= 11) migrateTo12(realm)
if (oldVersion <= 12) migrateTo13(realm)
}
private fun migrateTo1Legacy(realm: DynamicRealm) {
@ -497,4 +498,60 @@ internal object RealmCryptoStoreMigration : RealmMigration {
realm.schema.get("CryptoRoomEntity")
?.addRealmObjectField(CryptoRoomEntityFields.OUTBOUND_SESSION_INFO.`$`, outboundEntitySchema)
}
// Version 13L delete unreferenced TrustLevelEntity
private fun migrateTo13(realm: DynamicRealm) {
Timber.d("Step 12 -> 13")
// Use a trick to do that... Ref: https://stackoverflow.com/questions/55221366
val trustLevelEntitySchema = realm.schema.get("TrustLevelEntity")
/*
Creating a new temp field called isLinked which is set to true for those which are
references by other objects. Rest of them are set to false. Then removing all
those which are false and hence duplicate and unnecessary. Then removing the temp field
isLinked
*/
var mainCounter = 0
var deviceInfoCounter = 0
var keyInfoCounter = 0
val deleteCounter: Int
trustLevelEntitySchema
?.addField("isLinked", Boolean::class.java)
?.transform { obj ->
// Setting to false for all by default
obj.set("isLinked", false)
mainCounter++
}
realm.schema.get("DeviceInfoEntity")?.transform { obj ->
// Setting to true for those which are referenced in DeviceInfoEntity
deviceInfoCounter++
obj.getObject("trustLevelEntity")?.set("isLinked", true)
}
realm.schema.get("KeyInfoEntity")?.transform { obj ->
// Setting to true for those which are referenced in KeyInfoEntity
keyInfoCounter++
obj.getObject("trustLevelEntity")?.set("isLinked", true)
}
// Removing all those which are set as false
realm.where("TrustLevelEntity")
.equalTo("isLinked", false)
.findAll()
.also { deleteCounter = it.size }
.deleteAllFromRealm()
trustLevelEntitySchema?.removeField("isLinked")
Timber.w("TrustLevelEntity cleanup: $mainCounter entities")
Timber.w("TrustLevelEntity cleanup: $deviceInfoCounter entities referenced in DeviceInfoEntities")
Timber.w("TrustLevelEntity cleanup: $keyInfoCounter entities referenced in KeyInfoEntity")
Timber.w("TrustLevelEntity cleanup: $deleteCounter entities deleted!")
if (mainCounter != deviceInfoCounter + keyInfoCounter + deleteCounter) {
Timber.e("TrustLevelEntity cleanup: Something is not correct...")
}
}
}

View File

@ -44,23 +44,32 @@ object CryptoMapper {
))
internal fun mapToEntity(deviceInfo: CryptoDeviceInfo): DeviceInfoEntity {
return DeviceInfoEntity(
primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId),
userId = deviceInfo.userId,
deviceId = deviceInfo.deviceId,
algorithmListJson = listMigrationAdapter.toJson(deviceInfo.algorithms),
keysMapJson = mapMigrationAdapter.toJson(deviceInfo.keys),
signatureMapJson = mapMigrationAdapter.toJson(deviceInfo.signatures),
isBlocked = deviceInfo.isBlocked,
trustLevelEntity = deviceInfo.trustLevel?.let {
TrustLevelEntity(
crossSignedVerified = it.crossSigningVerified,
locallyVerified = it.locallyVerified
)
},
// We store the device name if present now
unsignedMapJson = deviceInfo.unsigned?.deviceDisplayName
)
return DeviceInfoEntity(primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId))
.also { updateDeviceInfoEntity(it, deviceInfo) }
}
internal fun updateDeviceInfoEntity(entity: DeviceInfoEntity, deviceInfo: CryptoDeviceInfo) {
entity.userId = deviceInfo.userId
entity.deviceId = deviceInfo.deviceId
entity.algorithmListJson = listMigrationAdapter.toJson(deviceInfo.algorithms)
entity.keysMapJson = mapMigrationAdapter.toJson(deviceInfo.keys)
entity.signatureMapJson = mapMigrationAdapter.toJson(deviceInfo.signatures)
entity.isBlocked = deviceInfo.isBlocked
val deviceInfoTrustLevel = deviceInfo.trustLevel
if (deviceInfoTrustLevel == null) {
entity.trustLevelEntity?.deleteFromRealm()
entity.trustLevelEntity = null
} else {
if (entity.trustLevelEntity == null) {
// Create a new TrustLevelEntity object
entity.trustLevelEntity = TrustLevelEntity()
}
// Update the existing TrustLevelEntity object
entity.trustLevelEntity?.crossSignedVerified = deviceInfoTrustLevel.crossSigningVerified
entity.trustLevelEntity?.locallyVerified = deviceInfoTrustLevel.locallyVerified
}
// We store the device name if present now
entity.unsignedMapJson = deviceInfo.unsigned?.deviceDisplayName
}
internal fun mapToModel(deviceInfoEntity: DeviceInfoEntity): CryptoDeviceInfo {