diff --git a/CHANGES.md b/CHANGES.md index 905f178526..39ea3f1477 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,76 @@ +Changes in Element v1.6.2 (2023-06-02) +====================================== + +Features ✨ +---------- + - **Element Android is now using the Crypto Rust SDK**. Migration of user's data should be done at first launch after application upgrade. ([#8390](https://github.com/vector-im/element-android/issues/8390)) + - Marks WebP files as Animated and allows them to play ([#8120](https://github.com/vector-im/element-android/issues/8120)) + - Updates to protocol used for Sign in with QR code ([#8299](https://github.com/vector-im/element-android/issues/8299)) + - Updated rust crypto SDK to version 0.3.9 ([#8488](https://github.com/vector-im/element-android/issues/8488)) + +Bugfixes 🐛 +---------- + - Fix: Allow users to sign out even if the sign out request fails. ([#4855](https://github.com/vector-im/element-android/issues/4855)) + - fix: Make some crypto calls suspendable to avoid reported ANR ([#8482](https://github.com/vector-im/element-android/issues/8482)) + +Other changes +------------- + - Refactoring: Extract a new interface for common access to crypto store between kotlin and rust crypto ([#8470](https://github.com/vector-im/element-android/issues/8470)) + + +Changes in Element v1.6.1 (2023-05-25) +====================================== + +Corrective release for 1.6.0 + +Bugfixes 🐛 +---------- + - Allow stateloss on verification dialogfragment ([#8439](https://github.com/vector-im/element-android/issues/8439)) + - Fix: Update verification popup text when a re-verification is needed after rust migration (read only sessions) ([#8445](https://github.com/vector-im/element-android/issues/8445)) + - Fix several performance issues causing app non responsive issues. ([#8454](https://github.com/vector-im/element-android/issues/8454)) + - Fix: The device list screen from the member profile page was always showing the current user devices (rust crypto). ([#8457](https://github.com/vector-im/element-android/issues/8457)) + +Other changes +------------- + - Remove UI option to manually verify a specific device of another user (deprecated behaviour) ([#8458](https://github.com/vector-im/element-android/issues/8458)) + + +Changes in Element v1.6.0 (2023-05-17) +====================================== + +Features ✨ +---------- + - **Element Android is now using the Crypto Rust SDK**. Migration of user's data should be done at first launch after application upgrade. ([#8390](https://github.com/vector-im/element-android/issues/8390)) + - Enable free style cropping for camera and gallery images ([#8325](https://github.com/vector-im/element-android/issues/8325)) + +Bugfixes 🐛 +---------- + - User pills get lost at message editing ([#748](https://github.com/vector-im/element-android/issues/748)) + - Upgrade Jitsi SDK from 6.2.2 to 8.1.1. This fixes video call on some Android devices. ([#7619](https://github.com/vector-im/element-android/issues/7619)) + - Fix duplicate reactions when using full emoji picker. Contributed by @tulir @ Beeper. ([#8327](https://github.com/vector-im/element-android/issues/8327)) + - Fix: RustCrossSigning service API confusion (identity trusted vs own device trusted by identity) ([#8352](https://github.com/vector-im/element-android/issues/8352)) + - Allow custom push gateway to use non-default port ([#8376](https://github.com/vector-im/element-android/issues/8376)) + - Fix crash when opening "Protect access" screen, and various other issue with `repeatOnLifecycle` ([#8410](https://github.com/vector-im/element-android/issues/8410)) + - RustCrypto: Verification UX not refreshed after scanning a QR code ([#8418](https://github.com/vector-im/element-android/issues/8418)) + +SDK API changes ⚠️ +------------------ + - First integration of rust crypto module. See documentation for details `docs/rust_crypto_integration.md` ([#7628](https://github.com/vector-im/element-android/issues/7628)) + - Add crypto database migration 22, that extract account and olm session to the new rust DB format ([#8405](https://github.com/vector-im/element-android/issues/8405)) + +Other changes +------------- + - Add an audio alert when the voice broadcast recording is automatically paused ([#8339](https://github.com/vector-im/element-android/issues/8339)) + - Analytics: add crypto module to E2E events ([#8340](https://github.com/vector-im/element-android/issues/8340)) + - Bump rust crypto crate to 0.3.5 ([#8354](https://github.com/vector-im/element-android/issues/8354)) + - Expose Rust SDK Version in Help & About page and in Bug Reports ([#8364](https://github.com/vector-im/element-android/issues/8364)) + - Matrix-Ids are sometimes shown in notice events instead of display names ([#8365](https://github.com/vector-im/element-android/issues/8365)) + - CI: Add workflow to run test with crypto flavor ([#8366](https://github.com/vector-im/element-android/issues/8366)) + - Remove ability to migrate session from Riot to Element. ([#8402](https://github.com/vector-im/element-android/issues/8402)) + - Improve keyboard navigation and accessibility when using a screen reader. ([#8426](https://github.com/vector-im/element-android/issues/8426)) + - Updated posthog url (cosmetic, target same server) and added a new sentry env. ([#8436](https://github.com/vector-im/element-android/issues/8436)) + + Changes in Element v1.5.32 (2023-04-19) ======================================= diff --git a/build.gradle b/build.gradle index 3060747977..a70b54a833 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ buildscript { classpath 'com.google.gms:google-services:4.3.15' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.0.0.2929' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6' - classpath "com.likethesalad.android:stem-plugin:2.4.0" + classpath "com.likethesalad.android:stem-plugin:2.4.1" classpath 'org.owasp:dependency-check-gradle:8.2.1' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.8.10" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" diff --git a/changelog.d/748.bugfix b/changelog.d/748.bugfix deleted file mode 100644 index cc33be0033..0000000000 --- a/changelog.d/748.bugfix +++ /dev/null @@ -1 +0,0 @@ -User pills get lost at message editing \ No newline at end of file diff --git a/changelog.d/7619.bugfix b/changelog.d/7619.bugfix deleted file mode 100644 index a41cf5321f..0000000000 --- a/changelog.d/7619.bugfix +++ /dev/null @@ -1 +0,0 @@ -Upgrade Jitsi SDK from 6.2.2 to 8.1.1. This fixes video call on some Android devices. diff --git a/changelog.d/7628.sdk b/changelog.d/7628.sdk deleted file mode 100644 index d9886730fd..0000000000 --- a/changelog.d/7628.sdk +++ /dev/null @@ -1 +0,0 @@ -First integration of rust crypto module. See documentation for details `docs/rust_crypto_integration.md` diff --git a/changelog.d/8325.feature b/changelog.d/8325.feature deleted file mode 100644 index 59e57838f8..0000000000 --- a/changelog.d/8325.feature +++ /dev/null @@ -1 +0,0 @@ -Enable free style cropping for camera and gallery images diff --git a/changelog.d/8327.bugfix b/changelog.d/8327.bugfix deleted file mode 100644 index d1e57f4c16..0000000000 --- a/changelog.d/8327.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix duplicate reactions when using full emoji picker. Contributed by @tulir @ Beeper. diff --git a/changelog.d/8339.misc b/changelog.d/8339.misc deleted file mode 100644 index 2fed9c438b..0000000000 --- a/changelog.d/8339.misc +++ /dev/null @@ -1 +0,0 @@ -Add an audio alert when the voice broadcast recording is automatically paused diff --git a/changelog.d/8340.misc b/changelog.d/8340.misc deleted file mode 100644 index 52556b5b42..0000000000 --- a/changelog.d/8340.misc +++ /dev/null @@ -1 +0,0 @@ -Analytics: add crypto module to E2E events diff --git a/changelog.d/8352.bugfix b/changelog.d/8352.bugfix deleted file mode 100644 index 695c02715e..0000000000 --- a/changelog.d/8352.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix: RustCrossSigning service API confusion (identity trusted vs own device trusted by identity) diff --git a/changelog.d/8354.misc b/changelog.d/8354.misc deleted file mode 100644 index f26be92ed6..0000000000 --- a/changelog.d/8354.misc +++ /dev/null @@ -1 +0,0 @@ -Bump rust crypto crate to 0.3.5 diff --git a/changelog.d/8364.misc b/changelog.d/8364.misc deleted file mode 100644 index 27bf6b975a..0000000000 --- a/changelog.d/8364.misc +++ /dev/null @@ -1 +0,0 @@ -Expose Rust SDK Version in Help & About page and in Bug Reports diff --git a/changelog.d/8365.misc b/changelog.d/8365.misc deleted file mode 100644 index ef9bbdca04..0000000000 --- a/changelog.d/8365.misc +++ /dev/null @@ -1 +0,0 @@ -Matrix-Ids are sometimes shown in notice events instead of display names diff --git a/changelog.d/8366.misc b/changelog.d/8366.misc deleted file mode 100644 index 87c5271688..0000000000 --- a/changelog.d/8366.misc +++ /dev/null @@ -1 +0,0 @@ -CI: Add workflow to run test with crypto flavor diff --git a/changelog.d/8376.bugfix b/changelog.d/8376.bugfix deleted file mode 100644 index 2c30a1e540..0000000000 --- a/changelog.d/8376.bugfix +++ /dev/null @@ -1 +0,0 @@ -Allow custom push gateway to use non-default port \ No newline at end of file diff --git a/changelog.d/8390.feature b/changelog.d/8390.feature deleted file mode 100644 index cc430a5a42..0000000000 --- a/changelog.d/8390.feature +++ /dev/null @@ -1 +0,0 @@ -Element Android is now using the Crypto Rust SDK. Migration of user's data should be done at first launch after application upgrade. diff --git a/changelog.d/8402.misc b/changelog.d/8402.misc deleted file mode 100644 index c493b6bf92..0000000000 --- a/changelog.d/8402.misc +++ /dev/null @@ -1 +0,0 @@ -Remove ability to migrate session from Riot to Element. diff --git a/changelog.d/8405.sdk b/changelog.d/8405.sdk deleted file mode 100644 index c8d165fcdc..0000000000 --- a/changelog.d/8405.sdk +++ /dev/null @@ -1 +0,0 @@ -Add crypto database migration 22, that extract account and olm session to the new rust DB format diff --git a/changelog.d/8410.bugfix b/changelog.d/8410.bugfix deleted file mode 100644 index f72d02e0b4..0000000000 --- a/changelog.d/8410.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash when opening "Protect access" screen, and various other issue with `repeatOnLifecycle` diff --git a/changelog.d/8418.bugfix b/changelog.d/8418.bugfix deleted file mode 100644 index 9b5a2e2fa4..0000000000 --- a/changelog.d/8418.bugfix +++ /dev/null @@ -1 +0,0 @@ -RustCrypto: Verification UX not refreshed after scanning a QR code diff --git a/changelog.d/8426.misc b/changelog.d/8426.misc deleted file mode 100644 index 6e3b5013c8..0000000000 --- a/changelog.d/8426.misc +++ /dev/null @@ -1 +0,0 @@ -Improve keyboard navigation and accessibility when using a screen reader. diff --git a/changelog.d/8436.misc b/changelog.d/8436.misc deleted file mode 100644 index ab174eca5b..0000000000 --- a/changelog.d/8436.misc +++ /dev/null @@ -1 +0,0 @@ -Updated posthog url (cosmetic, target same server) and added a new sentry env. diff --git a/dependencies.gradle b/dependencies.gradle index 9fbaf5608d..ccf675321d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -47,7 +47,7 @@ ext.libs = [ 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" ], androidx : [ - 'activity' : "androidx.activity:activity-ktx:1.7.1", + 'activity' : "androidx.activity:activity-ktx:1.7.2", 'appCompat' : "androidx.appcompat:appcompat:1.6.1", 'biometric' : "androidx.biometric:biometric:1.1.0", 'core' : "androidx.core:core-ktx:1.10.1", @@ -101,7 +101,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:2.2.0" + 'wysiwyg' : "io.element.android:wysiwyg:2.2.1" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", diff --git a/fastlane/metadata/android/en-US/changelogs/40106000.txt b/fastlane/metadata/android/en-US/changelogs/40106000.txt new file mode 100644 index 0000000000..badf979955 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Main changes in this version: Element Android is now using the Crypto Rust SDK. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40106010.txt b/fastlane/metadata/android/en-US/changelogs/40106010.txt new file mode 100644 index 0000000000..badf979955 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Main changes in this version: Element Android is now using the Crypto Rust SDK. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40106020.txt b/fastlane/metadata/android/en-US/changelogs/40106020.txt new file mode 100644 index 0000000000..badf979955 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Main changes in this version: Element Android is now using the Crypto Rust SDK. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 3d1a36d4c3..ee7d2fea12 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -328,6 +328,9 @@ Back up You’ll lose access to your encrypted messages unless you back up your keys before signing out. + Cannot reach the homeserver. If you sign out anyway, this device will not be erased from your device list, you may want to remove it using another client. + Sign out anyway + Loading… @@ -2446,6 +2449,7 @@ Verify this device + App updated Unable to verify this device You won’t be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh. @@ -2465,7 +2469,9 @@ This session is trusted for secure messaging because %1$s (%2$s) verified it: %1$s (%2$s) signed in using a new session: - Until this user trusts this session, messages sent to and from it are labeled with warnings. Alternatively, you can manually verify it. + + Until this user trusts this session, messages sent to and from it are labeled with warnings. Alternatively, you can manually verify it. + Until this user trusts this session, messages sent to and from it are labeled with warnings. Initialize CrossSigning @@ -2707,6 +2713,7 @@ Verify login Interactively Verify by Emoji Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages. + Secure messaging has been improved with the latest update. Please re-verify your device. Confirm your identity by verifying this login, granting it access to encrypted messages. Failed to set up Cross Signing diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 9c230bb400..420b24ab5a 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -63,7 +63,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.5.34\"" + buildConfigField "String", "SDK_VERSION", "\"1.6.2\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" @@ -216,8 +216,8 @@ dependencies { implementation libs.google.phonenumber - rustCryptoImplementation("org.matrix.rustcomponents:crypto-android:0.3.7") - // rustCryptoApi project(":library:rustCrypto") + rustCryptoImplementation("org.matrix.rustcomponents:crypto-android:0.3.9") +// rustCryptoApi project(":library:rustCrypto") testImplementation libs.tests.junit // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt b/matrix-sdk-android/src/androidTestKotlinCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt similarity index 100% rename from matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt rename to matrix-sdk-android/src/androidTestKotlinCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt b/matrix-sdk-android/src/androidTestKotlinCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt similarity index 100% rename from matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt rename to matrix-sdk-android/src/androidTestKotlinCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index 6ae12aaf3d..719c45a113 100644 --- a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -59,6 +59,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionD import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask +import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration @@ -254,6 +255,9 @@ internal abstract class CryptoModule { @Binds abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore + @Binds + abstract fun bindCommonCryptoStore(store: RealmCryptoStore): IMXCommonCryptoStore + @Binds abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 1dd02543cb..b25c04aa9b 100755 --- a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -256,7 +256,7 @@ internal class DefaultCryptoService @Inject constructor( return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version } - override fun getMyCryptoDevice(): CryptoDeviceInfo { + override suspend fun getMyCryptoDevice(): CryptoDeviceInfo { return myDeviceInfoHolder.get().myDevice } @@ -506,10 +506,7 @@ internal class DefaultCryptoService @Inject constructor( null } else { withContext(coroutineDispatchers.io) { - cryptoStore.deviceWithIdentityKey(senderKey).takeIf { - // check that the claimed user id matches - it?.userId == userId - } + cryptoStore.deviceWithIdentityKey(userId, senderKey) } } } @@ -539,7 +536,7 @@ internal class DefaultCryptoService @Inject constructor( // .executeBy(taskExecutor) // } - override fun getCryptoDeviceInfo(userId: String): List { + override suspend fun getCryptoDeviceInfo(userId: String): List { return cryptoStore.getUserDeviceList(userId).orEmpty() } // diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt similarity index 100% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt rename to matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt similarity index 100% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt rename to matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt similarity index 81% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt rename to matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index a13c15bd1a..fc882e5c1d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -28,20 +28,16 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.session.crypto.model.CryptoRoomInfo -import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.api.session.crypto.model.TrailType import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper -import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.olm.OlmAccount import org.matrix.olm.OlmOutboundGroupSession @@ -49,7 +45,7 @@ import org.matrix.olm.OlmOutboundGroupSession /** * The crypto data store. */ -internal interface IMXCryptoStore { +internal interface IMXCryptoStore : IMXCommonCryptoStore { /** * @return the device id @@ -78,21 +74,6 @@ internal interface IMXCryptoStore { */ fun getInboundGroupSessions(roomId: String): List - /** - * @return true to unilaterally blacklist all unverified devices. - */ - fun getGlobalBlacklistUnverifiedDevices(): Boolean - - /** - * Set the global override for whether the client should ever send encrypted - * messages to unverified devices. - * If false, it can still be overridden per-room. - * If true, it overrides the per-room settings. - * - * @param block true to unilaterally blacklist all - */ - fun setGlobalBlacklistUnverifiedDevices(block: Boolean) - /** * Enable or disable key gossiping. * Default is true. @@ -123,28 +104,6 @@ internal interface IMXCryptoStore { */ fun getRoomsListBlacklistUnverifiedDevices(): List - /** - * A live status regarding sharing keys for unverified devices in this room. - * - * @return Live status - */ - fun getLiveBlockUnverifiedDevices(roomId: String): LiveData - - /** - * 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. */ @@ -186,16 +145,6 @@ internal interface IMXCryptoStore { */ fun deleteStore() - /** - * open any existing crypto store. - */ - fun open() - - /** - * Close the store. - */ - fun close() - /** * Store the device id. * @@ -262,14 +211,6 @@ internal interface IMXCryptoStore { fun getLiveDeviceWithId(deviceId: String): LiveData> - fun getMyDevicesInfo(): List - - fun getLiveMyDevicesInfo(): LiveData> - - fun getLiveMyDevicesInfo(deviceId: String): LiveData> - - fun saveMyDevicesInfo(info: List) - /** * Store the crypto algorithm for a room. * @@ -278,47 +219,8 @@ internal interface IMXCryptoStore { */ fun storeRoomAlgorithm(roomId: String, algorithm: String?) - /** - * Provides the algorithm used in a dedicated room. - * - * @param roomId the room id - * @return the algorithm, null is the room is not encrypted - */ - fun getRoomAlgorithm(roomId: String): String? - - fun getRoomCryptoInfo(roomId: String): CryptoRoomInfo? - fun setAlgorithmInfo(roomId: String, encryption: EncryptionEventContent?) - - /** - * This is a bit different than isRoomEncrypted. - * A room is encrypted when there is a m.room.encryption state event in the room (malformed/invalid or not). - * But the crypto layer has additional guaranty to ensure that encryption would never been reverted. - * It's defensive coding out of precaution (if ever state is reset). - */ - fun roomWasOnceEncrypted(roomId: String): Boolean - - fun shouldEncryptForInvitedMembers(roomId: String): Boolean - - /** - * Sets a boolean flag that will determine whether or not this device should encrypt Events for - * invited members. - * - * @param roomId the room id - * @param shouldEncryptForInvitedMembers The boolean flag - */ - fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) - fun shouldShareHistory(roomId: String): Boolean - /** - * Sets a boolean flag that will determine whether or not room history (existing inbound sessions) - * will be shared to new user invites. - * - * @param roomId the room id - * @param shouldShareHistory The boolean flag - */ - fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) - /** * Store a session between the logged-in user and another device. * @@ -361,15 +263,6 @@ internal interface IMXCryptoStore { */ fun storeInboundGroupSessions(sessions: List) - /** - * Retrieve an inbound group session. - * - * @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 an inbound group session, filtering shared history. * @@ -552,7 +445,6 @@ internal interface IMXCryptoStore { // fun getCrossSigningPrivateKeysFlow(): Flow> fun getGlobalCryptoConfig(): GlobalCryptoConfig - fun getLiveGlobalCryptoConfig(): LiveData fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? @@ -597,14 +489,8 @@ internal interface IMXCryptoStore { fun setDeviceKeysUploaded(uploaded: Boolean) fun areDeviceKeysUploaded(): Boolean - fun tidyUpDataBase() fun getOutgoingRoomKeyRequests(inStates: Set): List - /** - * Store a bunch of data collected during a sync response treatment. @See [CryptoStoreAggregator]. - */ - fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) - /** * Store a bunch of data related to the users. @See [UserDataToStore]. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt similarity index 99% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt rename to matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 2d82f67b9e..e3595f6618 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -280,6 +280,19 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun deviceWithIdentityKey(userId: String, identityKey: String): CryptoDeviceInfo? { + return doWithRealm(realmConfiguration) { realm -> + realm.where() + .equalTo(DeviceInfoEntityFields.USER_ID, userId) + .contains(DeviceInfoEntityFields.KEYS_MAP_JSON, identityKey) + .findAll() + .mapNotNull { CryptoMapper.mapToModel(it) } + .firstOrNull { + it.identityKey() == identityKey + } + } + } + override fun storeUserDevices(userId: String, devices: Map?) { doRealmTransaction("storeUserDevices", realmConfiguration) { realm -> storeUserDevices(realm, userId, devices) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 043d0a29f5..31d11f6730 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -73,7 +73,7 @@ interface CryptoService { suspend fun getUserDevices(userId: String): List - fun getMyCryptoDevice(): CryptoDeviceInfo + suspend fun getMyCryptoDevice(): CryptoDeviceInfo fun getGlobalBlacklistUnverifiedDevices(): Boolean @@ -130,7 +130,7 @@ interface CryptoService { suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? - fun getCryptoDeviceInfo(userId: String): List + suspend fun getCryptoDeviceInfo(userId: String): List // fun getCryptoDeviceInfoFlow(userId: String): Flow> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt index d64b2e6e92..fade51600a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt @@ -37,6 +37,7 @@ interface SignOutService { /** * Sign out, and release the session, clear all the session data, including crypto data. * @param signOutFromHomeserver true if the sign out request has to be done + * @param ignoreServerRequestError true to ignore server error if any */ - suspend fun signOut(signOutFromHomeserver: Boolean) + suspend fun signOut(signOutFromHomeserver: Boolean, ignoreServerRequestError: Boolean = false) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt index 5ec0dedadf..af8ab71a87 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt @@ -30,6 +30,7 @@ object MimeTypes { const val BadJpg = "image/jpg" const val Jpeg = "image/jpeg" const val Gif = "image/gif" + const val Webp = "image/webp" const val Ogg = "audio/ogg" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index d1dd0238ba..bbef75a21d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -301,6 +301,9 @@ internal class DefaultAuthenticationService @Inject constructor( val oidcCompatibilityFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.sso" && it.delegatedOidcCompatibilty == true } val flows = if (oidcCompatibilityFlow != null) listOf(oidcCompatibilityFlow) else loginFlowResponse.flows + val supportsGetLoginTokenFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.token" && it.getLoginToken == true } != null + + @Suppress("DEPRECATION") return LoginFlowResult( supportedLoginTypes = flows.orEmpty().mapNotNull { it.type }, ssoIdentityProviders = flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider, @@ -309,7 +312,7 @@ internal class DefaultAuthenticationService @Inject constructor( isOutdatedHomeserver = !versions.isSupportedBySdk(), hasOidcCompatibilityFlow = oidcCompatibilityFlow != null, isLogoutDevicesSupported = versions.doesServerSupportLogoutDevices(), - isLoginWithQrSupported = versions.doesServerSupportQrCodeLogin(), + isLoginWithQrSupported = supportsGetLoginTokenFlow || versions.doesServerSupportQrCodeLogin(), ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt index 971407388c..ea749a56b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt @@ -51,5 +51,13 @@ internal data class LoginFlow( * See [MSC3824](https://github.com/matrix-org/matrix-spec-proposals/pull/3824) */ @Json(name = "org.matrix.msc3824.delegated_oidc_compatibility") - val delegatedOidcCompatibilty: Boolean? = null + val delegatedOidcCompatibilty: Boolean? = null, + + /** + * Whether a login flow of type m.login.token could accept a token issued using /login/get_token. + * + * See https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv1loginget_token + */ + @Json(name = "get_login_token") + val getLoginToken: Boolean? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt index 4d8e90cf35..3fe5541b68 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt @@ -54,6 +54,7 @@ private const val FEATURE_ID_ACCESS_TOKEN = "m.id_access_token" private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind" private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440" private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable" +@Deprecated("The availability of stable get_login_token is now exposed as a capability and part of login flow") private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882" private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771" private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773" @@ -94,7 +95,9 @@ internal fun Versions.doesServerSupportThreadUnreadNotifications(): Boolean { return getMaxVersion() >= HomeServerVersion.v1_4_0 || (msc3771 && msc3773) } +@Deprecated("The availability of stable get_login_token is now exposed as a capability and part of login flow") internal fun Versions.doesServerSupportQrCodeLogin(): Boolean { + @Suppress("DEPRECATION") return unstableFeatures?.get(FEATURE_QR_CODE_LOGIN) ?: false } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ShouldEncryptForInvitedMembersUseCase.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ShouldEncryptForInvitedMembersUseCase.kt index 29a550aad3..7c7c8ce901 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ShouldEncryptForInvitedMembersUseCase.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ShouldEncryptForInvitedMembersUseCase.kt @@ -17,11 +17,11 @@ package org.matrix.android.sdk.internal.crypto import org.matrix.android.sdk.api.crypto.MXCryptoConfig -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore import javax.inject.Inject internal class ShouldEncryptForInvitedMembersUseCase @Inject constructor(private val cryptoConfig: MXCryptoConfig, - private val cryptoStore: IMXCryptoStore) { + private val cryptoStore: IMXCommonCryptoStore) { operator fun invoke(roomId: String): Boolean { return cryptoConfig.enableEncryptionForInvitedMembers && cryptoStore.shouldEncryptForInvitedMembers(roomId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCommonCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCommonCryptoStore.kt new file mode 100644 index 0000000000..68b002c087 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCommonCryptoStore.kt @@ -0,0 +1,156 @@ +/* + * Copyright 2023 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.store + +import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoRoomInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator + +/** + * As a temporary measure rust and kotlin flavor are still using realm to store some crypto + * related information. In the near future rust flavor will complitly stop using realm, as soon + * as the missing bits are store in rust side (like room encryption settings, ..) + * This interface defines what's now used by both flavors. + * The actual implementation are moved in each flavors + */ +interface IMXCommonCryptoStore { + + /** + * Provides the algorithm used in a dedicated room. + * + * @param roomId the room id + * @return the algorithm, null is the room is not encrypted + */ + fun getRoomAlgorithm(roomId: String): String? + + fun getRoomCryptoInfo(roomId: String): CryptoRoomInfo? + + fun setAlgorithmInfo(roomId: String, encryption: EncryptionEventContent?) + + fun roomWasOnceEncrypted(roomId: String): Boolean + + fun saveMyDevicesInfo(info: List) + + // questionable that it's stored in crypto store + fun getMyDevicesInfo(): List + + // questionable that it's stored in crypto store + fun getLiveMyDevicesInfo(): LiveData> + + // questionable that it's stored in crypto store + fun getLiveMyDevicesInfo(deviceId: String): LiveData> + + /** + * open any existing crypto store. + */ + fun open() + fun tidyUpDataBase() + + /** + * Close the store. + */ + fun close() + + /* + * Store a bunch of data collected during a sync response treatment. @See [CryptoStoreAggregator]. + */ + fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) + + fun shouldEncryptForInvitedMembers(roomId: String): Boolean + + /** + * Sets a boolean flag that will determine whether or not room history (existing inbound sessions) + * will be shared to new user invites. + * + * @param roomId the room id + * @param shouldShareHistory The boolean flag + */ + fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) + + /** + * Sets a boolean flag that will determine whether or not this device should encrypt Events for + * invited members. + * + * @param roomId the room id + * @param shouldEncryptForInvitedMembers The boolean flag + */ + fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: 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) + + /** + * Set the global override for whether the client should ever send encrypted + * messages to unverified devices. + * If false, it can still be overridden per-room. + * If true, it overrides the per-room settings. + * + * @param block true to unilaterally blacklist all + */ + fun setGlobalBlacklistUnverifiedDevices(block: Boolean) + + fun getLiveGlobalCryptoConfig(): LiveData + + /** + * @return true to unilaterally blacklist all unverified devices. + */ + fun getGlobalBlacklistUnverifiedDevices(): Boolean + + /** + * A live status regarding sharing keys for unverified devices in this room. + * + * @return Live status + */ + fun getLiveBlockUnverifiedDevices(roomId: String): LiveData + + /** + * 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 + + /** + * Retrieve a device by its identity key. + * + * @param userId the device owner + * @param identityKey the device identity key (`MXDeviceInfo.identityKey`) + * @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? +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt index 95ff44807c..7c60eab08f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt @@ -71,7 +71,14 @@ internal data class Capabilities( * True if the user can use m.thread relation, false otherwise. */ @Json(name = "m.thread") - val threads: BooleanCapability? = null + val threads: BooleanCapability? = null, + + /** + * Capability to indicate if the server supports login token issuance for signing in another device. + * True if the user can use /login/get_token, false otherwise. + */ + @Json(name = "m.get_login_token") + val getLoginToken: BooleanCapability? = null ) @JsonClass(generateAdapter = true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index ec12695ecd..a368325793 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -151,8 +151,6 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( getVersionResult.doesServerSupportThreads() homeServerCapabilitiesEntity.canUseThreadReadReceiptsAndNotifications = getVersionResult.doesServerSupportThreadUnreadNotifications() - homeServerCapabilitiesEntity.canLoginWithQrCode = - getVersionResult.doesServerSupportQrCodeLogin() homeServerCapabilitiesEntity.canRemotelyTogglePushNotificationsOfDevices = getVersionResult.doesServerSupportRemoteToggleOfPushNotifications() homeServerCapabilitiesEntity.canRedactEventWithRelations = @@ -169,10 +167,25 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( } homeServerCapabilitiesEntity.externalAccountManagementUrl = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.accountManagementUrl } + + homeServerCapabilitiesEntity.canLoginWithQrCode = canLoginWithQrCode(getCapabilitiesResult, getVersionResult) + homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time } } + private fun canLoginWithQrCode(getCapabilitiesResult: GetCapabilitiesResult?, getVersionResult: Versions?): Boolean { + // in r0 of MSC3882 an unstable feature was exposed. In stable it is done via /capabilities and /login + + // in stable 1.7 a capability is exposed for the authenticated user + if (getCapabilitiesResult?.capabilities?.getLoginToken != null) { + return getCapabilitiesResult.capabilities.getLoginToken.enabled == true + } + + @Suppress("DEPRECATION") + return getVersionResult?.doesServerSupportQrCodeLogin() == true + } + companion object { // 8 hours like on Element Web private const val MIN_DELAY_BETWEEN_TWO_REQUEST_MILLIS = 8 * 60 * 60 * 1000 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt index 5a66e7e62d..fbf1dc532c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt @@ -26,11 +26,10 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import timber.log.Timber +import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore import javax.inject.Inject -internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCryptoStore) { +internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCommonCryptoStore) { sealed class EditValidity { object Valid : EditValidity() @@ -53,7 +52,6 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto * If the original event was encrypted, the replacement should be too. */ fun validateEdit(originalEvent: Event?, replaceEvent: Event): EditValidity { - Timber.v("###REPLACE valide event $originalEvent replaced $replaceEvent") // we might not know the original event at that time. In this case we can't perform the validation // Edits should be revalidated when the original event is received if (originalEvent == null) { @@ -80,25 +78,21 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto val replaceDecrypted = replaceEvent.toValidDecryptedEvent() ?: return EditValidity.Unknown // UTD can't decide - val originalCryptoSenderId = cryptoStore.deviceWithIdentityKey(originalDecrypted.cryptoSenderKey)?.userId - val editCryptoSenderId = cryptoStore.deviceWithIdentityKey(replaceDecrypted.cryptoSenderKey)?.userId + if (originalEvent.senderId != replaceEvent.senderId) { + return EditValidity.Invalid("original event and replacement event must have the same sender") + } + + val originalSendingDevice = originalEvent.senderId?.let { cryptoStore.deviceWithIdentityKey(it, originalDecrypted.cryptoSenderKey) } + val editSendingDevice = originalEvent.senderId?.let { cryptoStore.deviceWithIdentityKey(it, replaceDecrypted.cryptoSenderKey) } if (originalDecrypted.getRelationContent()?.type == RelationType.REPLACE) { return EditValidity.Invalid("The original event must not, itself, have a rel_type of m.replace ") } - if (originalCryptoSenderId == null || editCryptoSenderId == null) { + if (originalSendingDevice == null || editSendingDevice == null) { // mm what can we do? we don't know if it's cryptographically from same user? - // let valid and UI should display send by deleted device warning? - val bestEffortOriginal = originalCryptoSenderId ?: originalEvent.senderId - val bestEffortEdit = editCryptoSenderId ?: replaceEvent.senderId - if (bestEffortOriginal != bestEffortEdit) { - return EditValidity.Invalid("original event and replacement event must have the same sender") - } - } else { - if (originalCryptoSenderId != editCryptoSenderId) { - return EditValidity.Invalid("Crypto: original event and replacement event must have the same sender") - } + // maybe it's a deleted device or a not yet downloaded one? + return EditValidity.Unknown } if (originalDecrypted.type != replaceDecrypted.type) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt index 36ec5e8dac..73b7ae05fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt @@ -22,6 +22,8 @@ import com.zhuinden.monarchy.Monarchy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.api.util.Optional @@ -43,7 +45,8 @@ internal class DefaultReadService @AssistedInject constructor( private val setReadMarkersTask: SetReadMarkersTask, private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, @UserId private val userId: String, - private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource, + private val matrixCoroutineDispatchers: MatrixCoroutineDispatchers, ) : ReadService { @AssistedFactory @@ -66,7 +69,7 @@ internal class DefaultReadService @AssistedInject constructor( setReadMarkersTask.execute(taskParams) } - override suspend fun setReadReceipt(eventId: String, threadId: String) { + override suspend fun setReadReceipt(eventId: String, threadId: String) = withContext(matrixCoroutineDispatchers.io) { val readReceiptThreadId = if (homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.canUseThreadReadReceiptsAndNotifications == true) { threadId } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index 8e7592a8b4..5c44931009 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.room.read import com.zhuinden.monarchy.Monarchy import io.realm.Realm +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -64,9 +66,10 @@ internal class DefaultSetReadMarkersTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver, private val clock: Clock, private val homeServerCapabilitiesService: HomeServerCapabilitiesService, + private val coroutineDispatchers: MatrixCoroutineDispatchers, ) : SetReadMarkersTask { - override suspend fun execute(params: SetReadMarkersTask.Params) { + override suspend fun execute(params: SetReadMarkersTask.Params) = withContext(coroutineDispatchers.io) { val markers = mutableMapOf() Timber.v("Execute set read marker with params: $params") val latestSyncedEventId = latestSyncedEventId(params.roomId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index d29e7d8f36..7176e36e0c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -49,7 +49,7 @@ import org.matrix.android.sdk.api.util.CancelableBag import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.api.util.TextContent -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.session.content.UploadContentWorker @@ -69,7 +69,7 @@ internal class DefaultSendService @AssistedInject constructor( private val workManagerProvider: WorkManagerProvider, @SessionId private val sessionId: String, private val localEchoEventFactory: LocalEchoEventFactory, - private val cryptoStore: IMXCryptoStore, + private val cryptoStore: IMXCommonCryptoStore, private val taskExecutor: TaskExecutor, private val localEchoRepository: LocalEchoRepository, private val eventSenderProcessor: EventSenderProcessor, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt index 929fe7b9a6..9ce29c3c08 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.api.failure.isLimitExceededError import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.task.CoroutineSequencer import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer @@ -54,7 +54,7 @@ private const val MAX_RETRY_COUNT = 3 */ @SessionScope internal class EventSenderProcessorCoroutine @Inject constructor( - private val cryptoStore: IMXCryptoStore, + private val cryptoStore: IMXCommonCryptoStore, private val sessionParams: SessionParams, private val queuedTaskFactory: QueuedTaskFactory, private val taskExecutor: TaskExecutor, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt index 1bb86ecb4b..2c34f1e2d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt @@ -35,7 +35,12 @@ internal class DefaultSignOutService @Inject constructor( sessionParamsStore.updateCredentials(credentials) } - override suspend fun signOut(signOutFromHomeserver: Boolean) { - return signOutTask.execute(SignOutTask.Params(signOutFromHomeserver)) + override suspend fun signOut(signOutFromHomeserver: Boolean, ignoreServerRequestError: Boolean) { + return signOutTask.execute( + SignOutTask.Params( + signOutFromHomeserver = signOutFromHomeserver, + ignoreServerRequestError = ignoreServerRequestError + ) + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt index e5213c4696..f8ec23b24d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt @@ -30,7 +30,8 @@ import javax.inject.Inject internal interface SignOutTask : Task { data class Params( - val signOutFromHomeserver: Boolean + val signOutFromHomeserver: Boolean, + val ignoreServerRequestError: Boolean, ) } @@ -59,7 +60,9 @@ internal class DefaultSignOutTask @Inject constructor( // Ignore Timber.w("Ignore error due to https://github.com/matrix-org/synapse/issues/5755") } else { - throw throwable + if (!params.ignoreServerRequestError) { + throw throwable + } } } } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index 876109a2a3..cdc5973fa1 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -58,8 +58,8 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionD import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore +import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore +import org.matrix.android.sdk.internal.crypto.store.RustCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask @@ -246,7 +246,7 @@ internal abstract class CryptoModule { abstract fun bindVerificationService(service: RustVerificationService): VerificationService @Binds - abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore + abstract fun bindCryptoStore(store: RustCryptoStore): IMXCommonCryptoStore @Binds abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt index 2ff4d2d119..3686ab445d 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt @@ -54,6 +54,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 @@ -312,10 +313,10 @@ internal class OlmMachine @Inject constructor( /** * Used for lazy migration of inboundGroupSession from EA to ER */ - suspend fun importRoomKey(inbound: InboundGroupSessionHolder): Result { + suspend fun importRoomKey(inbound: MXInboundMegolmSessionWrapper): Result { Timber.v("Migration:: Tentative lazy migration") return withContext(coroutineDispatchers.io) { - val export = inbound.wrapper.exportKeys() + 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}") diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt index cce457f6a7..891e1fe3c0 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService import org.matrix.android.sdk.internal.crypto.network.RequestSender -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.rustcomponents.sdk.crypto.EncryptionSettings @@ -47,7 +47,7 @@ private val loggerTag = LoggerTag("PrepareToEncryptUseCase", LoggerTag.CRYPTO) internal class PrepareToEncryptUseCase @Inject constructor( private val olmMachine: OlmMachine, private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoStore: IMXCryptoStore, + private val cryptoStore: IMXCommonCryptoStore, private val getRoomUserIds: GetRoomUserIdsUseCase, private val requestSender: RequestSender, private val loadRoomMembersTask: LoadRoomMembersTask, diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt index 4769d57dc9..57f81ef592 100755 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.crypto import android.content.Context -import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData import androidx.lifecycle.map import androidx.paging.PagedList @@ -26,7 +25,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixCoroutineDispatchers @@ -76,7 +74,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.network.OutgoingRequestsProcessor import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask @@ -111,7 +109,7 @@ internal class RustCryptoService @Inject constructor( @UserId private val myUserId: String, @DeviceId private val deviceId: String, // the crypto store - private val cryptoStore: IMXCryptoStore, + private val cryptoStore: IMXCommonCryptoStore, // Set of parameters used to configure/customize the end-to-end crypto. private val mxCryptoConfig: MXCryptoConfig, // Actions @@ -185,12 +183,13 @@ internal class RustCryptoService @Inject constructor( override fun getCryptoVersion(context: Context, longFormat: Boolean): String { val version = org.matrix.rustcomponents.sdk.crypto.version() + val gitHash = org.matrix.rustcomponents.sdk.crypto.versionInfo().gitSha val vodozemac = org.matrix.rustcomponents.sdk.crypto.vodozemacVersion() - return if (longFormat) "Rust SDK $version, Vodozemac $vodozemac" else version + return if (longFormat) "Rust SDK $version ($gitHash), Vodozemac $vodozemac" else version } - override fun getMyCryptoDevice(): CryptoDeviceInfo { - return runBlocking { olmMachine.ownDevice() } + override suspend fun getMyCryptoDevice(): CryptoDeviceInfo = withContext(coroutineDispatchers.io) { + olmMachine.ownDevice() } override suspend fun fetchDevicesList(): List { @@ -342,11 +341,11 @@ internal class RustCryptoService @Inject constructor( */ override suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? { if (userId.isEmpty() || deviceId.isNullOrEmpty()) return null - return olmMachine.getCryptoDeviceInfo(userId, deviceId) + return withContext(coroutineDispatchers.io) { olmMachine.getCryptoDeviceInfo(userId, deviceId) } } - override fun getCryptoDeviceInfo(userId: String): List { - return runBlocking { + override suspend fun getCryptoDeviceInfo(userId: String): List { + return withContext(coroutineDispatchers.io) { olmMachine.getCryptoDeviceInfo(userId) } } @@ -360,9 +359,7 @@ internal class RustCryptoService @Inject constructor( } override fun getLiveCryptoDeviceInfo(userIds: List): LiveData> { - return olmMachine.getLiveDevices(listOf(myUserId)).map { - it.filter { it.userId == myUserId } - } + return olmMachine.getLiveDevices(userIds) } override fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData> { @@ -905,13 +902,6 @@ internal class RustCryptoService @Inject constructor( // TODO("Not yet implemented") } - /* ========================================================================================== - * For test only - * ========================================================================================== */ - - @VisibleForTesting - val cryptoStoreForTesting = cryptoStore - companion object { const val CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS = 3_600_000 // one hour } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt index df041bbf19..b5212ee45a 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt @@ -24,6 +24,7 @@ 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 @@ -36,7 +37,6 @@ 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.InboundGroupSessionStore 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 @@ -59,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 @@ -102,7 +103,7 @@ internal class RequestSender @Inject constructor( private val moshi: Moshi, cryptoCoroutineScope: CoroutineScope, private val rateLimiter: PerSessionBackupQueryRateLimiter, - private val inboundGroupSessionStore: InboundGroupSessionStore, + private val cryptoStore: IMXCommonCryptoStore, private val localEchoRepository: LocalEchoRepository, private val olmMachine: Lazy, ) { @@ -266,7 +267,9 @@ internal class RequestSender @Inject constructor( val senderKey = requestBody?.get("sender_key") as? String if (roomId != null && sessionId != null) { // try to perform a lazy migration from legacy store - val legacy = inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey.orEmpty()) + 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) } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/store/RustCryptoStore.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/store/RustCryptoStore.kt new file mode 100644 index 0000000000..b242a3ed34 --- /dev/null +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/store/RustCryptoStore.kt @@ -0,0 +1,387 @@ +/* + * Copyright 2023 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.store + +import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations +import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.kotlin.where +import kotlinx.coroutines.runBlocking +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +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.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoRoomInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.crypto.OlmMachine +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator +import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransaction +import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransactionAsync +import org.matrix.android.sdk.internal.crypto.store.db.doWithRealm +import org.matrix.android.sdk.internal.crypto.store.db.mapper.CryptoRoomInfoMapper +import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper +import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity +import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields +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.CryptoRoomEntityFields +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.OutgoingKeyRequestEntity +import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields +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 +import org.matrix.android.sdk.internal.di.DeviceId +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.util.time.Clock +import timber.log.Timber +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +private val loggerTag = LoggerTag("RealmCryptoStore", LoggerTag.CRYPTO) + +/** + * In the transition phase, the rust SDK is still using parts to the realm crypto store, + * this should be removed after full migration + */ +@SessionScope +internal class RustCryptoStore @Inject constructor( + @CryptoDatabase private val realmConfiguration: RealmConfiguration, + private val clock: Clock, + @UserId private val userId: String, + @DeviceId private val deviceId: String, + private val myDeviceLastSeenInfoEntityMapper: MyDeviceLastSeenInfoEntityMapper, + private val olmMachine: dagger.Lazy, + private val matrixCoroutineDispatchers: MatrixCoroutineDispatchers, +) : IMXCommonCryptoStore { + + // still needed on rust due to the global crypto settings + init { + // Ensure CryptoMetadataEntity is inserted in DB + doRealmTransaction("init", realmConfiguration) { realm -> + var currentMetadata = realm.where().findFirst() + + var deleteAll = false + + if (currentMetadata != null) { + // Check credentials + // The device id may not have been provided in credentials. + // Check it only if provided, else trust the stored one. + if (currentMetadata.userId != userId || deviceId != currentMetadata.deviceId) { + Timber.w("## open() : Credentials do not match, close this store and delete data") + deleteAll = true + currentMetadata = null + } + } + + if (currentMetadata == null) { + if (deleteAll) { + realm.deleteAll() + } + + // Metadata not found, or database cleaned, create it + realm.createObject(CryptoMetadataEntity::class.java, userId).apply { + deviceId = this@RustCryptoStore.deviceId + } + } + } + } + + /** + * Retrieve a device by its identity key. + * + * @param identityKey the device identity key (`MXDeviceInfo.identityKey`) + * @return the device or null if not found + */ + override fun deviceWithIdentityKey(userId: String, identityKey: String): CryptoDeviceInfo? { + // XXX make this suspendable? + val knownDevices = runBlocking(matrixCoroutineDispatchers.io) { + olmMachine.get().getUserDevices(userId) + } + return knownDevices + .map { it.toCryptoDeviceInfo() } + .firstOrNull { + it.identityKey() == identityKey + } + } + + /** + * 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() + .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) + .findFirst() + ?.toModel() + } + } + + // ================================================ + // Things that should be migrated to another store than realm + // ================================================ + + private val monarchyWriteAsyncExecutor = Executors.newSingleThreadExecutor() + + private val monarchy = Monarchy.Builder() + .setRealmConfiguration(realmConfiguration) + .setWriteAsyncExecutor(monarchyWriteAsyncExecutor) + .build() + + override fun open() { + // nop + } + + override fun tidyUpDataBase() { + // These entities are not used in rust actually, but as they are not yet cleaned up, this will do it with time + val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000 + doRealmTransaction("tidyUpDataBase", realmConfiguration) { realm -> + + // Clean the old ones? + realm.where() + .lessThan(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, prevWeekTs) + .findAll() + .also { Timber.i("## Crypto Clean up ${it.size} OutgoingKeyRequestEntity") } + .deleteAllFromRealm() + + // Only keep one month history + + val prevMonthTs = clock.epochMillis() - 4 * 7 * 24 * 60 * 60 * 1_000L + realm.where() + .lessThan(AuditTrailEntityFields.AGE_LOCAL_TS, prevMonthTs) + .findAll() + .also { Timber.i("## Crypto Clean up ${it.size} AuditTrailEntity") } + .deleteAllFromRealm() + + // Can we do something for WithHeldSessionEntity? + } + } + + override fun close() { + val tasks = monarchyWriteAsyncExecutor.shutdownNow() + Timber.w("Closing RealmCryptoStore, ${tasks.size} async task(s) cancelled") + tryOrNull("Interrupted") { + // Wait 1 minute max + monarchyWriteAsyncExecutor.awaitTermination(1, TimeUnit.MINUTES) + } + } + + override fun getRoomAlgorithm(roomId: String): String? { + return doWithRealm(realmConfiguration) { + CryptoRoomEntity.getById(it, roomId)?.algorithm + } + } + + override fun getRoomCryptoInfo(roomId: String): CryptoRoomInfo? { + return doWithRealm(realmConfiguration) { realm -> + CryptoRoomEntity.getById(realm, roomId)?.let { + CryptoRoomInfoMapper.map(it) + } + } + } + + /** + * This is a bit different than isRoomEncrypted. + * A room is encrypted when there is a m.room.encryption state event in the room (malformed/invalid or not). + * But the crypto layer has additional guaranty to ensure that encryption would never been reverted. + * It's defensive coding out of precaution (if ever state is reset). + */ + override fun roomWasOnceEncrypted(roomId: String): Boolean { + return doWithRealm(realmConfiguration) { + CryptoRoomEntity.getById(it, roomId)?.wasEncryptedOnce ?: false + } + } + + override fun setAlgorithmInfo(roomId: String, encryption: EncryptionEventContent?) { + doRealmTransaction("setAlgorithmInfo", realmConfiguration) { + CryptoRoomEntity.getOrCreate(it, roomId).let { entity -> + entity.algorithm = encryption?.algorithm + // store anyway the new algorithm, but mark the room + // as having been encrypted once whatever, this can never + // go back to false + if (encryption?.algorithm == MXCRYPTO_ALGORITHM_MEGOLM) { + entity.wasEncryptedOnce = true + entity.rotationPeriodMs = encryption.rotationPeriodMs + entity.rotationPeriodMsgs = encryption.rotationPeriodMsgs + } + } + } + } + + override fun saveMyDevicesInfo(info: List) { + val entities = info.map { myDeviceLastSeenInfoEntityMapper.map(it) } + doRealmTransactionAsync(realmConfiguration) { realm -> + realm.where().findAll().deleteAllFromRealm() + entities.forEach { + realm.insertOrUpdate(it) + } + } + } + + override fun getMyDevicesInfo(): List { + return monarchy.fetchAllCopiedSync { + it.where() + }.map { + DeviceInfo( + deviceId = it.deviceId, + lastSeenIp = it.lastSeenIp, + lastSeenTs = it.lastSeenTs, + displayName = it.displayName + ) + } + } + + override fun getLiveMyDevicesInfo(): LiveData> { + return monarchy.findAllMappedWithChanges( + { realm: Realm -> + realm.where() + }, + { entity -> myDeviceLastSeenInfoEntityMapper.map(entity) } + ) + } + + override fun getLiveMyDevicesInfo(deviceId: String): LiveData> { + val liveData = monarchy.findAllMappedWithChanges( + { realm: Realm -> + realm.where() + .equalTo(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, deviceId) + }, + { entity -> myDeviceLastSeenInfoEntityMapper.map(entity) } + ) + + return Transformations.map(liveData) { + it.firstOrNull().toOptional() + } + } + + override fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) { + if (cryptoStoreAggregator.isEmpty()) { + return + } + doRealmTransaction("storeData - CryptoStoreAggregator", realmConfiguration) { realm -> + // setShouldShareHistory + cryptoStoreAggregator.setShouldShareHistoryData.forEach { + Timber.tag(loggerTag.value) + .v("setShouldShareHistory for room ${it.key} is ${it.value}") + CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value + } + // setShouldEncryptForInvitedMembers + cryptoStoreAggregator.setShouldEncryptForInvitedMembersData.forEach { + CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value + } + } + } + + override fun shouldEncryptForInvitedMembers(roomId: String): Boolean { + return doWithRealm(realmConfiguration) { + CryptoRoomEntity.getById(it, roomId)?.shouldEncryptForInvitedMembers + } + ?: false + } + + override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { + Timber.tag(loggerTag.value) + .v("setShouldShareHistory for room $roomId is $shouldShareHistory") + doRealmTransaction("setShouldShareHistory", realmConfiguration) { + CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory + } + } + + override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { + doRealmTransaction("setShouldEncryptForInvitedMembers", realmConfiguration) { + CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers + } + } + + override fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) { + doRealmTransaction("blockUnverifiedDevicesInRoom", realmConfiguration) { realm -> + CryptoRoomEntity.getById(realm, roomId) + ?.blacklistUnverifiedDevices = block + } + } + + override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) { + doRealmTransaction("setGlobalBlacklistUnverifiedDevices", realmConfiguration) { + it.where().findFirst()?.globalBlacklistUnverifiedDevices = block + } + } + + override fun getLiveGlobalCryptoConfig(): LiveData { + val liveData = monarchy.findAllMappedWithChanges( + { realm: Realm -> + realm + .where() + }, + { + GlobalCryptoConfig( + globalBlockUnverifiedDevices = it.globalBlacklistUnverifiedDevices, + globalEnableKeyGossiping = it.globalEnableKeyGossiping, + enableKeyForwardingOnInvite = it.enableKeyForwardingOnInvite + ) + } + ) + return Transformations.map(liveData) { + it.firstOrNull() ?: GlobalCryptoConfig(false, false, false) + } + } + + override fun getGlobalBlacklistUnverifiedDevices(): Boolean { + return doWithRealm(realmConfiguration) { + it.where().findFirst()?.globalBlacklistUnverifiedDevices + } ?: false + } + + override fun getLiveBlockUnverifiedDevices(roomId: String): LiveData { + val liveData = monarchy.findAllMappedWithChanges( + { realm: Realm -> + realm.where() + .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() + .equalTo(CryptoRoomEntityFields.ROOM_ID, roomId) + .findFirst() + ?.blacklistUnverifiedDevices ?: false + } + } +} diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/store/db/migration/rust/ExtractUtils.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/store/db/migration/rust/ExtractUtils.kt index 608f68fc3d..d99403fe19 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/store/db/migration/rust/ExtractUtils.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/store/db/migration/rust/ExtractUtils.kt @@ -71,6 +71,10 @@ fun RealmToMigrate.getPickledAccount(pickleKey: ByteArray): MigrationData { val userKey = metadataEntity.xSignUserPrivateKey val selfSignedKey = metadataEntity.xSignSelfSignedPrivateKey + Timber.i("## Migration: has private MSK ${masterKey.isNullOrBlank().not()}") + Timber.i("## Migration: has private USK ${userKey.isNullOrBlank().not()}") + Timber.i("## Migration: has private SSK ${selfSignedKey.isNullOrBlank().not()}") + val userId = metadataEntity.userId ?: throw java.lang.IllegalArgumentException("Rust db migration: userId is null") val deviceId = metadataEntity.deviceId @@ -79,6 +83,8 @@ fun RealmToMigrate.getPickledAccount(pickleKey: ByteArray): MigrationData { val backupVersion = metadataEntity.backupVersion val backupRecoveryKey = metadataEntity.keyBackupRecoveryKey + Timber.i("## Migration: has private backup key ${backupRecoveryKey != null} for version $backupVersion") + val isOlmAccountShared = metadataEntity.deviceKeysSentToServer val olmAccount = metadataEntity.getOlmAccount() diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt index 8cb6618a18..35965d6f2e 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt @@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE import org.matrix.android.sdk.internal.crypto.model.rest.toValue import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.rustcomponents.sdk.crypto.VerificationRequestState import timber.log.Timber import javax.inject.Inject @@ -165,7 +166,7 @@ internal class RustVerificationService @Inject constructor( // If this is a SAS verification originating from a `m.key.verification.request` // event, we auto-accept here considering that we either initiated the request or // accepted the request. If it's a QR code verification, just dispatch an update. - if (request.isReady() && transaction is SasVerification) { + if (request.innerState() is VerificationRequestState.Ready && transaction is SasVerification) { // accept() will dispatch an update, no need to do it twice. Timber.d("## Verification: Auto accepting SAS verification with $sender") transaction.accept() @@ -308,7 +309,7 @@ internal class RustVerificationService @Inject constructor( return if (request != null) { request.acceptWithMethods(methods) request.startQrCode() - request.isReady() + request.innerState() is VerificationRequestState.Ready } else { false } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationRequest.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationRequest.kt index 8d1b3392aa..641bf66c12 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationRequest.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationRequest.kt @@ -136,9 +136,9 @@ internal class VerificationRequest @AssistedInject constructor( * concrete verification flow, i.e. we can show/scan a QR code or start emoji * verification. */ - internal fun isReady(): Boolean { - return innerVerificationRequest.isReady() - } +// internal fun isReady(): Boolean { +// return innerVerificationRequest.isReady() +// } /** Did we advertise that we're able to scan QR codes */ internal fun canScanQrCodes(): Boolean { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventEditValidatorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventEditValidatorTest.kt index 0ae712bff1..113dc4ce83 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventEditValidatorTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventEditValidatorTest.kt @@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult 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.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore class EventEditValidatorTest { @@ -62,7 +62,7 @@ class EventEditValidatorTest { @Test fun `edit should be valid`() { - val mockCryptoStore = mockk() + val mockCryptoStore = mockk() val validator = EventEditValidator(mockCryptoStore) validator @@ -71,7 +71,7 @@ class EventEditValidatorTest { @Test fun `original event and replacement event must have the same sender`() { - val mockCryptoStore = mockk() + val mockCryptoStore = mockk() val validator = EventEditValidator(mockCryptoStore) validator @@ -83,7 +83,7 @@ class EventEditValidatorTest { @Test fun `original event and replacement event must have the same room_id`() { - val mockCryptoStore = mockk() + val mockCryptoStore = mockk() val validator = EventEditValidator(mockCryptoStore) validator @@ -101,7 +101,7 @@ class EventEditValidatorTest { @Test fun `replacement and original events must not have a state_key property`() { - val mockCryptoStore = mockk() + val mockCryptoStore = mockk() val validator = EventEditValidator(mockCryptoStore) validator @@ -119,8 +119,8 @@ class EventEditValidatorTest { @Test fun `replacement event must have an new_content property`() { - val mockCryptoStore = mockk { - every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns mockk { every { userId } returns "@alice:example.com" } @@ -157,8 +157,8 @@ class EventEditValidatorTest { @Test fun `The original event must not itself have a rel_type of m_replace`() { - val mockCryptoStore = mockk { - every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns mockk { every { userId } returns "@alice:example.com" } @@ -207,8 +207,8 @@ class EventEditValidatorTest { @Test fun `valid e2ee edit`() { - val mockCryptoStore = mockk { - every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns mockk { every { userId } returns "@alice:example.com" } @@ -224,8 +224,8 @@ class EventEditValidatorTest { @Test fun `If the original event was encrypted, the replacement should be too`() { - val mockCryptoStore = mockk { - every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns mockk { every { userId } returns "@alice:example.com" } @@ -241,12 +241,12 @@ class EventEditValidatorTest { @Test fun `encrypted, original event and replacement event must have the same sender`() { - val mockCryptoStore = mockk { - every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns mockk { every { userId } returns "@alice:example.com" } - every { deviceWithIdentityKey("7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns + every { deviceWithIdentityKey("@bob:example.com", "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns mockk { every { userId } returns "@bob:example.com" } @@ -256,7 +256,9 @@ class EventEditValidatorTest { validator .validateEdit( encryptedEvent, - encryptedEditEvent.copy().apply { + encryptedEditEvent.copy( + senderId = "@bob:example.com" + ).apply { mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy( senderKey = "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI" ) @@ -269,12 +271,12 @@ class EventEditValidatorTest { @Test fun `encrypted, sent fom a deleted device, original event and replacement event must have the same sender`() { - val mockCryptoStore = mockk { - every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns mockk { every { userId } returns "@alice:example.com" } - every { deviceWithIdentityKey("7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns + every { deviceWithIdentityKey(any(), "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns null } val validator = EventEditValidator(mockCryptoStore) @@ -288,7 +290,7 @@ class EventEditValidatorTest { ) } - ) shouldBeInstanceOf EventEditValidator.EditValidity.Valid::class + ) shouldBeInstanceOf EventEditValidator.EditValidity.Unknown::class validator .validateEdit( diff --git a/tools/release/releaseScript.sh b/tools/release/releaseScript.sh index cf9671c1dc..f198670eae 100755 --- a/tools/release/releaseScript.sh +++ b/tools/release/releaseScript.sh @@ -160,11 +160,11 @@ adb -e uninstall im.vector.app.debug.test printf "\n================================================================================\n" printf "Running the integration test UiAllScreensSanityTest.allScreensTest()...\n" -./gradlew connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest +./gradlew connectedGplayRustCryptoDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest printf "\n================================================================================\n" printf "Building the app...\n" -./gradlew assembleGplayDebug +./gradlew assembleGplayRustCryptoDebug printf "\n================================================================================\n" printf "Uninstalling previous debug app if any...\n" @@ -172,7 +172,7 @@ adb -e uninstall im.vector.app.debug printf "\n================================================================================\n" printf "Installing the app...\n" -adb -e install ./vector-app/build/outputs/apk/gplay/debug/vector-gplay-arm64-v8a-debug.apk +adb -e install ./vector-app/build/outputs/apk/gplayRustCrypto/debug/vector-gplay-rustCrypto-arm64-v8a-debug.apk printf "\n================================================================================\n" printf "Running the app...\n" @@ -293,67 +293,67 @@ printf "Unzipping the artifact...\n" unzip ${targetPath}/vector-gplay-release-unsigned.zip -d ${targetPath} # Flatten folder hierarchy -mv ${targetPath}/gplay/release/* ${targetPath} +mv ${targetPath}/gplayRustCrypto/release/* ${targetPath} rm -rf ${targetPath}/gplay printf "\n================================================================================\n" printf "Signing the APKs...\n" -cp ${targetPath}/vector-gplay-arm64-v8a-release-unsigned.apk \ - ${targetPath}/vector-gplay-arm64-v8a-release-signed.apk +cp ${targetPath}/vector-gplay-rustCrypto-arm64-v8a-release-unsigned.apk \ + ${targetPath}/vector-gplay-rustCrypto-arm64-v8a-release-signed.apk ./tools/release/sign_apk_unsafe.sh \ ${keyStorePath} \ - ${targetPath}/vector-gplay-arm64-v8a-release-signed.apk \ + ${targetPath}/vector-gplay-rustCrypto-arm64-v8a-release-signed.apk \ ${keyStorePassword} \ ${keyPassword} -cp ${targetPath}/vector-gplay-armeabi-v7a-release-unsigned.apk \ - ${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk +cp ${targetPath}/vector-gplay-rustCrypto-armeabi-v7a-release-unsigned.apk \ + ${targetPath}/vector-gplay-rustCrypto-armeabi-v7a-release-signed.apk ./tools/release/sign_apk_unsafe.sh \ ${keyStorePath} \ - ${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk \ + ${targetPath}/vector-gplay-rustCrypto-armeabi-v7a-release-signed.apk \ ${keyStorePassword} \ ${keyPassword} -cp ${targetPath}/vector-gplay-x86-release-unsigned.apk \ - ${targetPath}/vector-gplay-x86-release-signed.apk +cp ${targetPath}/vector-gplay-rustCrypto-x86-release-unsigned.apk \ + ${targetPath}/vector-gplay-rustCrypto-x86-release-signed.apk ./tools/release/sign_apk_unsafe.sh \ ${keyStorePath} \ - ${targetPath}/vector-gplay-x86-release-signed.apk \ + ${targetPath}/vector-gplay-rustCrypto-x86-release-signed.apk \ ${keyStorePassword} \ ${keyPassword} -cp ${targetPath}/vector-gplay-x86_64-release-unsigned.apk \ - ${targetPath}/vector-gplay-x86_64-release-signed.apk +cp ${targetPath}/vector-gplay-rustCrypto-x86_64-release-unsigned.apk \ + ${targetPath}/vector-gplay-rustCrypto-x86_64-release-signed.apk ./tools/release/sign_apk_unsafe.sh \ ${keyStorePath} \ - ${targetPath}/vector-gplay-x86_64-release-signed.apk \ + ${targetPath}/vector-gplay-rustCrypto-x86_64-release-signed.apk \ ${keyStorePassword} \ ${keyPassword} # Ref: https://docs.fastlane.tools/getting-started/android/beta-deployment/#uploading-your-app -# set SUPPLY_APK_PATHS="${targetPath}/vector-gplay-arm64-v8a-release-unsigned.apk,${targetPath}/vector-gplay-armeabi-v7a-release-unsigned.apk,${targetPath}/vector-gplay-x86-release-unsigned.apk,${targetPath}/vector-gplay-x86_64-release-unsigned.apk" +# set SUPPLY_APK_PATHS="${targetPath}/vector-gplay-rustCrypto-arm64-v8a-release-unsigned.apk,${targetPath}/vector-gplay-rustCrypto-armeabi-v7a-release-unsigned.apk,${targetPath}/vector-gplay-rustCrypto-x86-release-unsigned.apk,${targetPath}/vector-gplay-rustCrypto-x86_64-release-unsigned.apk" # # ./fastlane beta printf "\n================================================================================\n" printf "Please check the information below:\n" -printf "File vector-gplay-arm64-v8a-release-signed.apk:\n" -${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-arm64-v8a-release-signed.apk | grep package -printf "File vector-gplay-armeabi-v7a-release-signed.apk:\n" -${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk | grep package -printf "File vector-gplay-x86-release-signed.apk:\n" -${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-x86-release-signed.apk | grep package -printf "File vector-gplay-x86_64-release-signed.apk:\n" -${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-x86_64-release-signed.apk | grep package +printf "File vector-gplay-rustCrypto-arm64-v8a-release-signed.apk:\n" +${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-rustCrypto-arm64-v8a-release-signed.apk | grep package +printf "File vector-gplay-rustCrypto-armeabi-v7a-release-signed.apk:\n" +${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-rustCrypto-armeabi-v7a-release-signed.apk | grep package +printf "File vector-gplay-rustCrypto-x86-release-signed.apk:\n" +${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-rustCrypto-x86-release-signed.apk | grep package +printf "File vector-gplay-rustCrypto-x86_64-release-signed.apk:\n" +${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-rustCrypto-x86_64-release-signed.apk | grep package printf "\n" read -p "Does it look correct? Press enter when it's done." printf "\n================================================================================\n" read -p "Installing apk on a real device, press enter when a real device is connected. " -apkPath="${targetPath}/vector-gplay-arm64-v8a-release-signed.apk" +apkPath="${targetPath}/vector-gplay-rustCrypto-arm64-v8a-release-signed.apk" adb -d install ${apkPath} read -p "Please run the APK on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done." diff --git a/vector-app/build.gradle b/vector-app/build.gradle index 2abf6526d2..9a2506391e 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -33,11 +33,11 @@ knit { // Note: 2 digits max for each value ext.versionMajor = 1 -ext.versionMinor = 5 +ext.versionMinor = 6 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 34 +ext.versionPatch = 2 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' diff --git a/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt b/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt index cafc2d65e6..313073da4d 100644 --- a/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt +++ b/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt @@ -17,6 +17,7 @@ package im.vector.app.gplay.features.settings.troubleshoot import androidx.fragment.app.FragmentActivity import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope import androidx.work.WorkInfo import androidx.work.WorkManager import im.vector.app.R @@ -25,6 +26,8 @@ import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager import im.vector.app.core.resources.StringProvider import im.vector.app.features.settings.troubleshoot.TroubleshootTest +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.pushers.PusherState import javax.inject.Inject @@ -60,16 +63,18 @@ class TestTokenRegistration @Inject constructor( ) quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_token_registration_quick_fix) { override fun doFix() { - val workId = pushersManager.enqueueRegisterPusherWithFcmKey(fcmToken) - WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo -> - if (workInfo != null) { - if (workInfo.state == WorkInfo.State.SUCCEEDED) { - manager?.retry(testParameters) - } else if (workInfo.state == WorkInfo.State.FAILED) { - manager?.retry(testParameters) + context.lifecycleScope.launch(Dispatchers.IO) { + val workId = pushersManager.enqueueRegisterPusherWithFcmKey(fcmToken) + WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo -> + if (workInfo != null) { + if (workInfo.state == WorkInfo.State.SUCCEEDED) { + manager?.retry(testParameters) + } else if (workInfo.state == WorkInfo.State.FAILED) { + manager?.retry(testParameters) + } } - } - }) + }) + } } } diff --git a/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt b/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt index 53e65f88b4..676cd0258f 100755 --- a/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt +++ b/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt @@ -26,8 +26,11 @@ import dagger.hilt.android.qualifiers.ApplicationContext import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.DefaultPreferences +import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -38,7 +41,12 @@ import javax.inject.Inject class GoogleFcmHelper @Inject constructor( @ApplicationContext private val context: Context, @DefaultPreferences private val sharedPrefs: SharedPreferences, + appScope: CoroutineScope, + private val coroutineDispatchers: CoroutineDispatchers ) : FcmHelper { + + private val scope = CoroutineScope(appScope.coroutineContext + coroutineDispatchers.io) + companion object { private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" } @@ -64,7 +72,9 @@ class GoogleFcmHelper @Inject constructor( .addOnSuccessListener { token -> storeFcmToken(token) if (registerPusher) { - pushersManager.enqueueRegisterPusherWithFcmKey(token) + scope.launch { + pushersManager.enqueueRegisterPusherWithFcmKey(token) + } } } .addOnFailureListener { e -> diff --git a/vector-app/src/gplay/java/im/vector/app/push/fcm/VectorFirebaseMessagingService.kt b/vector-app/src/gplay/java/im/vector/app/push/fcm/VectorFirebaseMessagingService.kt index 7fd55bd165..6ab9b90a84 100644 --- a/vector-app/src/gplay/java/im/vector/app/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector-app/src/gplay/java/im/vector/app/push/fcm/VectorFirebaseMessagingService.kt @@ -27,6 +27,10 @@ import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.pushers.VectorPushHandler import im.vector.app.features.settings.VectorPreferences +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.logger.LoggerTag import timber.log.Timber import javax.inject.Inject @@ -43,6 +47,12 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { @Inject lateinit var vectorPushHandler: VectorPushHandler @Inject lateinit var unifiedPushHelper: UnifiedPushHelper + private val scope = CoroutineScope(SupervisorJob()) + + override fun onDestroy() { + scope.cancel() + super.onDestroy() + } override fun onNewToken(token: String) { Timber.tag(loggerTag.value).d("New Firebase token") fcmHelper.storeFcmToken(token) @@ -51,7 +61,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { activeSessionHolder.hasActiveSession() && unifiedPushHelper.isEmbeddedDistributor() ) { - pushersManager.enqueueRegisterPusher(token, getString(R.string.pusher_http_url)) + scope.launch { + pushersManager.enqueueRegisterPusher(token, getString(R.string.pusher_http_url)) + } } } diff --git a/vector/build.gradle b/vector/build.gradle index a5f368ff9d..dab8b8bdeb 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -311,7 +311,7 @@ dependencies { // Fix issue with Jitsi. Inspired from https://github.com/android/android-test/issues/861#issuecomment-872067868 // Error was lots of `Duplicate class org.checkerframework.common.reflection.qual.MethodVal found in modules jetified-checker-3.1 (org.checkerframework:checker:3.1.1) and jetified-checker-qual-3.12.0 (org.checkerframework:checker-qual:3.12.0) //noinspection GradleDependency Cannot use latest 3.15.0 since it required min API 26. - implementation "org.checkerframework:checker:3.34.0" + implementation "org.checkerframework:checker:3.35.0" androidTestImplementation libs.androidx.testCore androidTestImplementation libs.androidx.testRunner diff --git a/vector/src/main/java/im/vector/app/core/device/GetDeviceInfoUseCase.kt b/vector/src/main/java/im/vector/app/core/device/GetDeviceInfoUseCase.kt index 4a66988b46..a97fa32a61 100644 --- a/vector/src/main/java/im/vector/app/core/device/GetDeviceInfoUseCase.kt +++ b/vector/src/main/java/im/vector/app/core/device/GetDeviceInfoUseCase.kt @@ -22,14 +22,14 @@ import javax.inject.Inject interface GetDeviceInfoUseCase { - fun execute(): CryptoDeviceInfo + suspend fun execute(): CryptoDeviceInfo } class DefaultGetDeviceInfoUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder ) : GetDeviceInfoUseCase { - override fun execute(): CryptoDeviceInfo { + override suspend fun execute(): CryptoDeviceInfo { return activeSessionHolder.getActiveSession().cryptoService().getMyCryptoDevice() } } diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index fb78feaef1..402471ecef 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -49,11 +49,11 @@ class PushersManager @Inject constructor( ) } - fun enqueueRegisterPusherWithFcmKey(pushKey: String): UUID { + suspend fun enqueueRegisterPusherWithFcmKey(pushKey: String): UUID { return enqueueRegisterPusher(pushKey, stringProvider.getString(R.string.pusher_http_url)) } - fun enqueueRegisterPusher( + suspend fun enqueueRegisterPusher( pushKey: String, gateway: String ): UUID { @@ -62,7 +62,7 @@ class PushersManager @Inject constructor( return currentSession.pushersService().enqueueAddHttpPusher(pusher) } - private fun createHttpPusher( + private suspend fun createHttpPusher( pushKey: String, gateway: String ) = HttpPusher( diff --git a/vector/src/main/java/im/vector/app/core/pushers/VectorUnifiedPushMessagingReceiver.kt b/vector/src/main/java/im/vector/app/core/pushers/VectorUnifiedPushMessagingReceiver.kt index 838cbd5935..f0e5cd2c2c 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/VectorUnifiedPushMessagingReceiver.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/VectorUnifiedPushMessagingReceiver.kt @@ -76,7 +76,9 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() { coroutineScope.launch { unifiedPushHelper.storeCustomOrDefaultGateway(endpoint) { unifiedPushHelper.getPushGateway()?.let { - pushersManager.enqueueRegisterPusher(endpoint, it) + coroutineScope.launch { + pushersManager.enqueueRegisterPusher(endpoint, it) + } } } } diff --git a/vector/src/main/java/im/vector/app/core/session/clientinfo/DeleteUnusedClientInformationUseCase.kt b/vector/src/main/java/im/vector/app/core/session/clientinfo/DeleteUnusedClientInformationUseCase.kt index dcd5c58480..cc5dc6725d 100644 --- a/vector/src/main/java/im/vector/app/core/session/clientinfo/DeleteUnusedClientInformationUseCase.kt +++ b/vector/src/main/java/im/vector/app/core/session/clientinfo/DeleteUnusedClientInformationUseCase.kt @@ -17,6 +17,7 @@ package im.vector.app.core.session.clientinfo import im.vector.app.core.di.ActiveSessionHolder +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import javax.inject.Inject @@ -27,16 +28,19 @@ class DeleteUnusedClientInformationUseCase @Inject constructor( suspend fun execute(deviceInfoList: List): Result = runCatching { // A defensive approach against local storage reports an empty device list (although it is not a seen situation). if (deviceInfoList.isEmpty()) return Result.success(Unit) - - val expectedClientInfoKeyList = deviceInfoList.map { MATRIX_CLIENT_INFO_KEY_PREFIX + it.deviceId } - activeSessionHolder - .getSafeActiveSession() - ?.accountDataService() - ?.getUserAccountDataEventsStartWith(MATRIX_CLIENT_INFO_KEY_PREFIX) - ?.map { it.type } - ?.subtract(expectedClientInfoKeyList.toSet()) - ?.forEach { userAccountDataKeyToDelete -> - activeSessionHolder.getSafeActiveSession()?.accountDataService()?.deleteUserAccountData(userAccountDataKeyToDelete) - } + val dispatcher = activeSessionHolder.getSafeActiveSession()?.coroutineDispatchers?.io + ?: return@runCatching + withContext(dispatcher) { + val expectedClientInfoKeyList = deviceInfoList.map { MATRIX_CLIENT_INFO_KEY_PREFIX + it.deviceId } + activeSessionHolder + .getSafeActiveSession() + ?.accountDataService() + ?.getUserAccountDataEventsStartWith(MATRIX_CLIENT_INFO_KEY_PREFIX) + ?.map { it.type } + ?.subtract(expectedClientInfoKeyList.toSet()) + ?.forEach { userAccountDataKeyToDelete -> + activeSessionHolder.getSafeActiveSession()?.accountDataService()?.deleteUserAccountData(userAccountDataKeyToDelete) + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index cffb1577cf..48fe092524 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -29,6 +29,7 @@ import com.airbnb.mvrx.viewModel import com.bumptech.glide.Glide import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.extensions.startSyncing import im.vector.app.core.extensions.vectorStore @@ -59,6 +60,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.failure.GlobalError +import org.matrix.android.sdk.api.session.Session import timber.log.Timber import javax.inject.Inject @@ -170,6 +172,19 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity } private fun handleAppStarted() { + // On the first run with rust crypto this would be false + if (!vectorPreferences.isOnRustCrypto()) { + if (activeSessionHolder.hasActiveSession()) { + vectorPreferences.setHadExistingLegacyData(activeSessionHolder.getActiveSession().isOpenable) + } else { + vectorPreferences.setHadExistingLegacyData(false) + } + } + + if (BuildConfig.FLAVOR == "rustCrypto") { + vectorPreferences.setIsOnRustCrypto(true) + } + if (intent.hasExtra(EXTRA_NEXT_INTENT)) { // Start the next Activity startSyncing() @@ -248,18 +263,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity } } args.clearCredentials -> { - lifecycleScope.launch { - try { - session.signOutService().signOut(!args.isUserLoggedOut) - } catch (failure: Throwable) { - displayError(failure) - return@launch - } - Timber.w("SIGN_OUT: success, start app") - activeSessionHolder.clearActiveSession() - doLocalCleanup(clearPreferences = true, onboardingStore) - startNextActivityAndFinish() - } + signout(session, onboardingStore, ignoreServerError = false) } args.clearCache -> { lifecycleScope.launch { @@ -272,6 +276,26 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity } } + private fun signout( + session: Session, + onboardingStore: VectorSessionStore, + ignoreServerError: Boolean, + ) { + lifecycleScope.launch { + try { + session.signOutService().signOut(!args.isUserLoggedOut, ignoreServerError) + } catch (failure: Throwable) { + Timber.e(failure, "SIGN_OUT: error, propose to sign out anyway") + displaySignOutFailedDialog(session, onboardingStore) + return@launch + } + Timber.w("SIGN_OUT: success, start app") + activeSessionHolder.clearActiveSession() + doLocalCleanup(clearPreferences = true, onboardingStore) + startNextActivityAndFinish() + } + } + override fun handleInvalidToken(globalError: GlobalError.InvalidToken) { // No op here Timber.w("Ignoring invalid token global error") @@ -299,12 +323,20 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity } } - private fun displayError(failure: Throwable) { + private fun displaySignOutFailedDialog( + session: Session, + onboardingStore: VectorSessionStore, + ) { if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { MaterialAlertDialogBuilder(this) .setTitle(R.string.dialog_title_error) - .setMessage(errorFormatter.toHumanReadable(failure)) - .setPositiveButton(R.string.global_retry) { _, _ -> doCleanUp() } + .setMessage(R.string.sign_out_failed_dialog_message) + .setPositiveButton(R.string.sign_out_anyway) { _, _ -> + signout(session, onboardingStore, ignoreServerError = true) + } + .setNeutralButton(R.string.global_retry) { _, _ -> + signout(session, onboardingStore, ignoreServerError = false) + } .setNegativeButton(R.string.action_cancel) { _, _ -> startNextActivityAndFinish(ignoreClearCredentials = true) } .setCancelable(false) .show() diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt index 66344107a4..d060e3996d 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt @@ -52,7 +52,7 @@ class SharedSecuredStorageResetAllFragment : views.ssssResetOtherDevices.debouncedClicks { withState(sharedViewModel) { - DeviceListBottomSheet.newInstance(it.userId, false).show(childFragmentManager, "DEV_LIST") + DeviceListBottomSheet.newInstance(it.userId).show(childFragmentManager, "DEV_LIST") } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationController.kt index 4097cf957b..9871d21601 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationController.kt @@ -275,7 +275,7 @@ class SelfVerificationController @Inject constructor( id("notice_div") } // Option to verify with another device - if (state.hasAnyOtherSession) { + if (state.hasAnyOtherSession.invoke() == true) { bottomSheetVerificationActionItem { id("start") title(host.stringProvider.getString(R.string.verification_verify_with_another_device)) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationViewModel.kt index e92616f7df..55d84a2446 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationViewModel.kt @@ -83,7 +83,7 @@ data class SelfVerificationViewState( val transactionId: String? = null, val currentDeviceCanCrossSign: Boolean = false, val userWantsToCancel: Boolean = false, - val hasAnyOtherSession: Boolean = false, + val hasAnyOtherSession: Async = Uninitialized, val quadSContainsSecrets: Boolean = false, val isVerificationRequired: Boolean = false, val isThisSessionVerified: Boolean = false, @@ -146,21 +146,28 @@ class SelfVerificationViewModel @AssistedInject constructor( } } - val hasAnyOtherSession = session.cryptoService() - .getCryptoDeviceInfo(session.myUserId) - .any { - it.deviceId != session.sessionParams.deviceId - } + setState { copy(hasAnyOtherSession = Loading()) } + viewModelScope.launch { + val hasAnyOtherSession = session.cryptoService() + .getCryptoDeviceInfo(session.myUserId) + .any { + it.deviceId != session.sessionParams.deviceId + } + setState { + copy( + hasAnyOtherSession = Success(hasAnyOtherSession) + ) + } + } setState { copy( currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(), quadSContainsSecrets = session.sharedSecretStorageService().isRecoverySetup(), - hasAnyOtherSession = hasAnyOtherSession ) } - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { val isThisSessionVerified = session.cryptoService().crossSigningService().isCrossSigningVerified() setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 42eb200887..428e62724b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -469,11 +469,21 @@ class HomeActivity : private fun handleOnNewSession(event: HomeActivityViewEvents.CurrentSessionNotVerified) { // We need to ask + val titleRes = if (event.afterMigration) { + R.string.crosssigning_verify_after_update + } else { + R.string.crosssigning_verify_this_session + } + val descRes = if (event.afterMigration) { + R.string.confirm_your_identity_after_update + } else { + R.string.confirm_your_identity + } promptSecurityEvent( uid = PopupAlertManager.VERIFY_SESSION_UID, userItem = event.userItem, - titleRes = R.string.crosssigning_verify_this_session, - descRes = R.string.confirm_your_identity, + titleRes = titleRes, + descRes = descRes, ) { it.navigator.requestSelfSessionVerification(it) } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt index 5f8d9cdc51..c57f32694b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt @@ -23,7 +23,7 @@ sealed interface HomeActivityViewEvents : VectorViewEvents { data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents data class CurrentSessionNotVerified( val userItem: MatrixItem.UserItem, -// val waitForIncomingRequest: Boolean = true, + val afterMigration: Boolean ) : HomeActivityViewEvents data class CurrentSessionCannotBeVerified( diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 73bbcac070..eb4a796a49 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -453,6 +453,7 @@ class HomeActivityViewModel @AssistedInject constructor( _viewEvents.post( HomeActivityViewEvents.CurrentSessionNotVerified( session.getUserOrDefault(session.myUserId).toMatrixItem(), + vectorPreferences.isOnRustCrypto() && vectorPreferences.hadExistingLegacyData() ) ) } else { diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index 28016f109f..6ba5976eb8 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -92,11 +92,6 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor( } init { - val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId) - .firstOrNull { it.deviceId == session.sessionParams.deviceId } - ?.firstTimeSeenLocalTs - ?: clock.epochMillis() - Timber.v("## Detector - Current Session first time seen $currentSessionTs") combine( session.flow().liveUserCryptoDevices(session.myUserId), @@ -108,6 +103,12 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor( deleteUnusedClientInformation(infoList) + val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId) + .firstOrNull { it.deviceId == session.sessionParams.deviceId } + ?.firstTimeSeenLocalTs + ?: clock.epochMillis() + Timber.v("## Detector - Current Session first time seen $currentSessionTs") + infoList .asSequence() .filter { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 09b91cc4f1..5f8883fdfe 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -528,6 +528,8 @@ class MessageItemFactory @Inject constructor( ) val playable = messageContent.mimeType == MimeTypes.Gif + // don't show play button because detecting animated webp isn't possible via mimetype + val playableIfAutoplay = playable || messageContent.mimeType == MimeTypes.Webp return MessageImageVideoItem_() .attributes(attributes) @@ -549,7 +551,7 @@ class MessageItemFactory @Inject constructor( } } }.apply { - if (playable && vectorPreferences.autoplayAnimatedImages()) { + if (playableIfAutoplay && vectorPreferences.autoplayAnimatedImages()) { mode(ImageContentRenderer.Mode.ANIMATED_THUMBNAIL) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 74b55d435d..fad1ad613d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -322,11 +322,10 @@ class RoomListViewModel @AssistedInject constructor( } private fun handleDeleteLocalRooms() { - val localRoomIds = session.roomService() - .getRoomSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) }) - .map { it.roomId } - - viewModelScope.launch { + viewModelScope.launch(session.coroutineDispatchers.io) { + val localRoomIds = session.roomService() + .getRoomSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) }) + .map { it.roomId } localRoomIds.forEach { session.roomService().deleteLocalRoom(it) } diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 0924532bc2..a0354f3d60 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -49,7 +49,7 @@ class DataAttachmentRoomProvider( return getItem(position).let { when (it) { is ImageContentRenderer.Data -> { - if (it.mimeType == MimeTypes.Gif) { + if (it.mimeType == MimeTypes.Gif || it.mimeType == MimeTypes.Webp) { AttachmentInfo.AnimatedImage( uid = it.eventId, url = it.url ?: "", diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index baad815df2..876812cea0 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -135,7 +135,7 @@ class ImageContentRenderer @Inject constructor( if (mode == Mode.ANIMATED_THUMBNAIL) it else it.dontAnimate() } - .transform(cornerTransformation) + .optionalTransform(cornerTransformation) .into(imageView) } @@ -167,7 +167,7 @@ class ImageContentRenderer @Inject constructor( } req - .fitCenter() + .optionalFitCenter() .into(target) } @@ -211,7 +211,7 @@ class ImageContentRenderer @Inject constructor( return false } }) - .fitCenter() + .optionalFitCenter() .into(imageView) } diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index e18a13a3e6..f833a33b87 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -71,7 +71,7 @@ class RoomEventsAttachmentProvider( allowNonMxcUrls = it.root.sendState.isSending() ) - if (content.mimeType == MimeTypes.Gif) { + if (content.mimeType == MimeTypes.Gif || content.mimeType == MimeTypes.Webp) { AttachmentInfo.AnimatedImage( uid = it.eventId, url = content.url ?: "", diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index d2628fcf0f..b38805f05a 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -38,6 +38,7 @@ import im.vector.app.config.OnboardingVariant import im.vector.app.core.debug.DebugNavigator import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.fatalError +import im.vector.app.core.extensions.commitTransaction import im.vector.app.features.VectorFeatures import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsViewRoom @@ -256,8 +257,9 @@ class DefaultNavigator @Inject constructor( otherSessionId ) if (context is AppCompatActivity) { - SelfVerificationBottomSheet.forTransaction(request.transactionId) - .show(context.supportFragmentManager, "VERIF") + context.supportFragmentManager.commitTransaction(allowStateLoss = true) { + add(SelfVerificationBottomSheet.forTransaction(request.transactionId), "VERIF") + } } } } @@ -271,8 +273,9 @@ class DefaultNavigator @Inject constructor( // .filter { it.deviceId != session.sessionParams.deviceId } // .map { it.deviceId } if (context is AppCompatActivity) { - SelfVerificationBottomSheet.verifyOwnUntrustedDevice() - .show(context.supportFragmentManager, "VERIF") + context.supportFragmentManager.commitTransaction(allowStateLoss = true) { + add(SelfVerificationBottomSheet.verifyOwnUntrustedDevice(), "VERIF") + } // if (otherSessions.isNotEmpty()) { // val pr = session.cryptoService().verificationService().requestSelfKeyVerification( // supportedVerificationMethodsProvider.provide()) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 2d799034d9..61cb14a70f 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -24,7 +24,9 @@ import im.vector.app.R import im.vector.app.core.resources.BuildMeta import im.vector.app.core.utils.FirstThrottler import im.vector.app.features.displayname.getBestName +import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.getUserOrDefault @@ -121,11 +123,15 @@ class NotificationDrawerManager @Inject constructor( * Used to ignore events related to that room (no need to display notification) and clean any existing notification on this room. */ fun setCurrentRoom(roomId: String?) { - updateEvents { - val hasChanged = roomId != currentRoomId - currentRoomId = roomId - if (hasChanged && roomId != null) { - it.clearMessagesForRoom(roomId) + val dispatcher = currentSession?.coroutineDispatchers?.io ?: return + val scope = currentSession?.coroutineScope ?: return + scope.launch(dispatcher) { + updateEvents { + val hasChanged = roomId != currentRoomId + currentRoomId = roomId + if (hasChanged && roomId != null) { + it.clearMessagesForRoom(roomId) + } } } } @@ -135,12 +141,16 @@ class NotificationDrawerManager @Inject constructor( * Used to ignore events related to that thread (no need to display notification) and clean any existing notification on this room. */ fun setCurrentThread(threadId: String?) { - updateEvents { - val hasChanged = threadId != currentThreadId - currentThreadId = threadId - currentRoomId?.let { roomId -> - if (hasChanged && threadId != null) { - it.clearMessagesForThread(roomId, threadId) + val dispatcher = currentSession?.coroutineDispatchers?.io ?: return + val scope = currentSession?.coroutineScope ?: return + scope.launch(dispatcher) { + updateEvents { + val hasChanged = threadId != currentThreadId + currentThreadId = threadId + currentRoomId?.let { roomId -> + if (hasChanged && threadId != null) { + it.clearMessagesForThread(roomId, threadId) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListAction.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListAction.kt index c269a4166a..09ae73a55c 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListAction.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListAction.kt @@ -22,6 +22,4 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo sealed class DeviceListAction : VectorViewModelAction { data class SelectDevice(val device: CryptoDeviceInfo) : DeviceListAction() object DeselectDevice : DeviceListAction() - - data class ManuallyVerify(val deviceId: String) : DeviceListAction() } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt index 2a0ca4850e..63c44b4000 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt @@ -47,16 +47,7 @@ class DeviceListBottomSheet : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.observeViewEvents { - when (it) { - is DeviceListBottomSheetViewEvents.Verify -> { - // TODO selfverif -// VerificationBottomSheet.withArgs( -// // roomId = null, -// otherUserId = it.userId, -// transactionId = it.txID -// ).show(requireActivity().supportFragmentManager, "REQPOP") - } - } + // nop } } @@ -109,13 +100,12 @@ class DeviceListBottomSheet : @Parcelize data class Args( val userId: String, - val allowDeviceAction: Boolean ) : Parcelable companion object { - fun newInstance(userId: String, allowDeviceAction: Boolean = true): DeviceListBottomSheet { + fun newInstance(userId: String): DeviceListBottomSheet { return DeviceListBottomSheet().apply { - setArguments(Args(userId, allowDeviceAction)) + setArguments(Args(userId)) } } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewEvents.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewEvents.kt index 8c6cba6cd9..159f505e16 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewEvents.kt @@ -21,6 +21,4 @@ import im.vector.app.core.platform.VectorViewEvents /** * Transient events for device list screen. */ -sealed class DeviceListBottomSheetViewEvents : VectorViewEvents { - data class Verify(val userId: String, val txID: String) : DeviceListBottomSheetViewEvents() -} +sealed class DeviceListBottomSheetViewEvents : VectorViewEvents diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index c3281858f0..435c44aa11 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -34,7 +34,6 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem @@ -42,7 +41,7 @@ import org.matrix.android.sdk.flow.flow data class DeviceListViewState( val userId: String, - val allowDeviceAction: Boolean, + val myUserId: String, val userItem: MatrixItem? = null, val memberCrossSigningKey: MXCrossSigningInfo? = null, val myDeviceId: String = "", @@ -69,7 +68,7 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor( val session = EntryPoints.get(viewModelContext.app(), SingletonEntryPoint::class.java).activeSessionHolder().getActiveSession() return DeviceListViewState( userId = userId, - allowDeviceAction = args.allowDeviceAction, + myUserId = session.myUserId, userItem = session.getUserOrDefault(userId).toMatrixItem(), myDeviceId = session.sessionParams.deviceId, ) @@ -104,7 +103,6 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor( when (action) { is DeviceListAction.SelectDevice -> selectDevice(action) is DeviceListAction.DeselectDevice -> deselectDevice() - is DeviceListAction.ManuallyVerify -> manuallyVerify(action) } } @@ -121,7 +119,6 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor( } private fun selectDevice(action: DeviceListAction.SelectDevice) { - if (!initialState.allowDeviceAction) return setState { copy(selectedDevice = action.device) } @@ -132,18 +129,4 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor( copy(selectedDevice = null) } } - - private fun manuallyVerify(action: DeviceListAction.ManuallyVerify) { - if (!initialState.allowDeviceAction) return - viewModelScope.launch { - session.cryptoService().verificationService().requestDeviceVerification( - methods = listOf(VerificationMethod.SAS), - otherUserId = initialState.userId, - otherDeviceId = action.deviceId, - ).transactionId - .let { txID -> - _viewEvents.post(DeviceListBottomSheetViewEvents.Verify(initialState.userId, txID)) - } - } - } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt index d8abd91091..d3b5a25913 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt @@ -28,7 +28,6 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.DimensionConverter import im.vector.app.databinding.BottomSheetGenericListBinding -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import javax.inject.Inject @AndroidEntryPoint @@ -63,8 +62,4 @@ class DeviceTrustInfoActionFragment : override fun invalidate() = withState(viewModel) { epoxyController.setData(it) } - - override fun onVerifyManually(device: CryptoDeviceInfo) { - viewModel.handle(DeviceListAction.ManuallyVerify(device.deviceId)) - } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt index 20c388dd95..0f39fc889c 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt @@ -25,13 +25,10 @@ import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.genericItem import im.vector.app.core.ui.list.genericWithValueItem import im.vector.app.core.utils.DimensionConverter -import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem -import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.devices.TrustUtils import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import javax.inject.Inject @@ -39,13 +36,10 @@ class DeviceTrustInfoEpoxyController @Inject constructor( private val stringProvider: StringProvider, private val colorProvider: ColorProvider, private val dimensionConverter: DimensionConverter, - private val vectorPreferences: VectorPreferences ) : TypedEpoxyController() { - interface InteractionListener { - fun onVerifyManually(device: CryptoDeviceInfo) - } + interface InteractionListener var interactionListener: InteractionListener? = null @@ -54,7 +48,7 @@ class DeviceTrustInfoEpoxyController @Inject constructor( data?.selectedDevice?.let { cryptoDeviceInfo -> val trustMSK = data.memberCrossSigningKey?.isTrusted().orFalse() val legacyMode = data.memberCrossSigningKey == null - val isMyDevice = data.myDeviceId == cryptoDeviceInfo.deviceId + val isMyDevice = data.userId == data.myUserId && data.myDeviceId == cryptoDeviceInfo.deviceId val trustLevel = TrustUtils.shieldForTrust( isMyDevice, trustMSK, @@ -126,18 +120,7 @@ class DeviceTrustInfoEpoxyController @Inject constructor( id("warn") centered(false) textColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) - text(host.stringProvider.getString(R.string.verification_profile_device_untrust_info).toEpoxyCharSequence()) - } - - bottomSheetVerificationActionItem { - id("verify") - title(host.stringProvider.getString(R.string.cross_signing_verify_by_emoji)) - titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary)) - iconRes(R.drawable.ic_arrow_right) - iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary)) - listener { - host.interactionListener?.onVerifyManually(cryptoDeviceInfo) - } + text(host.stringProvider.getString(R.string.verification_profile_other_device_untrust_info).toEpoxyCharSequence()) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index f915395a42..0dd8d14460 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -254,6 +254,8 @@ class VectorPreferences @Inject constructor( const val TAKE_PHOTO_VIDEO_MODE_PHOTO = 1 const val TAKE_PHOTO_VIDEO_MODE_VIDEO = 2 + const val HAD_EXISTING_LEGACY_DATA = "HAD_EXISTING_LEGACY_DATA" + const val IS_ON_RUST_CRYPTO = "IS_ON_RUST_CRYPTO" // Background sync modes // some preferences keys must be kept after a logout @@ -1278,4 +1280,24 @@ class VectorPreferences @Inject constructor( putBoolean(SETTINGS_NEW_LOGIN_ALERT_SHOWN_FOR_DEVICE + deviceId, true) } } + + fun hadExistingLegacyData(): Boolean { + return defaultPrefs.getBoolean(HAD_EXISTING_LEGACY_DATA, false) + } + + fun setHadExistingLegacyData(had: Boolean) { + defaultPrefs.edit { + putBoolean(HAD_EXISTING_LEGACY_DATA, had) + } + } + + fun isOnRustCrypto(): Boolean { + return defaultPrefs.getBoolean(IS_ON_RUST_CRYPTO, false) + } + + fun setIsOnRustCrypto(boolean: Boolean) { + defaultPrefs.edit { + putBoolean(IS_ON_RUST_CRYPTO, boolean) + } + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/verification/GetEncryptionTrustLevelForDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/verification/GetEncryptionTrustLevelForDeviceUseCase.kt index 268ae86601..762a636494 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/verification/GetEncryptionTrustLevelForDeviceUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/verification/GetEncryptionTrustLevelForDeviceUseCase.kt @@ -25,6 +25,8 @@ class GetEncryptionTrustLevelForDeviceUseCase @Inject constructor( private val getEncryptionTrustLevelForOtherDeviceUseCase: GetEncryptionTrustLevelForOtherDeviceUseCase, ) { + // XXX why is this using the RoomEncryptionTrustLevel? + // should be using a new DeviceTrustShield enum fun execute(currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo, cryptoDeviceInfo: CryptoDeviceInfo?): RoomEncryptionTrustLevel? { if (cryptoDeviceInfo == null) { return null diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt index b355b55903..6489fe537d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt @@ -17,6 +17,7 @@ package im.vector.app.features.settings.troubleshoot import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.lifecycleScope import androidx.work.WorkInfo import androidx.work.WorkManager import im.vector.app.R @@ -72,13 +73,15 @@ class TestEndpointAsTokenRegistration @Inject constructor( } private fun unregisterThenRegister(testParameters: TestParameters, pushKey: String) { - activeSessionHolder.getSafeActiveSession()?.coroutineScope?.launch { + val scope = activeSessionHolder.getSafeActiveSession()?.coroutineScope ?: return + val io = activeSessionHolder.getActiveSession().coroutineDispatchers.io + scope.launch(io) { unregisterUnifiedPushUseCase.execute(pushersManager) registerUnifiedPush(distributor = "", testParameters, pushKey) } } - private fun registerUnifiedPush( + private suspend fun registerUnifiedPush( distributor: String, testParameters: TestParameters, pushKey: String, @@ -106,7 +109,9 @@ class TestEndpointAsTokenRegistration @Inject constructor( pushKey: String, ) { unifiedPushHelper.showSelectDistributorDialog(context) { selection -> - registerUnifiedPush(distributor = selection, testParameters, pushKey) + context.lifecycleScope.launch { + registerUnifiedPush(distributor = selection, testParameters, pushKey) + } } } } diff --git a/vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt index 0673fbadb5..01e5c88809 100644 --- a/vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt @@ -19,6 +19,7 @@ package im.vector.app.core.device import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeCryptoService import im.vector.app.test.fakes.FakeSession +import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.junit.Test @@ -31,7 +32,7 @@ class DefaultGetDeviceInfoUseCaseTest { private val getDeviceInfoUseCase = DefaultGetDeviceInfoUseCase(activeSessionHolder.instance) @Test - fun `when execute, then get crypto device info`() { + fun `when execute, then get crypto device info`() = runTest { val result = getDeviceInfoUseCase.execute() result shouldBeEqualTo cryptoService.cryptoDeviceInfo diff --git a/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt b/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt index 1e228353f5..4b6063fb93 100644 --- a/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt +++ b/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt @@ -29,6 +29,7 @@ import im.vector.app.test.fixtures.CryptoDeviceInfoFixture.aCryptoDeviceInfo import im.vector.app.test.fixtures.PusherFixture import im.vector.app.test.fixtures.SessionParamsFixture import io.mockk.mockk +import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.junit.Test import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo @@ -56,7 +57,7 @@ class PushersManagerTest { ) @Test - fun `when enqueueRegisterPusher, then HttpPusher created and enqueued`() { + fun `when enqueueRegisterPusher, then HttpPusher created and enqueued`() = runTest { val pushKey = "abc" val gateway = "123" val pusherAppId = "app-id" diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt index e8bb15ac58..b5e503dd98 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt @@ -84,5 +84,5 @@ class FakeCryptoService( } } - override fun getMyCryptoDevice() = cryptoDeviceInfo + override suspend fun getMyCryptoDevice() = cryptoDeviceInfo } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeGetDeviceInfoUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeGetDeviceInfoUseCase.kt index c284263d28..34853d1776 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeGetDeviceInfoUseCase.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeGetDeviceInfoUseCase.kt @@ -17,13 +17,13 @@ package im.vector.app.test.fakes import im.vector.app.core.device.GetDeviceInfoUseCase -import io.mockk.every +import io.mockk.coEvery import io.mockk.mockk import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo class FakeGetDeviceInfoUseCase : GetDeviceInfoUseCase by mockk() { fun givenDeviceInfo(cryptoDeviceInfo: CryptoDeviceInfo) { - every { execute() } returns cryptoDeviceInfo + coEvery { execute() } returns cryptoDeviceInfo } }