Merge branch 'main' of github.com:vector-im/element-android into jonny/feat/rich-text-mentions

This commit is contained in:
David Langley 2023-06-09 16:05:46 +01:00
commit 36b97b1647
101 changed files with 1060 additions and 428 deletions

View File

@ -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) Changes in Element v1.5.32 (2023-04-19)
======================================= =======================================

View File

@ -28,7 +28,7 @@ buildscript {
classpath 'com.google.gms:google-services:4.3.15' classpath 'com.google.gms:google-services:4.3.15'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.0.0.2929' 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.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.owasp:dependency-check-gradle:8.2.1'
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.8.10" classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.8.10"
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"

View File

@ -1 +0,0 @@
User pills get lost at message editing

View File

@ -1 +0,0 @@
Upgrade Jitsi SDK from 6.2.2 to 8.1.1. This fixes video call on some Android devices.

View File

@ -1 +0,0 @@
First integration of rust crypto module. See documentation for details `docs/rust_crypto_integration.md`

View File

@ -1 +0,0 @@
Enable free style cropping for camera and gallery images

View File

@ -1 +0,0 @@
Fix duplicate reactions when using full emoji picker. Contributed by @tulir @ Beeper.

View File

@ -1 +0,0 @@
Add an audio alert when the voice broadcast recording is automatically paused

View File

@ -1 +0,0 @@
Analytics: add crypto module to E2E events

View File

@ -1 +0,0 @@
Fix: RustCrossSigning service API confusion (identity trusted vs own device trusted by identity)

View File

@ -1 +0,0 @@
Bump rust crypto crate to 0.3.5

View File

@ -1 +0,0 @@
Expose Rust SDK Version in Help & About page and in Bug Reports

View File

@ -1 +0,0 @@
Matrix-Ids are sometimes shown in notice events instead of display names

View File

@ -1 +0,0 @@
CI: Add workflow to run test with crypto flavor

View File

@ -1 +0,0 @@
Allow custom push gateway to use non-default port

View File

@ -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.

View File

@ -1 +0,0 @@
Remove ability to migrate session from Riot to Element.

View File

@ -1 +0,0 @@
Add crypto database migration 22, that extract account and olm session to the new rust DB format

View File

@ -1 +0,0 @@
Fix crash when opening "Protect access" screen, and various other issue with `repeatOnLifecycle`

View File

@ -1 +0,0 @@
RustCrypto: Verification UX not refreshed after scanning a QR code

View File

@ -1 +0,0 @@
Improve keyboard navigation and accessibility when using a screen reader.

View File

@ -1 +0,0 @@
Updated posthog url (cosmetic, target same server) and added a new sentry env.

View File

@ -47,7 +47,7 @@ ext.libs = [
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
], ],
androidx : [ androidx : [
'activity' : "androidx.activity:activity-ktx:1.7.1", 'activity' : "androidx.activity:activity-ktx:1.7.2",
'appCompat' : "androidx.appcompat:appcompat:1.6.1", 'appCompat' : "androidx.appcompat:appcompat:1.6.1",
'biometric' : "androidx.biometric:biometric:1.1.0", 'biometric' : "androidx.biometric:biometric:1.1.0",
'core' : "androidx.core:core-ktx:1.10.1", 'core' : "androidx.core:core-ktx:1.10.1",
@ -101,7 +101,7 @@ ext.libs = [
], ],
element : [ element : [
'opusencoder' : "io.element.android:opusencoder:1.1.0", '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 : [ squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi", 'moshi' : "com.squareup.moshi:moshi:$moshi",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -328,6 +328,9 @@
<string name="backup">Back up</string> <string name="backup">Back up</string>
<string name="sign_out_bottom_sheet_will_lose_secure_messages">Youll lose access to your encrypted messages unless you back up your keys before signing out.</string> <string name="sign_out_bottom_sheet_will_lose_secure_messages">Youll lose access to your encrypted messages unless you back up your keys before signing out.</string>
<string name="sign_out_failed_dialog_message">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.</string>
<string name="sign_out_anyway">Sign out anyway</string>
<!-- splash screen accessibility --> <!-- splash screen accessibility -->
<string name="loading">Loading…</string> <string name="loading">Loading…</string>
@ -2446,6 +2449,7 @@
</plurals> </plurals>
<string name="crosssigning_verify_this_session">Verify this device</string> <string name="crosssigning_verify_this_session">Verify this device</string>
<string name="crosssigning_verify_after_update">App updated</string>
<string name="crosssigning_cannot_verify_this_session">Unable to verify this device</string> <string name="crosssigning_cannot_verify_this_session">Unable to verify this device</string>
<string name="crosssigning_cannot_verify_this_session_desc">You wont be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh.</string> <string name="crosssigning_cannot_verify_this_session_desc">You wont be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh.</string>
@ -2465,7 +2469,9 @@
<string name="verification_profile_device_verified_because">This session is trusted for secure messaging because %1$s (%2$s) verified it:</string> <string name="verification_profile_device_verified_because">This session is trusted for secure messaging because %1$s (%2$s) verified it:</string>
<string name="verification_profile_device_new_signing">%1$s (%2$s) signed in using a new session:</string> <string name="verification_profile_device_new_signing">%1$s (%2$s) signed in using a new session:</string>
<string name="verification_profile_device_untrust_info">Until this user trusts this session, messages sent to and from it are labeled with warnings. Alternatively, you can manually verify it.</string> <!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="verification_profile_device_untrust_info">Until this user trusts this session, messages sent to and from it are labeled with warnings. Alternatively, you can manually verify it.</string>
<string name="verification_profile_other_device_untrust_info">Until this user trusts this session, messages sent to and from it are labeled with warnings.</string>
<string name="initialize_cross_signing">Initialize CrossSigning</string> <string name="initialize_cross_signing">Initialize CrossSigning</string>
@ -2707,6 +2713,7 @@
<string tools:ignore="UnusedResources" name="crosssigning_verify_session">Verify login</string> <string tools:ignore="UnusedResources" name="crosssigning_verify_session">Verify login</string>
<string name="cross_signing_verify_by_emoji">Interactively Verify by Emoji</string> <string name="cross_signing_verify_by_emoji">Interactively Verify by Emoji</string>
<string name="confirm_your_identity">Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.</string> <string name="confirm_your_identity">Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.</string>
<string name="confirm_your_identity_after_update">Secure messaging has been improved with the latest update. Please re-verify your device.</string>
<string name="confirm_your_identity_quad_s">Confirm your identity by verifying this login, granting it access to encrypted messages.</string> <string name="confirm_your_identity_quad_s">Confirm your identity by verifying this login, granting it access to encrypted messages.</string>
<string name="failed_to_initialize_cross_signing">Failed to set up Cross Signing</string> <string name="failed_to_initialize_cross_signing">Failed to set up Cross Signing</string>

View File

@ -63,7 +63,7 @@ android {
// that the app's state is completely cleared between tests. // that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true' 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", "\"${gitRevision()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
@ -216,8 +216,8 @@ dependencies {
implementation libs.google.phonenumber implementation libs.google.phonenumber
rustCryptoImplementation("org.matrix.rustcomponents:crypto-android:0.3.7") rustCryptoImplementation("org.matrix.rustcomponents:crypto-android:0.3.9")
// rustCryptoApi project(":library:rustCrypto") // rustCryptoApi project(":library:rustCrypto")
testImplementation libs.tests.junit testImplementation libs.tests.junit
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281

View File

@ -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.StoreRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask 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.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.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
@ -254,6 +255,9 @@ internal abstract class CryptoModule {
@Binds @Binds
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
@Binds
abstract fun bindCommonCryptoStore(store: RealmCryptoStore): IMXCommonCryptoStore
@Binds @Binds
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask

View File

@ -256,7 +256,7 @@ internal class DefaultCryptoService @Inject constructor(
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
} }
override fun getMyCryptoDevice(): CryptoDeviceInfo { override suspend fun getMyCryptoDevice(): CryptoDeviceInfo {
return myDeviceInfoHolder.get().myDevice return myDeviceInfoHolder.get().myDevice
} }
@ -506,10 +506,7 @@ internal class DefaultCryptoService @Inject constructor(
null null
} else { } else {
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
cryptoStore.deviceWithIdentityKey(senderKey).takeIf { cryptoStore.deviceWithIdentityKey(userId, senderKey)
// check that the claimed user id matches
it?.userId == userId
}
} }
} }
} }
@ -539,7 +536,7 @@ internal class DefaultCryptoService @Inject constructor(
// .executeBy(taskExecutor) // .executeBy(taskExecutor)
// } // }
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> { override suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDeviceList(userId).orEmpty() return cryptoStore.getUserDeviceList(userId).orEmpty()
} }
// //

View File

@ -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.keysbackup.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail 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.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.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody 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.crypto.model.TrailType
import org.matrix.android.sdk.api.session.events.model.Event 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.RoomKeyWithHeldContent
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
import org.matrix.android.sdk.api.util.Optional 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.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper 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.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.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
import org.matrix.olm.OlmAccount import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmOutboundGroupSession import org.matrix.olm.OlmOutboundGroupSession
@ -49,7 +45,7 @@ import org.matrix.olm.OlmOutboundGroupSession
/** /**
* The crypto data store. * The crypto data store.
*/ */
internal interface IMXCryptoStore { internal interface IMXCryptoStore : IMXCommonCryptoStore {
/** /**
* @return the device id * @return the device id
@ -78,21 +74,6 @@ internal interface IMXCryptoStore {
*/ */
fun getInboundGroupSessions(roomId: String): List<MXInboundMegolmSessionWrapper> fun getInboundGroupSessions(roomId: String): List<MXInboundMegolmSessionWrapper>
/**
* @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. * Enable or disable key gossiping.
* Default is true. * Default is true.
@ -123,28 +104,6 @@ internal interface IMXCryptoStore {
*/ */
fun getRoomsListBlacklistUnverifiedDevices(): List<String> fun getRoomsListBlacklistUnverifiedDevices(): List<String>
/**
* A live status regarding sharing keys for unverified devices in this room.
*
* @return Live status
*/
fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean>
/**
* 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. * Get the current keys backup version.
*/ */
@ -186,16 +145,6 @@ internal interface IMXCryptoStore {
*/ */
fun deleteStore() fun deleteStore()
/**
* open any existing crypto store.
*/
fun open()
/**
* Close the store.
*/
fun close()
/** /**
* Store the device id. * Store the device id.
* *
@ -262,14 +211,6 @@ internal interface IMXCryptoStore {
fun getLiveDeviceWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>> fun getLiveDeviceWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>>
fun getMyDevicesInfo(): List<DeviceInfo>
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
fun getLiveMyDevicesInfo(deviceId: String): LiveData<Optional<DeviceInfo>>
fun saveMyDevicesInfo(info: List<DeviceInfo>)
/** /**
* Store the crypto algorithm for a room. * Store the crypto algorithm for a room.
* *
@ -278,47 +219,8 @@ internal interface IMXCryptoStore {
*/ */
fun storeRoomAlgorithm(roomId: String, algorithm: String?) 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 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. * Store a session between the logged-in user and another device.
* *
@ -361,15 +263,6 @@ internal interface IMXCryptoStore {
*/ */
fun storeInboundGroupSessions(sessions: List<MXInboundMegolmSessionWrapper>) fun storeInboundGroupSessions(sessions: List<MXInboundMegolmSessionWrapper>)
/**
* 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. * Retrieve an inbound group session, filtering shared history.
* *
@ -552,7 +445,6 @@ internal interface IMXCryptoStore {
// fun getCrossSigningPrivateKeysFlow(): Flow<Optional<PrivateKeysInfo>> // fun getCrossSigningPrivateKeysFlow(): Flow<Optional<PrivateKeysInfo>>
fun getGlobalCryptoConfig(): GlobalCryptoConfig fun getGlobalCryptoConfig(): GlobalCryptoConfig
fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig>
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
@ -597,14 +489,8 @@ internal interface IMXCryptoStore {
fun setDeviceKeysUploaded(uploaded: Boolean) fun setDeviceKeysUploaded(uploaded: Boolean)
fun areDeviceKeysUploaded(): Boolean fun areDeviceKeysUploaded(): Boolean
fun tidyUpDataBase()
fun getOutgoingRoomKeyRequests(inStates: Set<OutgoingRoomKeyRequestState>): List<OutgoingKeyRequest> fun getOutgoingRoomKeyRequests(inStates: Set<OutgoingRoomKeyRequestState>): List<OutgoingKeyRequest>
/**
* 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]. * Store a bunch of data related to the users. @See [UserDataToStore].
*/ */

View File

@ -280,6 +280,19 @@ internal class RealmCryptoStore @Inject constructor(
} }
} }
override fun deviceWithIdentityKey(userId: String, identityKey: String): CryptoDeviceInfo? {
return doWithRealm(realmConfiguration) { realm ->
realm.where<DeviceInfoEntity>()
.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<String, CryptoDeviceInfo>?) { override fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) {
doRealmTransaction("storeUserDevices", realmConfiguration) { realm -> doRealmTransaction("storeUserDevices", realmConfiguration) { realm ->
storeUserDevices(realm, userId, devices) storeUserDevices(realm, userId, devices)

View File

@ -73,7 +73,7 @@ interface CryptoService {
suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo> suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo>
fun getMyCryptoDevice(): CryptoDeviceInfo suspend fun getMyCryptoDevice(): CryptoDeviceInfo
fun getGlobalBlacklistUnverifiedDevices(): Boolean fun getGlobalBlacklistUnverifiedDevices(): Boolean
@ -130,7 +130,7 @@ interface CryptoService {
suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
// fun getCryptoDeviceInfoFlow(userId: String): Flow<List<CryptoDeviceInfo>> // fun getCryptoDeviceInfoFlow(userId: String): Flow<List<CryptoDeviceInfo>>

View File

@ -37,6 +37,7 @@ interface SignOutService {
/** /**
* Sign out, and release the session, clear all the session data, including crypto data. * 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 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)
} }

View File

@ -30,6 +30,7 @@ object MimeTypes {
const val BadJpg = "image/jpg" const val BadJpg = "image/jpg"
const val Jpeg = "image/jpeg" const val Jpeg = "image/jpeg"
const val Gif = "image/gif" const val Gif = "image/gif"
const val Webp = "image/webp"
const val Ogg = "audio/ogg" const val Ogg = "audio/ogg"

View File

@ -301,6 +301,9 @@ internal class DefaultAuthenticationService @Inject constructor(
val oidcCompatibilityFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.sso" && it.delegatedOidcCompatibilty == true } 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 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( return LoginFlowResult(
supportedLoginTypes = flows.orEmpty().mapNotNull { it.type }, supportedLoginTypes = flows.orEmpty().mapNotNull { it.type },
ssoIdentityProviders = flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider, ssoIdentityProviders = flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
@ -309,7 +312,7 @@ internal class DefaultAuthenticationService @Inject constructor(
isOutdatedHomeserver = !versions.isSupportedBySdk(), isOutdatedHomeserver = !versions.isSupportedBySdk(),
hasOidcCompatibilityFlow = oidcCompatibilityFlow != null, hasOidcCompatibilityFlow = oidcCompatibilityFlow != null,
isLogoutDevicesSupported = versions.doesServerSupportLogoutDevices(), isLogoutDevicesSupported = versions.doesServerSupportLogoutDevices(),
isLoginWithQrSupported = versions.doesServerSupportQrCodeLogin(), isLoginWithQrSupported = supportsGetLoginTokenFlow || versions.doesServerSupportQrCodeLogin(),
) )
} }

View File

@ -51,5 +51,13 @@ internal data class LoginFlow(
* See [MSC3824](https://github.com/matrix-org/matrix-spec-proposals/pull/3824) * See [MSC3824](https://github.com/matrix-org/matrix-spec-proposals/pull/3824)
*/ */
@Json(name = "org.matrix.msc3824.delegated_oidc_compatibility") @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
) )

View File

@ -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_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind"
private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440" private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440"
private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable" 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_QR_CODE_LOGIN = "org.matrix.msc3882"
private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771" private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771"
private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773" 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) 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 { internal fun Versions.doesServerSupportQrCodeLogin(): Boolean {
@Suppress("DEPRECATION")
return unstableFeatures?.get(FEATURE_QR_CODE_LOGIN) ?: false return unstableFeatures?.get(FEATURE_QR_CODE_LOGIN) ?: false
} }

View File

@ -17,11 +17,11 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import org.matrix.android.sdk.api.crypto.MXCryptoConfig 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 import javax.inject.Inject
internal class ShouldEncryptForInvitedMembersUseCase @Inject constructor(private val cryptoConfig: MXCryptoConfig, internal class ShouldEncryptForInvitedMembersUseCase @Inject constructor(private val cryptoConfig: MXCryptoConfig,
private val cryptoStore: IMXCryptoStore) { private val cryptoStore: IMXCommonCryptoStore) {
operator fun invoke(roomId: String): Boolean { operator fun invoke(roomId: String): Boolean {
return cryptoConfig.enableEncryptionForInvitedMembers && cryptoStore.shouldEncryptForInvitedMembers(roomId) return cryptoConfig.enableEncryptionForInvitedMembers && cryptoStore.shouldEncryptForInvitedMembers(roomId)

View File

@ -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<DeviceInfo>)
// questionable that it's stored in crypto store
fun getMyDevicesInfo(): List<DeviceInfo>
// questionable that it's stored in crypto store
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
// questionable that it's stored in crypto store
fun getLiveMyDevicesInfo(deviceId: String): LiveData<Optional<DeviceInfo>>
/**
* 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<GlobalCryptoConfig>
/**
* @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<Boolean>
/**
* 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?
}

View File

@ -71,7 +71,14 @@ internal data class Capabilities(
* True if the user can use m.thread relation, false otherwise. * True if the user can use m.thread relation, false otherwise.
*/ */
@Json(name = "m.thread") @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) @JsonClass(generateAdapter = true)

View File

@ -151,8 +151,6 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
getVersionResult.doesServerSupportThreads() getVersionResult.doesServerSupportThreads()
homeServerCapabilitiesEntity.canUseThreadReadReceiptsAndNotifications = homeServerCapabilitiesEntity.canUseThreadReadReceiptsAndNotifications =
getVersionResult.doesServerSupportThreadUnreadNotifications() getVersionResult.doesServerSupportThreadUnreadNotifications()
homeServerCapabilitiesEntity.canLoginWithQrCode =
getVersionResult.doesServerSupportQrCodeLogin()
homeServerCapabilitiesEntity.canRemotelyTogglePushNotificationsOfDevices = homeServerCapabilitiesEntity.canRemotelyTogglePushNotificationsOfDevices =
getVersionResult.doesServerSupportRemoteToggleOfPushNotifications() getVersionResult.doesServerSupportRemoteToggleOfPushNotifications()
homeServerCapabilitiesEntity.canRedactEventWithRelations = homeServerCapabilitiesEntity.canRedactEventWithRelations =
@ -169,10 +167,25 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
} }
homeServerCapabilitiesEntity.externalAccountManagementUrl = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.accountManagementUrl homeServerCapabilitiesEntity.externalAccountManagementUrl = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.accountManagementUrl
} }
homeServerCapabilitiesEntity.canLoginWithQrCode = canLoginWithQrCode(getCapabilitiesResult, getVersionResult)
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time 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 { companion object {
// 8 hours like on Element Web // 8 hours like on Element Web
private const val MIN_DELAY_BETWEEN_TWO_REQUEST_MILLIS = 8 * 60 * 60 * 1000 private const val MIN_DELAY_BETWEEN_TWO_REQUEST_MILLIS = 8 * 60 * 60 * 1000

View File

@ -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.events.model.toValidDecryptedEvent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent 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.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCryptoStore) { internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCommonCryptoStore) {
sealed class EditValidity { sealed class EditValidity {
object Valid : 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. * If the original event was encrypted, the replacement should be too.
*/ */
fun validateEdit(originalEvent: Event?, replaceEvent: Event): EditValidity { 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 // 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 // Edits should be revalidated when the original event is received
if (originalEvent == null) { if (originalEvent == null) {
@ -80,25 +78,21 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto
val replaceDecrypted = replaceEvent.toValidDecryptedEvent() val replaceDecrypted = replaceEvent.toValidDecryptedEvent()
?: return EditValidity.Unknown // UTD can't decide ?: return EditValidity.Unknown // UTD can't decide
val originalCryptoSenderId = cryptoStore.deviceWithIdentityKey(originalDecrypted.cryptoSenderKey)?.userId if (originalEvent.senderId != replaceEvent.senderId) {
val editCryptoSenderId = cryptoStore.deviceWithIdentityKey(replaceDecrypted.cryptoSenderKey)?.userId 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) { if (originalDecrypted.getRelationContent()?.type == RelationType.REPLACE) {
return EditValidity.Invalid("The original event must not, itself, have a rel_type of m.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? // 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? // maybe it's a deleted device or a not yet downloaded one?
val bestEffortOriginal = originalCryptoSenderId ?: originalEvent.senderId return EditValidity.Unknown
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")
}
} }
if (originalDecrypted.type != replaceDecrypted.type) { if (originalDecrypted.type != replaceDecrypted.type) {

View File

@ -22,6 +22,8 @@ import com.zhuinden.monarchy.Monarchy
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject 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.model.ReadReceipt
import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
@ -43,7 +45,8 @@ internal class DefaultReadService @AssistedInject constructor(
private val setReadMarkersTask: SetReadMarkersTask, private val setReadMarkersTask: SetReadMarkersTask,
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
@UserId private val userId: String, @UserId private val userId: String,
private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
private val matrixCoroutineDispatchers: MatrixCoroutineDispatchers,
) : ReadService { ) : ReadService {
@AssistedFactory @AssistedFactory
@ -66,7 +69,7 @@ internal class DefaultReadService @AssistedInject constructor(
setReadMarkersTask.execute(taskParams) 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) { val readReceiptThreadId = if (homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.canUseThreadReadReceiptsAndNotifications == true) {
threadId threadId
} else { } else {

View File

@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.room.read
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import io.realm.Realm 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.events.model.LocalEcho
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
@ -64,9 +66,10 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
private val globalErrorReceiver: GlobalErrorReceiver, private val globalErrorReceiver: GlobalErrorReceiver,
private val clock: Clock, private val clock: Clock,
private val homeServerCapabilitiesService: HomeServerCapabilitiesService, private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
) : SetReadMarkersTask { ) : SetReadMarkersTask {
override suspend fun execute(params: SetReadMarkersTask.Params) { override suspend fun execute(params: SetReadMarkersTask.Params) = withContext(coroutineDispatchers.io) {
val markers = mutableMapOf<String, String>() val markers = mutableMapOf<String, String>()
Timber.v("Execute set read marker with params: $params") Timber.v("Execute set read marker with params: $params")
val latestSyncedEventId = latestSyncedEventId(params.roomId) val latestSyncedEventId = latestSyncedEventId(params.roomId)

View File

@ -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.JsonDict
import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.api.util.TextContent 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.SessionId
import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.session.content.UploadContentWorker import org.matrix.android.sdk.internal.session.content.UploadContentWorker
@ -69,7 +69,7 @@ internal class DefaultSendService @AssistedInject constructor(
private val workManagerProvider: WorkManagerProvider, private val workManagerProvider: WorkManagerProvider,
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCommonCryptoStore,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val localEchoRepository: LocalEchoRepository, private val localEchoRepository: LocalEchoRepository,
private val eventSenderProcessor: EventSenderProcessor, private val eventSenderProcessor: EventSenderProcessor,

View File

@ -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.Session
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.util.Cancelable 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.session.SessionScope
import org.matrix.android.sdk.internal.task.CoroutineSequencer import org.matrix.android.sdk.internal.task.CoroutineSequencer
import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
@ -54,7 +54,7 @@ private const val MAX_RETRY_COUNT = 3
*/ */
@SessionScope @SessionScope
internal class EventSenderProcessorCoroutine @Inject constructor( internal class EventSenderProcessorCoroutine @Inject constructor(
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCommonCryptoStore,
private val sessionParams: SessionParams, private val sessionParams: SessionParams,
private val queuedTaskFactory: QueuedTaskFactory, private val queuedTaskFactory: QueuedTaskFactory,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,

View File

@ -35,7 +35,12 @@ internal class DefaultSignOutService @Inject constructor(
sessionParamsStore.updateCredentials(credentials) sessionParamsStore.updateCredentials(credentials)
} }
override suspend fun signOut(signOutFromHomeserver: Boolean) { override suspend fun signOut(signOutFromHomeserver: Boolean, ignoreServerRequestError: Boolean) {
return signOutTask.execute(SignOutTask.Params(signOutFromHomeserver)) return signOutTask.execute(
SignOutTask.Params(
signOutFromHomeserver = signOutFromHomeserver,
ignoreServerRequestError = ignoreServerRequestError
)
)
} }
} }

View File

@ -30,7 +30,8 @@ import javax.inject.Inject
internal interface SignOutTask : Task<SignOutTask.Params, Unit> { internal interface SignOutTask : Task<SignOutTask.Params, Unit> {
data class Params( data class Params(
val signOutFromHomeserver: Boolean val signOutFromHomeserver: Boolean,
val ignoreServerRequestError: Boolean,
) )
} }
@ -59,7 +60,9 @@ internal class DefaultSignOutTask @Inject constructor(
// Ignore // Ignore
Timber.w("Ignore error due to https://github.com/matrix-org/synapse/issues/5755") Timber.w("Ignore error due to https://github.com/matrix-org/synapse/issues/5755")
} else { } else {
throw throwable if (!params.ignoreServerRequestError) {
throw throwable
}
} }
} }
} }

View File

@ -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.StoreRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask 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.keysbackup.tasks.UpdateKeysBackupVersionTask
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.RealmCryptoStore 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.RealmCryptoStoreMigration
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
@ -246,7 +246,7 @@ internal abstract class CryptoModule {
abstract fun bindVerificationService(service: RustVerificationService): VerificationService abstract fun bindVerificationService(service: RustVerificationService): VerificationService
@Binds @Binds
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore abstract fun bindCryptoStore(store: RustCryptoStore): IMXCommonCryptoStore
@Binds @Binds
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask

View File

@ -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.coroutines.builder.safeInvokeOnClose
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.DefaultKeysAlgorithmAndData 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.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.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.SasVerification import org.matrix.android.sdk.internal.crypto.verification.SasVerification
import org.matrix.android.sdk.internal.crypto.verification.VerificationRequest 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 * Used for lazy migration of inboundGroupSession from EA to ER
*/ */
suspend fun importRoomKey(inbound: InboundGroupSessionHolder): Result<Unit> { suspend fun importRoomKey(inbound: MXInboundMegolmSessionWrapper): Result<Unit> {
Timber.v("Migration:: Tentative lazy migration") Timber.v("Migration:: Tentative lazy migration")
return withContext(coroutineDispatchers.io) { return withContext(coroutineDispatchers.io) {
val export = inbound.wrapper.exportKeys() val export = inbound.exportKeys()
?: return@withContext Result.failure(Exception("Failed to export key")) ?: return@withContext Result.failure(Exception("Failed to export key"))
val result = importDecryptedKeys(listOf(export), null).also { val result = importDecryptedKeys(listOf(export), null).also {
Timber.v("Migration:: Tentative lazy migration result: ${it.totalNumberOfKeys}") Timber.v("Migration:: Tentative lazy migration result: ${it.totalNumberOfKeys}")

View File

@ -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.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService 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.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.SessionScope
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.rustcomponents.sdk.crypto.EncryptionSettings import org.matrix.rustcomponents.sdk.crypto.EncryptionSettings
@ -47,7 +47,7 @@ private val loggerTag = LoggerTag("PrepareToEncryptUseCase", LoggerTag.CRYPTO)
internal class PrepareToEncryptUseCase @Inject constructor( internal class PrepareToEncryptUseCase @Inject constructor(
private val olmMachine: OlmMachine, private val olmMachine: OlmMachine,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCommonCryptoStore,
private val getRoomUserIds: GetRoomUserIdsUseCase, private val getRoomUserIds: GetRoomUserIdsUseCase,
private val requestSender: RequestSender, private val requestSender: RequestSender,
private val loadRoomMembersTask: LoadRoomMembersTask, private val loadRoomMembersTask: LoadRoomMembersTask,

View File

@ -17,7 +17,6 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import android.content.Context import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.map import androidx.lifecycle.map
import androidx.paging.PagedList import androidx.paging.PagedList
@ -26,7 +25,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers 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.model.SessionInfo
import org.matrix.android.sdk.internal.crypto.network.OutgoingRequestsProcessor 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.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.store.db.CryptoStoreAggregator
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
@ -111,7 +109,7 @@ internal class RustCryptoService @Inject constructor(
@UserId private val myUserId: String, @UserId private val myUserId: String,
@DeviceId private val deviceId: String, @DeviceId private val deviceId: String,
// the crypto store // the crypto store
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCommonCryptoStore,
// Set of parameters used to configure/customize the end-to-end crypto. // Set of parameters used to configure/customize the end-to-end crypto.
private val mxCryptoConfig: MXCryptoConfig, private val mxCryptoConfig: MXCryptoConfig,
// Actions // Actions
@ -185,12 +183,13 @@ internal class RustCryptoService @Inject constructor(
override fun getCryptoVersion(context: Context, longFormat: Boolean): String { override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
val version = org.matrix.rustcomponents.sdk.crypto.version() 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() 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 { override suspend fun getMyCryptoDevice(): CryptoDeviceInfo = withContext(coroutineDispatchers.io) {
return runBlocking { olmMachine.ownDevice() } olmMachine.ownDevice()
} }
override suspend fun fetchDevicesList(): List<DeviceInfo> { override suspend fun fetchDevicesList(): List<DeviceInfo> {
@ -342,11 +341,11 @@ internal class RustCryptoService @Inject constructor(
*/ */
override suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? { override suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
if (userId.isEmpty() || deviceId.isNullOrEmpty()) return null 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<CryptoDeviceInfo> { override suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return runBlocking { return withContext(coroutineDispatchers.io) {
olmMachine.getCryptoDeviceInfo(userId) olmMachine.getCryptoDeviceInfo(userId)
} }
} }
@ -360,9 +359,7 @@ internal class RustCryptoService @Inject constructor(
} }
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> { override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
return olmMachine.getLiveDevices(listOf(myUserId)).map { return olmMachine.getLiveDevices(userIds)
it.filter { it.userId == myUserId }
}
} }
override fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>> { override fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>> {
@ -905,13 +902,6 @@ internal class RustCryptoService @Inject constructor(
// TODO("Not yet implemented") // TODO("Not yet implemented")
} }
/* ==========================================================================================
* For test only
* ========================================================================================== */
@VisibleForTesting
val cryptoStoreForTesting = cryptoStore
companion object { companion object {
const val CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS = 3_600_000 // one hour const val CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS = 3_600_000 // one hour
} }

View File

@ -24,6 +24,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor 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.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.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.events.model.EventType
import org.matrix.android.sdk.api.session.uia.UiaResult import org.matrix.android.sdk.api.session.uia.UiaResult
import org.matrix.android.sdk.internal.auth.registration.handleUIA 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.OlmMachine
import org.matrix.android.sdk.internal.crypto.PerSessionBackupQueryRateLimiter import org.matrix.android.sdk.internal.crypto.PerSessionBackupQueryRateLimiter
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.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.KeysUploadResponse
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo 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.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.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
@ -102,7 +103,7 @@ internal class RequestSender @Inject constructor(
private val moshi: Moshi, private val moshi: Moshi,
cryptoCoroutineScope: CoroutineScope, cryptoCoroutineScope: CoroutineScope,
private val rateLimiter: PerSessionBackupQueryRateLimiter, private val rateLimiter: PerSessionBackupQueryRateLimiter,
private val inboundGroupSessionStore: InboundGroupSessionStore, private val cryptoStore: IMXCommonCryptoStore,
private val localEchoRepository: LocalEchoRepository, private val localEchoRepository: LocalEchoRepository,
private val olmMachine: Lazy<OlmMachine>, private val olmMachine: Lazy<OlmMachine>,
) { ) {
@ -266,7 +267,9 @@ internal class RequestSender @Inject constructor(
val senderKey = requestBody?.get("sender_key") as? String val senderKey = requestBody?.get("sender_key") as? String
if (roomId != null && sessionId != null) { if (roomId != null && sessionId != null) {
// try to perform a lazy migration from legacy store // 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) { if (legacy == null || olmMachine.get().importRoomKey(legacy).isFailure) {
rateLimiter.tryFromBackupIfPossible(sessionId, roomId) rateLimiter.tryFromBackupIfPossible(sessionId, roomId)
} }

View File

@ -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<OlmMachine>,
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<CryptoMetadataEntity>().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<OlmInboundGroupSessionEntity>()
.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<OutgoingKeyRequestEntity>()
.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<AuditTrailEntity>()
.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<DeviceInfo>) {
val entities = info.map { myDeviceLastSeenInfoEntityMapper.map(it) }
doRealmTransactionAsync(realmConfiguration) { realm ->
realm.where<MyDeviceLastSeenInfoEntity>().findAll().deleteAllFromRealm()
entities.forEach {
realm.insertOrUpdate(it)
}
}
}
override fun getMyDevicesInfo(): List<DeviceInfo> {
return monarchy.fetchAllCopiedSync {
it.where<MyDeviceLastSeenInfoEntity>()
}.map {
DeviceInfo(
deviceId = it.deviceId,
lastSeenIp = it.lastSeenIp,
lastSeenTs = it.lastSeenTs,
displayName = it.displayName
)
}
}
override fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>> {
return monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm.where<MyDeviceLastSeenInfoEntity>()
},
{ entity -> myDeviceLastSeenInfoEntityMapper.map(entity) }
)
}
override fun getLiveMyDevicesInfo(deviceId: String): LiveData<Optional<DeviceInfo>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm.where<MyDeviceLastSeenInfoEntity>()
.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<CryptoMetadataEntity>().findFirst()?.globalBlacklistUnverifiedDevices = block
}
}
override fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm
.where<CryptoMetadataEntity>()
},
{
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<CryptoMetadataEntity>().findFirst()?.globalBlacklistUnverifiedDevices
} ?: false
}
override fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm.where<CryptoRoomEntity>()
.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<CryptoRoomEntity>()
.equalTo(CryptoRoomEntityFields.ROOM_ID, roomId)
.findFirst()
?.blacklistUnverifiedDevices ?: false
}
}
}

View File

@ -71,6 +71,10 @@ fun RealmToMigrate.getPickledAccount(pickleKey: ByteArray): MigrationData {
val userKey = metadataEntity.xSignUserPrivateKey val userKey = metadataEntity.xSignUserPrivateKey
val selfSignedKey = metadataEntity.xSignSelfSignedPrivateKey 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 val userId = metadataEntity.userId
?: throw java.lang.IllegalArgumentException("Rust db migration: userId is null") ?: throw java.lang.IllegalArgumentException("Rust db migration: userId is null")
val deviceId = metadataEntity.deviceId val deviceId = metadataEntity.deviceId
@ -79,6 +83,8 @@ fun RealmToMigrate.getPickledAccount(pickleKey: ByteArray): MigrationData {
val backupVersion = metadataEntity.backupVersion val backupVersion = metadataEntity.backupVersion
val backupRecoveryKey = metadataEntity.keyBackupRecoveryKey val backupRecoveryKey = metadataEntity.keyBackupRecoveryKey
Timber.i("## Migration: has private backup key ${backupRecoveryKey != null} for version $backupVersion")
val isOlmAccountShared = metadataEntity.deviceKeysSentToServer val isOlmAccountShared = metadataEntity.deviceKeysSentToServer
val olmAccount = metadataEntity.getOlmAccount() val olmAccount = metadataEntity.getOlmAccount()

View File

@ -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.VERIFICATION_METHOD_RECIPROCATE
import org.matrix.android.sdk.internal.crypto.model.rest.toValue import org.matrix.android.sdk.internal.crypto.model.rest.toValue
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.rustcomponents.sdk.crypto.VerificationRequestState
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject 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` // 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 // 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. // 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. // accept() will dispatch an update, no need to do it twice.
Timber.d("## Verification: Auto accepting SAS verification with $sender") Timber.d("## Verification: Auto accepting SAS verification with $sender")
transaction.accept() transaction.accept()
@ -308,7 +309,7 @@ internal class RustVerificationService @Inject constructor(
return if (request != null) { return if (request != null) {
request.acceptWithMethods(methods) request.acceptWithMethods(methods)
request.startQrCode() request.startQrCode()
request.isReady() request.innerState() is VerificationRequestState.Ready
} else { } else {
false false
} }

View File

@ -136,9 +136,9 @@ internal class VerificationRequest @AssistedInject constructor(
* concrete verification flow, i.e. we can show/scan a QR code or start emoji * concrete verification flow, i.e. we can show/scan a QR code or start emoji
* verification. * verification.
*/ */
internal fun isReady(): Boolean { // internal fun isReady(): Boolean {
return innerVerificationRequest.isReady() // return innerVerificationRequest.isReady()
} // }
/** Did we advertise that we're able to scan QR codes */ /** Did we advertise that we're able to scan QR codes */
internal fun canScanQrCodes(): Boolean { internal fun canScanQrCodes(): Boolean {

View File

@ -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.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.events.model.Event 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.events.model.EventType
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
class EventEditValidatorTest { class EventEditValidatorTest {
@ -62,7 +62,7 @@ class EventEditValidatorTest {
@Test @Test
fun `edit should be valid`() { fun `edit should be valid`() {
val mockCryptoStore = mockk<IMXCryptoStore>() val mockCryptoStore = mockk<IMXCommonCryptoStore>()
val validator = EventEditValidator(mockCryptoStore) val validator = EventEditValidator(mockCryptoStore)
validator validator
@ -71,7 +71,7 @@ class EventEditValidatorTest {
@Test @Test
fun `original event and replacement event must have the same sender`() { fun `original event and replacement event must have the same sender`() {
val mockCryptoStore = mockk<IMXCryptoStore>() val mockCryptoStore = mockk<IMXCommonCryptoStore>()
val validator = EventEditValidator(mockCryptoStore) val validator = EventEditValidator(mockCryptoStore)
validator validator
@ -83,7 +83,7 @@ class EventEditValidatorTest {
@Test @Test
fun `original event and replacement event must have the same room_id`() { fun `original event and replacement event must have the same room_id`() {
val mockCryptoStore = mockk<IMXCryptoStore>() val mockCryptoStore = mockk<IMXCommonCryptoStore>()
val validator = EventEditValidator(mockCryptoStore) val validator = EventEditValidator(mockCryptoStore)
validator validator
@ -101,7 +101,7 @@ class EventEditValidatorTest {
@Test @Test
fun `replacement and original events must not have a state_key property`() { fun `replacement and original events must not have a state_key property`() {
val mockCryptoStore = mockk<IMXCryptoStore>() val mockCryptoStore = mockk<IMXCommonCryptoStore>()
val validator = EventEditValidator(mockCryptoStore) val validator = EventEditValidator(mockCryptoStore)
validator validator
@ -119,8 +119,8 @@ class EventEditValidatorTest {
@Test @Test
fun `replacement event must have an new_content property`() { fun `replacement event must have an new_content property`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk<CryptoDeviceInfo> { mockk<CryptoDeviceInfo> {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
@ -157,8 +157,8 @@ class EventEditValidatorTest {
@Test @Test
fun `The original event must not itself have a rel_type of m_replace`() { fun `The original event must not itself have a rel_type of m_replace`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk<CryptoDeviceInfo> { mockk<CryptoDeviceInfo> {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
@ -207,8 +207,8 @@ class EventEditValidatorTest {
@Test @Test
fun `valid e2ee edit`() { fun `valid e2ee edit`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk<CryptoDeviceInfo> { mockk<CryptoDeviceInfo> {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
@ -224,8 +224,8 @@ class EventEditValidatorTest {
@Test @Test
fun `If the original event was encrypted, the replacement should be too`() { fun `If the original event was encrypted, the replacement should be too`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk<CryptoDeviceInfo> { mockk<CryptoDeviceInfo> {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
@ -241,12 +241,12 @@ class EventEditValidatorTest {
@Test @Test
fun `encrypted, original event and replacement event must have the same sender`() { fun `encrypted, original event and replacement event must have the same sender`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk { mockk {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
every { deviceWithIdentityKey("7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns every { deviceWithIdentityKey("@bob:example.com", "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns
mockk { mockk {
every { userId } returns "@bob:example.com" every { userId } returns "@bob:example.com"
} }
@ -256,7 +256,9 @@ class EventEditValidatorTest {
validator validator
.validateEdit( .validateEdit(
encryptedEvent, encryptedEvent,
encryptedEditEvent.copy().apply { encryptedEditEvent.copy(
senderId = "@bob:example.com"
).apply {
mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy( mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy(
senderKey = "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI" senderKey = "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI"
) )
@ -269,12 +271,12 @@ class EventEditValidatorTest {
@Test @Test
fun `encrypted, sent fom a deleted device, original event and replacement event must have the same sender`() { fun `encrypted, sent fom a deleted device, original event and replacement event must have the same sender`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk { mockk {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
every { deviceWithIdentityKey("7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns every { deviceWithIdentityKey(any(), "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns
null null
} }
val validator = EventEditValidator(mockCryptoStore) val validator = EventEditValidator(mockCryptoStore)
@ -288,7 +290,7 @@ class EventEditValidatorTest {
) )
} }
) shouldBeInstanceOf EventEditValidator.EditValidity.Valid::class ) shouldBeInstanceOf EventEditValidator.EditValidity.Unknown::class
validator validator
.validateEdit( .validateEdit(

View File

@ -160,11 +160,11 @@ adb -e uninstall im.vector.app.debug.test
printf "\n================================================================================\n" printf "\n================================================================================\n"
printf "Running the integration test UiAllScreensSanityTest.allScreensTest()...\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 "\n================================================================================\n"
printf "Building the app...\n" printf "Building the app...\n"
./gradlew assembleGplayDebug ./gradlew assembleGplayRustCryptoDebug
printf "\n================================================================================\n" printf "\n================================================================================\n"
printf "Uninstalling previous debug app if any...\n" printf "Uninstalling previous debug app if any...\n"
@ -172,7 +172,7 @@ adb -e uninstall im.vector.app.debug
printf "\n================================================================================\n" printf "\n================================================================================\n"
printf "Installing the app...\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 "\n================================================================================\n"
printf "Running the app...\n" printf "Running the app...\n"
@ -293,67 +293,67 @@ printf "Unzipping the artifact...\n"
unzip ${targetPath}/vector-gplay-release-unsigned.zip -d ${targetPath} unzip ${targetPath}/vector-gplay-release-unsigned.zip -d ${targetPath}
# Flatten folder hierarchy # Flatten folder hierarchy
mv ${targetPath}/gplay/release/* ${targetPath} mv ${targetPath}/gplayRustCrypto/release/* ${targetPath}
rm -rf ${targetPath}/gplay rm -rf ${targetPath}/gplay
printf "\n================================================================================\n" printf "\n================================================================================\n"
printf "Signing the APKs...\n" printf "Signing the APKs...\n"
cp ${targetPath}/vector-gplay-arm64-v8a-release-unsigned.apk \ cp ${targetPath}/vector-gplay-rustCrypto-arm64-v8a-release-unsigned.apk \
${targetPath}/vector-gplay-arm64-v8a-release-signed.apk ${targetPath}/vector-gplay-rustCrypto-arm64-v8a-release-signed.apk
./tools/release/sign_apk_unsafe.sh \ ./tools/release/sign_apk_unsafe.sh \
${keyStorePath} \ ${keyStorePath} \
${targetPath}/vector-gplay-arm64-v8a-release-signed.apk \ ${targetPath}/vector-gplay-rustCrypto-arm64-v8a-release-signed.apk \
${keyStorePassword} \ ${keyStorePassword} \
${keyPassword} ${keyPassword}
cp ${targetPath}/vector-gplay-armeabi-v7a-release-unsigned.apk \ cp ${targetPath}/vector-gplay-rustCrypto-armeabi-v7a-release-unsigned.apk \
${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk ${targetPath}/vector-gplay-rustCrypto-armeabi-v7a-release-signed.apk
./tools/release/sign_apk_unsafe.sh \ ./tools/release/sign_apk_unsafe.sh \
${keyStorePath} \ ${keyStorePath} \
${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk \ ${targetPath}/vector-gplay-rustCrypto-armeabi-v7a-release-signed.apk \
${keyStorePassword} \ ${keyStorePassword} \
${keyPassword} ${keyPassword}
cp ${targetPath}/vector-gplay-x86-release-unsigned.apk \ cp ${targetPath}/vector-gplay-rustCrypto-x86-release-unsigned.apk \
${targetPath}/vector-gplay-x86-release-signed.apk ${targetPath}/vector-gplay-rustCrypto-x86-release-signed.apk
./tools/release/sign_apk_unsafe.sh \ ./tools/release/sign_apk_unsafe.sh \
${keyStorePath} \ ${keyStorePath} \
${targetPath}/vector-gplay-x86-release-signed.apk \ ${targetPath}/vector-gplay-rustCrypto-x86-release-signed.apk \
${keyStorePassword} \ ${keyStorePassword} \
${keyPassword} ${keyPassword}
cp ${targetPath}/vector-gplay-x86_64-release-unsigned.apk \ cp ${targetPath}/vector-gplay-rustCrypto-x86_64-release-unsigned.apk \
${targetPath}/vector-gplay-x86_64-release-signed.apk ${targetPath}/vector-gplay-rustCrypto-x86_64-release-signed.apk
./tools/release/sign_apk_unsafe.sh \ ./tools/release/sign_apk_unsafe.sh \
${keyStorePath} \ ${keyStorePath} \
${targetPath}/vector-gplay-x86_64-release-signed.apk \ ${targetPath}/vector-gplay-rustCrypto-x86_64-release-signed.apk \
${keyStorePassword} \ ${keyStorePassword} \
${keyPassword} ${keyPassword}
# Ref: https://docs.fastlane.tools/getting-started/android/beta-deployment/#uploading-your-app # 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 # ./fastlane beta
printf "\n================================================================================\n" printf "\n================================================================================\n"
printf "Please check the information below:\n" printf "Please check the information below:\n"
printf "File vector-gplay-arm64-v8a-release-signed.apk:\n" printf "File vector-gplay-rustCrypto-arm64-v8a-release-signed.apk:\n"
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-arm64-v8a-release-signed.apk | grep package ${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-rustCrypto-arm64-v8a-release-signed.apk | grep package
printf "File vector-gplay-armeabi-v7a-release-signed.apk:\n" printf "File vector-gplay-rustCrypto-armeabi-v7a-release-signed.apk:\n"
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk | grep package ${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-rustCrypto-armeabi-v7a-release-signed.apk | grep package
printf "File vector-gplay-x86-release-signed.apk:\n" printf "File vector-gplay-rustCrypto-x86-release-signed.apk:\n"
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-x86-release-signed.apk | grep package ${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-rustCrypto-x86-release-signed.apk | grep package
printf "File vector-gplay-x86_64-release-signed.apk:\n" printf "File vector-gplay-rustCrypto-x86_64-release-signed.apk:\n"
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-x86_64-release-signed.apk | grep package ${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-rustCrypto-x86_64-release-signed.apk | grep package
printf "\n" printf "\n"
read -p "Does it look correct? Press enter when it's done." read -p "Does it look correct? Press enter when it's done."
printf "\n================================================================================\n" printf "\n================================================================================\n"
read -p "Installing apk on a real device, press enter when a real device is connected. " 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} 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." 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."

View File

@ -33,11 +33,11 @@ knit {
// Note: 2 digits max for each value // Note: 2 digits max for each value
ext.versionMajor = 1 ext.versionMajor = 1
ext.versionMinor = 5 ext.versionMinor = 6
// Note: even values are reserved for regular release, odd values for hotfix release. // 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 // When creating a hotfix, you should decrease the value, since the current value
// is the value for the next regular release. // is the value for the next regular release.
ext.versionPatch = 34 ext.versionPatch = 2
static def getGitTimestamp() { static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct' def cmd = 'git show -s --format=%ct'

View File

@ -17,6 +17,7 @@ package im.vector.app.gplay.features.settings.troubleshoot
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import im.vector.app.R 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.pushers.PushersManager
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.troubleshoot.TroubleshootTest 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 org.matrix.android.sdk.api.session.pushers.PusherState
import javax.inject.Inject import javax.inject.Inject
@ -60,16 +63,18 @@ class TestTokenRegistration @Inject constructor(
) )
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_token_registration_quick_fix) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_token_registration_quick_fix) {
override fun doFix() { override fun doFix() {
val workId = pushersManager.enqueueRegisterPusherWithFcmKey(fcmToken) context.lifecycleScope.launch(Dispatchers.IO) {
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo -> val workId = pushersManager.enqueueRegisterPusherWithFcmKey(fcmToken)
if (workInfo != null) { WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo ->
if (workInfo.state == WorkInfo.State.SUCCEEDED) { if (workInfo != null) {
manager?.retry(testParameters) if (workInfo.state == WorkInfo.State.SUCCEEDED) {
} else if (workInfo.state == WorkInfo.State.FAILED) { manager?.retry(testParameters)
manager?.retry(testParameters) } else if (workInfo.state == WorkInfo.State.FAILED) {
manager?.retry(testParameters)
}
} }
} })
}) }
} }
} }

View File

@ -26,8 +26,11 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.DefaultPreferences 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.FcmHelper
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -38,7 +41,12 @@ import javax.inject.Inject
class GoogleFcmHelper @Inject constructor( class GoogleFcmHelper @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
@DefaultPreferences private val sharedPrefs: SharedPreferences, @DefaultPreferences private val sharedPrefs: SharedPreferences,
appScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers
) : FcmHelper { ) : FcmHelper {
private val scope = CoroutineScope(appScope.coroutineContext + coroutineDispatchers.io)
companion object { companion object {
private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN"
} }
@ -64,7 +72,9 @@ class GoogleFcmHelper @Inject constructor(
.addOnSuccessListener { token -> .addOnSuccessListener { token ->
storeFcmToken(token) storeFcmToken(token)
if (registerPusher) { if (registerPusher) {
pushersManager.enqueueRegisterPusherWithFcmKey(token) scope.launch {
pushersManager.enqueueRegisterPusherWithFcmKey(token)
}
} }
} }
.addOnFailureListener { e -> .addOnFailureListener { e ->

View File

@ -27,6 +27,10 @@ import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.pushers.VectorPushHandler import im.vector.app.core.pushers.VectorPushHandler
import im.vector.app.features.settings.VectorPreferences 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 org.matrix.android.sdk.api.logger.LoggerTag
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -43,6 +47,12 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
@Inject lateinit var vectorPushHandler: VectorPushHandler @Inject lateinit var vectorPushHandler: VectorPushHandler
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper @Inject lateinit var unifiedPushHelper: UnifiedPushHelper
private val scope = CoroutineScope(SupervisorJob())
override fun onDestroy() {
scope.cancel()
super.onDestroy()
}
override fun onNewToken(token: String) { override fun onNewToken(token: String) {
Timber.tag(loggerTag.value).d("New Firebase token") Timber.tag(loggerTag.value).d("New Firebase token")
fcmHelper.storeFcmToken(token) fcmHelper.storeFcmToken(token)
@ -51,7 +61,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
activeSessionHolder.hasActiveSession() && activeSessionHolder.hasActiveSession() &&
unifiedPushHelper.isEmbeddedDistributor() unifiedPushHelper.isEmbeddedDistributor()
) { ) {
pushersManager.enqueueRegisterPusher(token, getString(R.string.pusher_http_url)) scope.launch {
pushersManager.enqueueRegisterPusher(token, getString(R.string.pusher_http_url))
}
} }
} }

View File

@ -311,7 +311,7 @@ dependencies {
// Fix issue with Jitsi. Inspired from https://github.com/android/android-test/issues/861#issuecomment-872067868 // 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) // 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. //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.testCore
androidTestImplementation libs.androidx.testRunner androidTestImplementation libs.androidx.testRunner

View File

@ -22,14 +22,14 @@ import javax.inject.Inject
interface GetDeviceInfoUseCase { interface GetDeviceInfoUseCase {
fun execute(): CryptoDeviceInfo suspend fun execute(): CryptoDeviceInfo
} }
class DefaultGetDeviceInfoUseCase @Inject constructor( class DefaultGetDeviceInfoUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder private val activeSessionHolder: ActiveSessionHolder
) : GetDeviceInfoUseCase { ) : GetDeviceInfoUseCase {
override fun execute(): CryptoDeviceInfo { override suspend fun execute(): CryptoDeviceInfo {
return activeSessionHolder.getActiveSession().cryptoService().getMyCryptoDevice() return activeSessionHolder.getActiveSession().cryptoService().getMyCryptoDevice()
} }
} }

View File

@ -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)) return enqueueRegisterPusher(pushKey, stringProvider.getString(R.string.pusher_http_url))
} }
fun enqueueRegisterPusher( suspend fun enqueueRegisterPusher(
pushKey: String, pushKey: String,
gateway: String gateway: String
): UUID { ): UUID {
@ -62,7 +62,7 @@ class PushersManager @Inject constructor(
return currentSession.pushersService().enqueueAddHttpPusher(pusher) return currentSession.pushersService().enqueueAddHttpPusher(pusher)
} }
private fun createHttpPusher( private suspend fun createHttpPusher(
pushKey: String, pushKey: String,
gateway: String gateway: String
) = HttpPusher( ) = HttpPusher(

View File

@ -76,7 +76,9 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
coroutineScope.launch { coroutineScope.launch {
unifiedPushHelper.storeCustomOrDefaultGateway(endpoint) { unifiedPushHelper.storeCustomOrDefaultGateway(endpoint) {
unifiedPushHelper.getPushGateway()?.let { unifiedPushHelper.getPushGateway()?.let {
pushersManager.enqueueRegisterPusher(endpoint, it) coroutineScope.launch {
pushersManager.enqueueRegisterPusher(endpoint, it)
}
} }
} }
} }

View File

@ -17,6 +17,7 @@
package im.vector.app.core.session.clientinfo package im.vector.app.core.session.clientinfo
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import javax.inject.Inject import javax.inject.Inject
@ -27,16 +28,19 @@ class DeleteUnusedClientInformationUseCase @Inject constructor(
suspend fun execute(deviceInfoList: List<DeviceInfo>): Result<Unit> = runCatching { suspend fun execute(deviceInfoList: List<DeviceInfo>): Result<Unit> = runCatching {
// A defensive approach against local storage reports an empty device list (although it is not a seen situation). // 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) if (deviceInfoList.isEmpty()) return Result.success(Unit)
val dispatcher = activeSessionHolder.getSafeActiveSession()?.coroutineDispatchers?.io
val expectedClientInfoKeyList = deviceInfoList.map { MATRIX_CLIENT_INFO_KEY_PREFIX + it.deviceId } ?: return@runCatching
activeSessionHolder withContext(dispatcher) {
.getSafeActiveSession() val expectedClientInfoKeyList = deviceInfoList.map { MATRIX_CLIENT_INFO_KEY_PREFIX + it.deviceId }
?.accountDataService() activeSessionHolder
?.getUserAccountDataEventsStartWith(MATRIX_CLIENT_INFO_KEY_PREFIX) .getSafeActiveSession()
?.map { it.type } ?.accountDataService()
?.subtract(expectedClientInfoKeyList.toSet()) ?.getUserAccountDataEventsStartWith(MATRIX_CLIENT_INFO_KEY_PREFIX)
?.forEach { userAccountDataKeyToDelete -> ?.map { it.type }
activeSessionHolder.getSafeActiveSession()?.accountDataService()?.deleteUserAccountData(userAccountDataKeyToDelete) ?.subtract(expectedClientInfoKeyList.toSet())
} ?.forEach { userAccountDataKeyToDelete ->
activeSessionHolder.getSafeActiveSession()?.accountDataService()?.deleteUserAccountData(userAccountDataKeyToDelete)
}
}
} }
} }

View File

@ -29,6 +29,7 @@ import com.airbnb.mvrx.viewModel
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.startSyncing import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.extensions.vectorStore import im.vector.app.core.extensions.vectorStore
@ -59,6 +60,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.failure.GlobalError
import org.matrix.android.sdk.api.session.Session
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -170,6 +172,19 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
} }
private fun handleAppStarted() { 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)) { if (intent.hasExtra(EXTRA_NEXT_INTENT)) {
// Start the next Activity // Start the next Activity
startSyncing() startSyncing()
@ -248,18 +263,7 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
} }
} }
args.clearCredentials -> { args.clearCredentials -> {
lifecycleScope.launch { signout(session, onboardingStore, ignoreServerError = false)
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()
}
} }
args.clearCache -> { args.clearCache -> {
lifecycleScope.launch { lifecycleScope.launch {
@ -272,6 +276,26 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), 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) { override fun handleInvalidToken(globalError: GlobalError.InvalidToken) {
// No op here // No op here
Timber.w("Ignoring invalid token global error") Timber.w("Ignoring invalid token global error")
@ -299,12 +323,20 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
} }
} }
private fun displayError(failure: Throwable) { private fun displaySignOutFailedDialog(
session: Session,
onboardingStore: VectorSessionStore,
) {
if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle(R.string.dialog_title_error) .setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(failure)) .setMessage(R.string.sign_out_failed_dialog_message)
.setPositiveButton(R.string.global_retry) { _, _ -> doCleanUp() } .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) } .setNegativeButton(R.string.action_cancel) { _, _ -> startNextActivityAndFinish(ignoreClearCredentials = true) }
.setCancelable(false) .setCancelable(false)
.show() .show()

View File

@ -52,7 +52,7 @@ class SharedSecuredStorageResetAllFragment :
views.ssssResetOtherDevices.debouncedClicks { views.ssssResetOtherDevices.debouncedClicks {
withState(sharedViewModel) { withState(sharedViewModel) {
DeviceListBottomSheet.newInstance(it.userId, false).show(childFragmentManager, "DEV_LIST") DeviceListBottomSheet.newInstance(it.userId).show(childFragmentManager, "DEV_LIST")
} }
} }

View File

@ -275,7 +275,7 @@ class SelfVerificationController @Inject constructor(
id("notice_div") id("notice_div")
} }
// Option to verify with another device // Option to verify with another device
if (state.hasAnyOtherSession) { if (state.hasAnyOtherSession.invoke() == true) {
bottomSheetVerificationActionItem { bottomSheetVerificationActionItem {
id("start") id("start")
title(host.stringProvider.getString(R.string.verification_verify_with_another_device)) title(host.stringProvider.getString(R.string.verification_verify_with_another_device))

View File

@ -83,7 +83,7 @@ data class SelfVerificationViewState(
val transactionId: String? = null, val transactionId: String? = null,
val currentDeviceCanCrossSign: Boolean = false, val currentDeviceCanCrossSign: Boolean = false,
val userWantsToCancel: Boolean = false, val userWantsToCancel: Boolean = false,
val hasAnyOtherSession: Boolean = false, val hasAnyOtherSession: Async<Boolean> = Uninitialized,
val quadSContainsSecrets: Boolean = false, val quadSContainsSecrets: Boolean = false,
val isVerificationRequired: Boolean = false, val isVerificationRequired: Boolean = false,
val isThisSessionVerified: Boolean = false, val isThisSessionVerified: Boolean = false,
@ -146,21 +146,28 @@ class SelfVerificationViewModel @AssistedInject constructor(
} }
} }
val hasAnyOtherSession = session.cryptoService() setState { copy(hasAnyOtherSession = Loading()) }
.getCryptoDeviceInfo(session.myUserId) viewModelScope.launch {
.any { val hasAnyOtherSession = session.cryptoService()
it.deviceId != session.sessionParams.deviceId .getCryptoDeviceInfo(session.myUserId)
} .any {
it.deviceId != session.sessionParams.deviceId
}
setState {
copy(
hasAnyOtherSession = Success(hasAnyOtherSession)
)
}
}
setState { setState {
copy( copy(
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(), currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
quadSContainsSecrets = session.sharedSecretStorageService().isRecoverySetup(), quadSContainsSecrets = session.sharedSecretStorageService().isRecoverySetup(),
hasAnyOtherSession = hasAnyOtherSession
) )
} }
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
val isThisSessionVerified = session.cryptoService().crossSigningService().isCrossSigningVerified() val isThisSessionVerified = session.cryptoService().crossSigningService().isCrossSigningVerified()
setState { setState {
copy( copy(

View File

@ -469,11 +469,21 @@ class HomeActivity :
private fun handleOnNewSession(event: HomeActivityViewEvents.CurrentSessionNotVerified) { private fun handleOnNewSession(event: HomeActivityViewEvents.CurrentSessionNotVerified) {
// We need to ask // 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( promptSecurityEvent(
uid = PopupAlertManager.VERIFY_SESSION_UID, uid = PopupAlertManager.VERIFY_SESSION_UID,
userItem = event.userItem, userItem = event.userItem,
titleRes = R.string.crosssigning_verify_this_session, titleRes = titleRes,
descRes = R.string.confirm_your_identity, descRes = descRes,
) { ) {
it.navigator.requestSelfSessionVerification(it) it.navigator.requestSelfSessionVerification(it)
} }

View File

@ -23,7 +23,7 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
data class CurrentSessionNotVerified( data class CurrentSessionNotVerified(
val userItem: MatrixItem.UserItem, val userItem: MatrixItem.UserItem,
// val waitForIncomingRequest: Boolean = true, val afterMigration: Boolean
) : HomeActivityViewEvents ) : HomeActivityViewEvents
data class CurrentSessionCannotBeVerified( data class CurrentSessionCannotBeVerified(

View File

@ -453,6 +453,7 @@ class HomeActivityViewModel @AssistedInject constructor(
_viewEvents.post( _viewEvents.post(
HomeActivityViewEvents.CurrentSessionNotVerified( HomeActivityViewEvents.CurrentSessionNotVerified(
session.getUserOrDefault(session.myUserId).toMatrixItem(), session.getUserOrDefault(session.myUserId).toMatrixItem(),
vectorPreferences.isOnRustCrypto() && vectorPreferences.hadExistingLegacyData()
) )
) )
} else { } else {

View File

@ -92,11 +92,6 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(
} }
init { 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( combine(
session.flow().liveUserCryptoDevices(session.myUserId), session.flow().liveUserCryptoDevices(session.myUserId),
@ -108,6 +103,12 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(
deleteUnusedClientInformation(infoList) 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 infoList
.asSequence() .asSequence()
.filter { .filter {

View File

@ -528,6 +528,8 @@ class MessageItemFactory @Inject constructor(
) )
val playable = messageContent.mimeType == MimeTypes.Gif 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_() return MessageImageVideoItem_()
.attributes(attributes) .attributes(attributes)
@ -549,7 +551,7 @@ class MessageItemFactory @Inject constructor(
} }
} }
}.apply { }.apply {
if (playable && vectorPreferences.autoplayAnimatedImages()) { if (playableIfAutoplay && vectorPreferences.autoplayAnimatedImages()) {
mode(ImageContentRenderer.Mode.ANIMATED_THUMBNAIL) mode(ImageContentRenderer.Mode.ANIMATED_THUMBNAIL)
} }
} }

View File

@ -322,11 +322,10 @@ class RoomListViewModel @AssistedInject constructor(
} }
private fun handleDeleteLocalRooms() { private fun handleDeleteLocalRooms() {
val localRoomIds = session.roomService() viewModelScope.launch(session.coroutineDispatchers.io) {
.getRoomSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) }) val localRoomIds = session.roomService()
.map { it.roomId } .getRoomSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) })
.map { it.roomId }
viewModelScope.launch {
localRoomIds.forEach { localRoomIds.forEach {
session.roomService().deleteLocalRoom(it) session.roomService().deleteLocalRoom(it)
} }

View File

@ -49,7 +49,7 @@ class DataAttachmentRoomProvider(
return getItem(position).let { return getItem(position).let {
when (it) { when (it) {
is ImageContentRenderer.Data -> { is ImageContentRenderer.Data -> {
if (it.mimeType == MimeTypes.Gif) { if (it.mimeType == MimeTypes.Gif || it.mimeType == MimeTypes.Webp) {
AttachmentInfo.AnimatedImage( AttachmentInfo.AnimatedImage(
uid = it.eventId, uid = it.eventId,
url = it.url ?: "", url = it.url ?: "",

View File

@ -135,7 +135,7 @@ class ImageContentRenderer @Inject constructor(
if (mode == Mode.ANIMATED_THUMBNAIL) it if (mode == Mode.ANIMATED_THUMBNAIL) it
else it.dontAnimate() else it.dontAnimate()
} }
.transform(cornerTransformation) .optionalTransform(cornerTransformation)
.into(imageView) .into(imageView)
} }
@ -167,7 +167,7 @@ class ImageContentRenderer @Inject constructor(
} }
req req
.fitCenter() .optionalFitCenter()
.into(target) .into(target)
} }
@ -211,7 +211,7 @@ class ImageContentRenderer @Inject constructor(
return false return false
} }
}) })
.fitCenter() .optionalFitCenter()
.into(imageView) .into(imageView)
} }

View File

@ -71,7 +71,7 @@ class RoomEventsAttachmentProvider(
allowNonMxcUrls = it.root.sendState.isSending() allowNonMxcUrls = it.root.sendState.isSending()
) )
if (content.mimeType == MimeTypes.Gif) { if (content.mimeType == MimeTypes.Gif || content.mimeType == MimeTypes.Webp) {
AttachmentInfo.AnimatedImage( AttachmentInfo.AnimatedImage(
uid = it.eventId, uid = it.eventId,
url = content.url ?: "", url = content.url ?: "",

View File

@ -38,6 +38,7 @@ import im.vector.app.config.OnboardingVariant
import im.vector.app.core.debug.DebugNavigator import im.vector.app.core.debug.DebugNavigator
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.fatalError 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.VectorFeatures
import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsViewRoom import im.vector.app.features.analytics.extensions.toAnalyticsViewRoom
@ -256,8 +257,9 @@ class DefaultNavigator @Inject constructor(
otherSessionId otherSessionId
) )
if (context is AppCompatActivity) { if (context is AppCompatActivity) {
SelfVerificationBottomSheet.forTransaction(request.transactionId) context.supportFragmentManager.commitTransaction(allowStateLoss = true) {
.show(context.supportFragmentManager, "VERIF") add(SelfVerificationBottomSheet.forTransaction(request.transactionId), "VERIF")
}
} }
} }
} }
@ -271,8 +273,9 @@ class DefaultNavigator @Inject constructor(
// .filter { it.deviceId != session.sessionParams.deviceId } // .filter { it.deviceId != session.sessionParams.deviceId }
// .map { it.deviceId } // .map { it.deviceId }
if (context is AppCompatActivity) { if (context is AppCompatActivity) {
SelfVerificationBottomSheet.verifyOwnUntrustedDevice() context.supportFragmentManager.commitTransaction(allowStateLoss = true) {
.show(context.supportFragmentManager, "VERIF") add(SelfVerificationBottomSheet.verifyOwnUntrustedDevice(), "VERIF")
}
// if (otherSessions.isNotEmpty()) { // if (otherSessions.isNotEmpty()) {
// val pr = session.cryptoService().verificationService().requestSelfKeyVerification( // val pr = session.cryptoService().verificationService().requestSelfKeyVerification(
// supportedVerificationMethodsProvider.provide()) // supportedVerificationMethodsProvider.provide())

View File

@ -24,7 +24,9 @@ import im.vector.app.R
import im.vector.app.core.resources.BuildMeta import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.utils.FirstThrottler import im.vector.app.core.utils.FirstThrottler
import im.vector.app.features.displayname.getBestName import im.vector.app.features.displayname.getBestName
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences 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.Session
import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.content.ContentUrlResolver
import org.matrix.android.sdk.api.session.getUserOrDefault 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. * 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?) { fun setCurrentRoom(roomId: String?) {
updateEvents { val dispatcher = currentSession?.coroutineDispatchers?.io ?: return
val hasChanged = roomId != currentRoomId val scope = currentSession?.coroutineScope ?: return
currentRoomId = roomId scope.launch(dispatcher) {
if (hasChanged && roomId != null) { updateEvents {
it.clearMessagesForRoom(roomId) 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. * 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?) { fun setCurrentThread(threadId: String?) {
updateEvents { val dispatcher = currentSession?.coroutineDispatchers?.io ?: return
val hasChanged = threadId != currentThreadId val scope = currentSession?.coroutineScope ?: return
currentThreadId = threadId scope.launch(dispatcher) {
currentRoomId?.let { roomId -> updateEvents {
if (hasChanged && threadId != null) { val hasChanged = threadId != currentThreadId
it.clearMessagesForThread(roomId, threadId) currentThreadId = threadId
currentRoomId?.let { roomId ->
if (hasChanged && threadId != null) {
it.clearMessagesForThread(roomId, threadId)
}
} }
} }
} }

View File

@ -22,6 +22,4 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
sealed class DeviceListAction : VectorViewModelAction { sealed class DeviceListAction : VectorViewModelAction {
data class SelectDevice(val device: CryptoDeviceInfo) : DeviceListAction() data class SelectDevice(val device: CryptoDeviceInfo) : DeviceListAction()
object DeselectDevice : DeviceListAction() object DeselectDevice : DeviceListAction()
data class ManuallyVerify(val deviceId: String) : DeviceListAction()
} }

View File

@ -47,16 +47,7 @@ class DeviceListBottomSheet :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel.observeViewEvents { viewModel.observeViewEvents {
when (it) { // nop
is DeviceListBottomSheetViewEvents.Verify -> {
// TODO selfverif
// VerificationBottomSheet.withArgs(
// // roomId = null,
// otherUserId = it.userId,
// transactionId = it.txID
// ).show(requireActivity().supportFragmentManager, "REQPOP")
}
}
} }
} }
@ -109,13 +100,12 @@ class DeviceListBottomSheet :
@Parcelize @Parcelize
data class Args( data class Args(
val userId: String, val userId: String,
val allowDeviceAction: Boolean
) : Parcelable ) : Parcelable
companion object { companion object {
fun newInstance(userId: String, allowDeviceAction: Boolean = true): DeviceListBottomSheet { fun newInstance(userId: String): DeviceListBottomSheet {
return DeviceListBottomSheet().apply { return DeviceListBottomSheet().apply {
setArguments(Args(userId, allowDeviceAction)) setArguments(Args(userId))
} }
} }
} }

View File

@ -21,6 +21,4 @@ import im.vector.app.core.platform.VectorViewEvents
/** /**
* Transient events for device list screen. * Transient events for device list screen.
*/ */
sealed class DeviceListBottomSheetViewEvents : VectorViewEvents { sealed class DeviceListBottomSheetViewEvents : VectorViewEvents
data class Verify(val userId: String, val txID: String) : DeviceListBottomSheetViewEvents()
}

View File

@ -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.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo 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.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.session.getUserOrDefault
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
@ -42,7 +41,7 @@ import org.matrix.android.sdk.flow.flow
data class DeviceListViewState( data class DeviceListViewState(
val userId: String, val userId: String,
val allowDeviceAction: Boolean, val myUserId: String,
val userItem: MatrixItem? = null, val userItem: MatrixItem? = null,
val memberCrossSigningKey: MXCrossSigningInfo? = null, val memberCrossSigningKey: MXCrossSigningInfo? = null,
val myDeviceId: String = "", val myDeviceId: String = "",
@ -69,7 +68,7 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(
val session = EntryPoints.get(viewModelContext.app(), SingletonEntryPoint::class.java).activeSessionHolder().getActiveSession() val session = EntryPoints.get(viewModelContext.app(), SingletonEntryPoint::class.java).activeSessionHolder().getActiveSession()
return DeviceListViewState( return DeviceListViewState(
userId = userId, userId = userId,
allowDeviceAction = args.allowDeviceAction, myUserId = session.myUserId,
userItem = session.getUserOrDefault(userId).toMatrixItem(), userItem = session.getUserOrDefault(userId).toMatrixItem(),
myDeviceId = session.sessionParams.deviceId, myDeviceId = session.sessionParams.deviceId,
) )
@ -104,7 +103,6 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(
when (action) { when (action) {
is DeviceListAction.SelectDevice -> selectDevice(action) is DeviceListAction.SelectDevice -> selectDevice(action)
is DeviceListAction.DeselectDevice -> deselectDevice() is DeviceListAction.DeselectDevice -> deselectDevice()
is DeviceListAction.ManuallyVerify -> manuallyVerify(action)
} }
} }
@ -121,7 +119,6 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(
} }
private fun selectDevice(action: DeviceListAction.SelectDevice) { private fun selectDevice(action: DeviceListAction.SelectDevice) {
if (!initialState.allowDeviceAction) return
setState { setState {
copy(selectedDevice = action.device) copy(selectedDevice = action.device)
} }
@ -132,18 +129,4 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(
copy(selectedDevice = null) 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))
}
}
}
} }

View File

@ -28,7 +28,6 @@ import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.databinding.BottomSheetGenericListBinding import im.vector.app.databinding.BottomSheetGenericListBinding
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -63,8 +62,4 @@ class DeviceTrustInfoActionFragment :
override fun invalidate() = withState(viewModel) { override fun invalidate() = withState(viewModel) {
epoxyController.setData(it) epoxyController.setData(it)
} }
override fun onVerifyManually(device: CryptoDeviceInfo) {
viewModel.handle(DeviceListAction.ManuallyVerify(device.deviceId))
}
} }

View File

@ -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.genericItem
import im.vector.app.core.ui.list.genericWithValueItem import im.vector.app.core.ui.list.genericWithValueItem
import im.vector.app.core.utils.DimensionConverter 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.app.features.settings.devices.TrustUtils
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
import me.gujun.android.span.span import me.gujun.android.span.span
import org.matrix.android.sdk.api.extensions.orFalse 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 org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import javax.inject.Inject import javax.inject.Inject
@ -39,13 +36,10 @@ class DeviceTrustInfoEpoxyController @Inject constructor(
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val dimensionConverter: DimensionConverter, private val dimensionConverter: DimensionConverter,
private val vectorPreferences: VectorPreferences
) : ) :
TypedEpoxyController<DeviceListViewState>() { TypedEpoxyController<DeviceListViewState>() {
interface InteractionListener { interface InteractionListener
fun onVerifyManually(device: CryptoDeviceInfo)
}
var interactionListener: InteractionListener? = null var interactionListener: InteractionListener? = null
@ -54,7 +48,7 @@ class DeviceTrustInfoEpoxyController @Inject constructor(
data?.selectedDevice?.let { cryptoDeviceInfo -> data?.selectedDevice?.let { cryptoDeviceInfo ->
val trustMSK = data.memberCrossSigningKey?.isTrusted().orFalse() val trustMSK = data.memberCrossSigningKey?.isTrusted().orFalse()
val legacyMode = data.memberCrossSigningKey == null 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( val trustLevel = TrustUtils.shieldForTrust(
isMyDevice, isMyDevice,
trustMSK, trustMSK,
@ -126,18 +120,7 @@ class DeviceTrustInfoEpoxyController @Inject constructor(
id("warn") id("warn")
centered(false) centered(false)
textColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) textColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
text(host.stringProvider.getString(R.string.verification_profile_device_untrust_info).toEpoxyCharSequence()) text(host.stringProvider.getString(R.string.verification_profile_other_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)
}
} }
} }
} }

View File

@ -254,6 +254,8 @@ class VectorPreferences @Inject constructor(
const val TAKE_PHOTO_VIDEO_MODE_PHOTO = 1 const val TAKE_PHOTO_VIDEO_MODE_PHOTO = 1
const val TAKE_PHOTO_VIDEO_MODE_VIDEO = 2 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 // Background sync modes
// some preferences keys must be kept after a logout // 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) 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)
}
}
} }

View File

@ -25,6 +25,8 @@ class GetEncryptionTrustLevelForDeviceUseCase @Inject constructor(
private val getEncryptionTrustLevelForOtherDeviceUseCase: GetEncryptionTrustLevelForOtherDeviceUseCase, 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? { fun execute(currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo, cryptoDeviceInfo: CryptoDeviceInfo?): RoomEncryptionTrustLevel? {
if (cryptoDeviceInfo == null) { if (cryptoDeviceInfo == null) {
return null return null

View File

@ -17,6 +17,7 @@
package im.vector.app.features.settings.troubleshoot package im.vector.app.features.settings.troubleshoot
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import im.vector.app.R import im.vector.app.R
@ -72,13 +73,15 @@ class TestEndpointAsTokenRegistration @Inject constructor(
} }
private fun unregisterThenRegister(testParameters: TestParameters, pushKey: String) { 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) unregisterUnifiedPushUseCase.execute(pushersManager)
registerUnifiedPush(distributor = "", testParameters, pushKey) registerUnifiedPush(distributor = "", testParameters, pushKey)
} }
} }
private fun registerUnifiedPush( private suspend fun registerUnifiedPush(
distributor: String, distributor: String,
testParameters: TestParameters, testParameters: TestParameters,
pushKey: String, pushKey: String,
@ -106,7 +109,9 @@ class TestEndpointAsTokenRegistration @Inject constructor(
pushKey: String, pushKey: String,
) { ) {
unifiedPushHelper.showSelectDistributorDialog(context) { selection -> unifiedPushHelper.showSelectDistributorDialog(context) { selection ->
registerUnifiedPush(distributor = selection, testParameters, pushKey) context.lifecycleScope.launch {
registerUnifiedPush(distributor = selection, testParameters, pushKey)
}
} }
} }
} }

View File

@ -19,6 +19,7 @@ package im.vector.app.core.device
import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakeCryptoService import im.vector.app.test.fakes.FakeCryptoService
import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeSession
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test import org.junit.Test
@ -31,7 +32,7 @@ class DefaultGetDeviceInfoUseCaseTest {
private val getDeviceInfoUseCase = DefaultGetDeviceInfoUseCase(activeSessionHolder.instance) private val getDeviceInfoUseCase = DefaultGetDeviceInfoUseCase(activeSessionHolder.instance)
@Test @Test
fun `when execute, then get crypto device info`() { fun `when execute, then get crypto device info`() = runTest {
val result = getDeviceInfoUseCase.execute() val result = getDeviceInfoUseCase.execute()
result shouldBeEqualTo cryptoService.cryptoDeviceInfo result shouldBeEqualTo cryptoService.cryptoDeviceInfo

View File

@ -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.PusherFixture
import im.vector.app.test.fixtures.SessionParamsFixture import im.vector.app.test.fixtures.SessionParamsFixture
import io.mockk.mockk import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo
@ -56,7 +57,7 @@ class PushersManagerTest {
) )
@Test @Test
fun `when enqueueRegisterPusher, then HttpPusher created and enqueued`() { fun `when enqueueRegisterPusher, then HttpPusher created and enqueued`() = runTest {
val pushKey = "abc" val pushKey = "abc"
val gateway = "123" val gateway = "123"
val pusherAppId = "app-id" val pusherAppId = "app-id"

View File

@ -84,5 +84,5 @@ class FakeCryptoService(
} }
} }
override fun getMyCryptoDevice() = cryptoDeviceInfo override suspend fun getMyCryptoDevice() = cryptoDeviceInfo
} }

Some files were not shown because too many files have changed in this diff Show More