diff --git a/CHANGES.md b/CHANGES.md index b11be910db..bb132982d8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,7 +14,7 @@ Improvements 🙌: - Fix autocompletion issues and add support for rooms, groups, and emoji (#780) Other changes: - - + - Change the way RiotX identifies a session to allow the SDK to support several sessions with the same user (#800) Bugfix 🐛: - Fix crash when opening room creation screen from the room filtering screen diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionId.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionId.kt new file mode 100644 index 0000000000..2f39806aa5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionId.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2020 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.auth + +import im.vector.matrix.android.internal.util.md5 + +internal fun createSessionId(userId: String, deviceId: String?): String { + return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5() +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt index 83bf7b7822..7d7f8cc22c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt @@ -16,6 +16,9 @@ package im.vector.matrix.android.internal.auth.db +import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.internal.auth.createSessionId +import im.vector.matrix.android.internal.di.MoshiProvider import io.realm.DynamicRealm import io.realm.RealmMigration import timber.log.Timber @@ -23,35 +26,60 @@ import timber.log.Timber internal object AuthRealmMigration : RealmMigration { // Current schema version - const val SCHEMA_VERSION = 2L + const val SCHEMA_VERSION = 3L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Auth Realm from $oldVersion to $newVersion") - if (oldVersion <= 0) { - Timber.d("Step 0 -> 1") - Timber.d("Create PendingSessionEntity") + if (oldVersion <= 0) migrateTo1(realm) + if (oldVersion <= 1) migrateTo2(realm) + if (oldVersion <= 2) migrateTo3(realm) + } - realm.schema.create("PendingSessionEntity") - .addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java) - .setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true) - .addField(PendingSessionEntityFields.CLIENT_SECRET, String::class.java) - .setRequired(PendingSessionEntityFields.CLIENT_SECRET, true) - .addField(PendingSessionEntityFields.SEND_ATTEMPT, Integer::class.java) - .setRequired(PendingSessionEntityFields.SEND_ATTEMPT, true) - .addField(PendingSessionEntityFields.RESET_PASSWORD_DATA_JSON, String::class.java) - .addField(PendingSessionEntityFields.CURRENT_SESSION, String::class.java) - .addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java) - .addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java) - } + private fun migrateTo1(realm: DynamicRealm) { + Timber.d("Step 0 -> 1") + Timber.d("Create PendingSessionEntity") - if (oldVersion <= 1) { - Timber.d("Step 1 -> 2") - Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true") + realm.schema.create("PendingSessionEntity") + .addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java) + .setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true) + .addField(PendingSessionEntityFields.CLIENT_SECRET, String::class.java) + .setRequired(PendingSessionEntityFields.CLIENT_SECRET, true) + .addField(PendingSessionEntityFields.SEND_ATTEMPT, Integer::class.java) + .setRequired(PendingSessionEntityFields.SEND_ATTEMPT, true) + .addField(PendingSessionEntityFields.RESET_PASSWORD_DATA_JSON, String::class.java) + .addField(PendingSessionEntityFields.CURRENT_SESSION, String::class.java) + .addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java) + .addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java) + } - realm.schema.get("SessionParamsEntity") - ?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java) - ?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) } - } + private fun migrateTo2(realm: DynamicRealm) { + Timber.d("Step 1 -> 2") + Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true") + + realm.schema.get("SessionParamsEntity") + ?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java) + ?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) } + } + + private fun migrateTo3(realm: DynamicRealm) { + Timber.d("Step 2 -> 3") + Timber.d("Update SessionParamsEntity primary key, to allow several sessions with the same userId") + + realm.schema.get("SessionParamsEntity") + ?.removePrimaryKey() + ?.addField(SessionParamsEntityFields.SESSION_ID, String::class.java) + ?.setRequired(SessionParamsEntityFields.SESSION_ID, true) + ?.transform { + val userId = it.getString(SessionParamsEntityFields.USER_ID) + val credentialsJson = it.getString(SessionParamsEntityFields.CREDENTIALS_JSON) + + val credentials = MoshiProvider.providesMoshi() + .adapter(Credentials::class.java) + .fromJson(credentialsJson) + + it.set(SessionParamsEntityFields.SESSION_ID, createSessionId(userId, credentials?.deviceId)) + } + ?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsEntity.kt index 92511dccf7..72eed95fcc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsEntity.kt @@ -20,7 +20,8 @@ import io.realm.RealmObject import io.realm.annotations.PrimaryKey internal open class SessionParamsEntity( - @PrimaryKey var userId: String = "", + @PrimaryKey var sessionId: String = "", + var userId: String = "", var credentialsJson: String = "", var homeServerConnectionConfigJson: String = "", // Set to false when the token is invalid and the user has been soft logged out diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt index 72e8087f3f..d4ba1eb818 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.Moshi import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.internal.auth.createSessionId import javax.inject.Inject internal class SessionParamsMapper @Inject constructor(moshi: Moshi) { @@ -49,6 +50,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) { return null } return SessionParamsEntity( + createSessionId(sessionParams.credentials.userId, sessionParams.credentials.deviceId), sessionParams.credentials.userId, credentialsJson, homeServerConnectionConfigJson, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index fd180a93b0..9bf3d227c0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -47,7 +47,7 @@ internal abstract class CryptoModule { @Module companion object { - internal const val DB_ALIAS_PREFIX = "crypto_module_" + internal fun getKeyAlias(userMd5: String) = "crypto_module_$userMd5" @JvmStatic @Provides @@ -59,7 +59,7 @@ internal abstract class CryptoModule { return RealmConfiguration.Builder() .directory(directory) .apply { - realmKeysUtils.configureEncryption(this, "$DB_ALIAS_PREFIX$userMd5") + realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5)) } .name("crypto_store.realm") .modules(RealmCryptoStoreModule()) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt index 24f8a83c56..ddc7f5e8e6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.database import android.content.Context import im.vector.matrix.android.internal.database.model.SessionRealmModule +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.di.UserCacheDirectory import im.vector.matrix.android.internal.di.UserMd5 import im.vector.matrix.android.internal.session.SessionModule @@ -37,13 +38,14 @@ private const val REALM_NAME = "disk_store.realm" */ internal class SessionRealmConfigurationFactory @Inject constructor(private val realmKeysUtils: RealmKeysUtils, @UserCacheDirectory val directory: File, + @SessionId val sessionId: String, @UserMd5 val userMd5: String, context: Context) { private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE) fun create(): RealmConfiguration { - val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$userMd5", false) + val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false) if (shouldClearRealm) { Timber.v("************************************************************") Timber.v("The realm file session was corrupted and couldn't be loaded.") @@ -53,7 +55,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(private val } sharedPreferences .edit() - .putBoolean("$REALM_SHOULD_CLEAR_FLAG_$userMd5", true) + .putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", true) .apply() val realmConfiguration = RealmConfiguration.Builder() @@ -61,7 +63,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(private val .directory(directory) .name(REALM_NAME) .apply { - realmKeysUtils.configureEncryption(this, "${SessionModule.DB_ALIAS_PREFIX}$userMd5") + realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) } .modules(SessionRealmModule()) .deleteRealmIfMigrationNeeded() @@ -72,7 +74,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(private val Timber.v("Successfully create realm instance") sharedPreferences .edit() - .putBoolean("$REALM_SHOULD_CLEAR_FLAG_$userMd5", false) + .putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false) .apply() } return realmConfiguration diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/StringQualifiers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/StringQualifiers.kt index 0e38618590..32649443db 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/StringQualifiers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/StringQualifiers.kt @@ -31,3 +31,10 @@ internal annotation class UserId @Qualifier @Retention(AnnotationRetention.RUNTIME) internal annotation class UserMd5 + +/** + * Used to inject the sessionId, which is defined as md5(userId|deviceId) + */ +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +internal annotation class SessionId diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt index c160ac9b31..66b94cf68d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt @@ -25,7 +25,7 @@ import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments -import im.vector.matrix.android.internal.di.UserMd5 +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.md5 @@ -42,7 +42,7 @@ import java.io.IOException import javax.inject.Inject internal class DefaultFileService @Inject constructor(private val context: Context, - @UserMd5 private val userMd5: String, + @SessionId private val sessionId: String, private val contentUrlResolver: ContentUrlResolver, private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService { @@ -103,9 +103,9 @@ internal class DefaultFileService @Inject constructor(private val context: Conte return when (downloadMode) { FileService.DownloadMode.FOR_INTERNAL_USE -> { // Create dir tree (MF stands for Matrix File): - // /MF/// + // /MF/// val tmpFolderRoot = File(context.cacheDir, "MF") - val tmpFolderUser = File(tmpFolderRoot, userMd5) + val tmpFolderUser = File(tmpFolderRoot, sessionId) File(tmpFolderUser, id.md5()) } FileService.DownloadMode.TO_EXPORT -> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 0e88894969..437a559ea1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService import im.vector.matrix.android.api.session.securestorage.SecureStorageService +import im.vector.matrix.android.internal.auth.createSessionId import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory import im.vector.matrix.android.internal.di.* @@ -54,8 +55,7 @@ internal abstract class SessionModule { @Module companion object { - - internal const val DB_ALIAS_PREFIX = "session_db_" + internal fun getKeyAlias(userMd5: String) = "session_db_$userMd5" @JvmStatic @Provides @@ -83,11 +83,26 @@ internal abstract class SessionModule { return userId.md5() } + @JvmStatic + @SessionId + @Provides + fun providesSessionId(credentials: Credentials): String { + return createSessionId(credentials.userId, credentials.deviceId) + } + @JvmStatic @Provides @UserCacheDirectory - fun providesFilesDir(@UserMd5 userMd5: String, context: Context): File { - return File(context.filesDir, userMd5) + fun providesFilesDir(@UserMd5 userMd5: String, + @SessionId sessionId: String, + context: Context): File { + // Temporary code for migration + val old = File(context.filesDir, userMd5) + if (old.exists()) { + old.renameTo(File(context.filesDir, sessionId)) + } + + return File(context.filesDir, sessionId) } @JvmStatic diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt index 51cb22c988..b43bfa603c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt @@ -97,8 +97,8 @@ internal class DefaultSignOutTask @Inject constructor(private val context: Conte userFile.deleteRecursively() Timber.d("SignOut: clear the database keys") - realmKeysUtils.clear(SessionModule.DB_ALIAS_PREFIX + userMd5) - realmKeysUtils.clear(CryptoModule.DB_ALIAS_PREFIX + userMd5) + realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5)) + realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5)) // Sanity check if (BuildConfig.DEBUG) {