Skip to content

Commit

Permalink
Restore data migration from realm
Browse files Browse the repository at this point in the history
  • Loading branch information
yostyle committed Feb 7, 2025
1 parent 2065225 commit 5d4f0d5
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.coroutines.builder.safeInvokeOnClose
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.DefaultKeysAlgorithmAndData
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysAlgorithmAndData
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.SasVerification
import org.matrix.android.sdk.internal.crypto.verification.VerificationRequest
Expand Down Expand Up @@ -319,6 +320,22 @@ internal class OlmMachine @Inject constructor(
inner.receiveVerificationEvent(serializedEvent, roomId)
}

/**
* Used for lazy migration of inboundGroupSession from EA to ER.
*/
suspend fun importRoomKey(inbound: MXInboundMegolmSessionWrapper): Result<Unit> {
Timber.v("Migration:: Tentative lazy migration")
return withContext(coroutineDispatchers.io) {
val export = inbound.exportKeys()
?: return@withContext Result.failure(Exception("Failed to export key"))
val result = importDecryptedKeys(listOf(export), null).also {
Timber.v("Migration:: Tentative lazy migration result: ${it.totalNumberOfKeys}")
}
if (result.totalNumberOfKeys == 1) return@withContext Result.success(Unit)
return@withContext Result.failure(Exception("Import failed"))
}
}

/**
* Mark the given list of users to be tracked, triggering a key query request for them.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,8 +505,15 @@ internal class RustCryptoService @Inject constructor(
val content = event.content?.toModel<EncryptedEventContent>() ?: throw mxCryptoError
val roomId = event.roomId
val sessionId = content.sessionId
val senderKey = content.senderKey
if (roomId != null && sessionId != null) {
perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)
// try to perform a lazy migration from legacy store
val legacy = tryOrNull("Failed to access legacy crypto store") {
cryptoStore.getInboundGroupSession(sessionId, senderKey.orEmpty())
}
if (legacy == null || olmMachine.importRoomKey(legacy).isFailure) {
perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)
}
}
}
throw mxCryptoError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,23 @@ import dagger.Lazy
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.uia.UiaResult
import org.matrix.android.sdk.internal.auth.registration.handleUIA
import org.matrix.android.sdk.internal.crypto.OlmMachine
import org.matrix.android.sdk.internal.crypto.PerSessionBackupQueryRateLimiter
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
Expand All @@ -53,6 +59,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
Expand Down Expand Up @@ -95,10 +102,10 @@ internal class RequestSender @Inject constructor(
private val getRoomSessionDataTask: GetRoomSessionDataTask,
private val moshi: Moshi,
cryptoCoroutineScope: CoroutineScope,
// private val rateLimiter: PerSessionBackupQueryRateLimiter,
// private val cryptoStore: IMXCommonCryptoStore,
private val rateLimiter: PerSessionBackupQueryRateLimiter,
private val cryptoStore: IMXCommonCryptoStore,
private val localEchoRepository: LocalEchoRepository,
// private val olmMachine: Lazy<OlmMachine>
private val olmMachine: Lazy<OlmMachine>
) {

private val scope = CoroutineScope(
Expand Down Expand Up @@ -242,40 +249,40 @@ internal class RequestSender @Inject constructor(
.adapter<Map<String, Map<String, Any>>>(Map::class.java)
val jsonBody = adapter.fromJson(body)!!

// if (eventType == EventType.ROOM_KEY_REQUEST) {
// scope.launch {
// Timber.v("Intercepting key request, try backup")
// /**
// * It's a bit hacky, check how this can be better integrated with rust?
// */
// try {
// jsonBody.forEach { (_, deviceToContent) ->
// deviceToContent.forEach { (_, content) ->
// val hashMap = content as? Map<*, *>
// val action = hashMap?.get("action")?.toString()
// if (GossipingToDeviceObject.ACTION_SHARE_REQUEST == action) {
// val requestBody = hashMap["body"] as? Map<*, *>
// val roomId = requestBody?.get("room_id") as? String
// val sessionId = requestBody?.get("session_id") as? String
// val senderKey = requestBody?.get("sender_key") as? String
// if (roomId != null && sessionId != null) {
// // try to perform a lazy migration from legacy store
// val legacy = tryOrNull("Failed to access legacy crypto store") {
// cryptoStore.getInboundGroupSession(sessionId, senderKey.orEmpty())
// }
// if (legacy == null || olmMachine.get().importRoomKey(legacy).isFailure) {
// rateLimiter.tryFromBackupIfPossible(sessionId, roomId)
// }
// }
// }
// }
// }
// Timber.v("Intercepting key request, try backup")
// } catch (failure: Throwable) {
// Timber.v(failure, "Failed to use backup")
// }
// }
// }
if (eventType == EventType.ROOM_KEY_REQUEST) {
scope.launch {
Timber.v("Intercepting key request, try backup")
/**
* It's a bit hacky, check how this can be better integrated with rust?
*/
try {
jsonBody.forEach { (_, deviceToContent) ->
deviceToContent.forEach { (_, content) ->
val hashMap = content as? Map<*, *>
val action = hashMap?.get("action")?.toString()
if (GossipingToDeviceObject.ACTION_SHARE_REQUEST == action) {
val requestBody = hashMap["body"] as? Map<*, *>
val roomId = requestBody?.get("room_id") as? String
val sessionId = requestBody?.get("session_id") as? String
val senderKey = requestBody?.get("sender_key") as? String
if (roomId != null && sessionId != null) {
// try to perform a lazy migration from legacy store
val legacy = tryOrNull("Failed to access legacy crypto store") {
cryptoStore.getInboundGroupSession(sessionId, senderKey.orEmpty())
}
if (legacy == null || olmMachine.get().importRoomKey(legacy).isFailure) {
rateLimiter.tryFromBackupIfPossible(sessionId, roomId)
}
}
}
}
}
Timber.v("Intercepting key request, try backup")
} catch (failure: Throwable) {
Timber.v(failure, "Failed to use backup")
}
}
}

val userMap = MXUsersDevicesMap<Any>()
userMap.join(jsonBody)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
*/
interface IMXCommonCryptoStore {

/**
* Retrieve the known inbound group sessions.
*
* @return the list of all known group sessions, to export them.
*/
fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper>

/**
* Provides the algorithm used in a dedicated room.
*
Expand Down Expand Up @@ -150,4 +143,21 @@ interface IMXCommonCryptoStore {
* @return the device or null if not found
*/
fun deviceWithIdentityKey(userId: String, identityKey: String): CryptoDeviceInfo?

/**
* Retrieve an inbound group session.
* Used in rust for lazy migration
*
* @param sessionId the session identifier.
* @param senderKey the base64-encoded curve25519 key of the sender.
* @return an inbound group session.
*/
fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper?

/**
* Retrieve the known inbound group sessions.
*
* @return the list of all known group sessions, to export them.
*/
fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper>
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFie
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.createPrimaryKey
import org.matrix.android.sdk.internal.crypto.store.db.query.getById
import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate
import org.matrix.android.sdk.internal.di.CryptoDatabase
Expand Down Expand Up @@ -126,6 +128,32 @@ internal class RustCryptoStore @Inject constructor(
}
}

/**
* 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.
*/
override fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper> {
return doWithRealm(realmConfiguration) { realm ->
realm.where<OlmInboundGroupSessionEntity>()
.findAll()
.mapNotNull { it.toModel() }
}
}

/**
* Needed for lazy migration of sessions from the legacy store.
*/
override fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper? {
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)

return doWithRealm(realmConfiguration) { realm ->
realm.where<OlmInboundGroupSessionEntity>()
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
.findFirst()
?.toModel()
}
}

// ================================================
// Things that should be migrated to another store than realm
// ================================================
Expand All @@ -152,18 +180,6 @@ internal class RustCryptoStore @Inject constructor(
}
}

/**
* 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.
*/
override fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper> {
return doWithRealm(realmConfiguration) { realm ->
realm.where<OlmInboundGroupSessionEntity>()
.findAll()
.mapNotNull { it.toModel() }
}
}

override fun getRoomAlgorithm(roomId: String): String? {
return doWithRealm(realmConfiguration) {
CryptoRoomEntity.getById(it, roomId)?.algorithm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.realm.annotations.RealmModule
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity

/**
* Realm module for Crypto store classes.
Expand All @@ -29,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInf
classes = [
CryptoMetadataEntity::class,
CryptoRoomEntity::class,
OlmInboundGroupSessionEntity::class,
MyDeviceLastSeenInfoEntity::class,
]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package org.matrix.android.sdk.internal.crypto.store.db.migration

import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.extensions.safeRemove
import org.matrix.android.sdk.internal.util.database.RealmMigrator

internal class MigrateCryptoTo024(realm: DynamicRealm) : RealmMigrator(realm, 24) {
Expand All @@ -33,20 +32,20 @@ internal class MigrateCryptoTo024(realm: DynamicRealm) : RealmMigrator(realm, 24
get("CryptoRoomEntity")?.removeField("outboundSessionInfo")

// Warning: order is important, first remove classes that depends on others.
safeRemove("UserEntity")
safeRemove("DeviceInfoEntity")
safeRemove("CrossSigningInfoEntity")
safeRemove("KeyInfoEntity")
safeRemove("TrustLevelEntity")
safeRemove("KeysBackupDataEntity")
// safeRemove("UserEntity")
// safeRemove("DeviceInfoEntity")
// safeRemove("CrossSigningInfoEntity")
// safeRemove("KeyInfoEntity")
// safeRemove("TrustLevelEntity")
// safeRemove("KeysBackupDataEntity")
// safeRemove("OlmInboundGroupSessionEntity")
safeRemove("OlmSessionEntity")
safeRemove("AuditTrailEntity")
safeRemove("OutgoingKeyRequestEntity")
safeRemove("KeyRequestReplyEntity")
safeRemove("WithHeldSessionEntity")
safeRemove("SharedSessionEntity")
safeRemove("OutboundGroupSessionInfoEntity")
// safeRemove("OlmSessionEntity")
// safeRemove("AuditTrailEntity")
// safeRemove("OutgoingKeyRequestEntity")
// safeRemove("KeyRequestReplyEntity")
// safeRemove("WithHeldSessionEntity")
// safeRemove("SharedSessionEntity")
// safeRemove("OutboundGroupSessionInfoEntity")
}
}
}

0 comments on commit 5d4f0d5

Please sign in to comment.