From 7e7a9533453c5de2e3b90b03333a24bb0d7a42f0 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 22 Oct 2020 16:52:32 +0200 Subject: [PATCH 01/23] Crypto cleaning and perf improvement --- .../crypto/CryptoSessionInfoProvider.kt | 61 ++++ .../internal/crypto/DefaultCryptoService.kt | 171 +++------- .../sdk/internal/crypto/DeviceListManager.kt | 2 +- .../sdk/internal/crypto/EventDecryptor.kt | 169 ++++++++++ .../crypto/InboundGroupSessionStore.kt | 96 ++++++ .../sdk/internal/crypto/MXOlmDevice.kt | 8 +- .../crypto/OutgoingGossipingRequestManager.kt | 4 +- .../DefaultCrossSigningService.kt | 123 +++++-- .../SessionToCryptoRoomMembersUpdate.kt | 26 -- .../crypto/crosssigning/ShieldTrustUpdater.kt | 126 ------- .../crypto/crosssigning/UpdateTrustWorker.kt | 318 ++++++++++++++++++ .../keysbackup/DefaultKeysBackupService.kt | 13 +- .../internal/crypto/tasks/EncryptEventTask.kt | 11 +- .../internal/crypto/tasks/SendEventTask.kt | 7 +- .../tasks/SendVerificationMessageTask.kt | 13 +- .../DefaultVerificationService.kt | 8 +- .../SendVerificationMessageWorker.kt | 5 +- .../VerificationMessageProcessor.kt | 6 +- .../database/EventInsertLiveObserver.kt | 41 ++- .../sdk/internal/session/DefaultSession.kt | 5 +- .../sdk/internal/session/SessionComponent.kt | 3 + .../sdk/internal/session/SessionModule.kt | 5 - .../EventRelationsAggregationProcessor.kt | 8 +- .../room/relation/DefaultRelationService.kt | 15 +- .../session/room/send/DefaultSendService.kt | 8 +- .../session/room/send/SendEventWorker.kt | 2 +- .../room/send/queue/SendEventQueuedTask.kt | 2 +- .../room/summary/RoomSummaryUpdater.kt | 34 +- .../session/room/timeline/DefaultTimeline.kt | 2 +- .../room/timeline/TimelineEventDecryptor.kt | 48 +-- .../internal/session/sync/RoomSyncHandler.kt | 10 +- .../internal/session/sync/job/SyncThread.kt | 2 +- 32 files changed, 898 insertions(+), 454 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/SessionToCryptoRoomMembersUpdate.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ShieldTrustUpdater.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt new file mode 100644 index 0000000000..68cc139521 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventEntityFields +import org.matrix.android.sdk.internal.database.query.whereType +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper +import org.matrix.android.sdk.internal.util.fetchCopied +import javax.inject.Inject + +/** + * The crypto module needs some information regarding rooms that are stored + * in the session DB, this class encapsulate this functionality + */ +class CryptoSessionInfoProvider @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, +) { + + fun isRoomEncrypted(roomId: String): Boolean { + val encryptionEvent = monarchy.fetchCopied { realm -> + EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) + .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") + .isNotNull(EventEntityFields.STATE_KEY) // should be an empty key + .findFirst() + } + return encryptionEvent != null + } + + /** + * @param allActive if true return joined as well as invited, if false, only joined + */ + fun getRoomUserIdsForCrypto(roomId: String, allActive: Boolean): List { + var userIds: List = emptyList() + monarchy.doWithRealm { realm -> + userIds = if (allActive) { + RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() + } else { + RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds() + } + } + return userIds + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index b78afe6d41..0a18aba8ba 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -17,12 +17,9 @@ package org.matrix.android.sdk.internal.crypto import android.content.Context -import android.os.Handler -import android.os.Looper import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData import com.squareup.moshi.Types -import com.zhuinden.monarchy.Monarchy import dagger.Lazy import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope @@ -51,9 +48,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent -import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter -import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension @@ -68,7 +63,6 @@ import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent -import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent @@ -82,21 +76,15 @@ import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask -import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService -import org.matrix.android.sdk.internal.database.model.EventEntity -import org.matrix.android.sdk.internal.database.model.EventEntityFields -import org.matrix.android.sdk.internal.database.query.whereType import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.MoshiProvider -import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.foldToCallback 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.RoomMemberHelper import org.matrix.android.sdk.internal.session.sync.model.SyncResponse import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskThread @@ -104,11 +92,11 @@ import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import org.matrix.android.sdk.internal.util.fetchCopied import org.matrix.olm.OlmManager import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject +import kotlin.jvm.Throws import kotlin.math.max /** @@ -171,28 +159,16 @@ internal class DefaultCryptoService @Inject constructor( private val setDeviceNameTask: SetDeviceNameTask, private val uploadKeysTask: UploadKeysTask, private val loadRoomMembersTask: LoadRoomMembersTask, - @SessionDatabase private val monarchy: Monarchy, + private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val taskExecutor: TaskExecutor, private val cryptoCoroutineScope: CoroutineScope, - private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, - private val sendToDeviceTask: SendToDeviceTask, - private val messageEncrypter: MessageEncrypter + private val eventDecryptor: EventDecryptor ) : CryptoService { - init { - verificationService.cryptoService = this - } - - private val uiHandler = Handler(Looper.getMainLooper()) - private val isStarting = AtomicBoolean(false) private val isStarted = AtomicBoolean(false) - // The date of the last time we forced establishment - // of a new session for each user:device. - private val lastNewSessionForcedDates = MXUsersDevicesMap() - fun onStateEvent(roomId: String, event: Event) { when (event.getClearType()) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) @@ -612,13 +588,7 @@ internal class DefaultCryptoService @Inject constructor( * @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM */ override fun isRoomEncrypted(roomId: String): Boolean { - val encryptionEvent = monarchy.fetchCopied { realm -> - EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) - .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") - .isNotNull(EventEntityFields.STATE_KEY) - .findFirst() - } - return encryptionEvent != null + return cryptoSessionInfoProvider.isRoomEncrypted(roomId) } /** @@ -660,11 +630,8 @@ internal class DefaultCryptoService @Inject constructor( eventType: String, roomId: String, callback: MatrixCallback) { + // moved to crypto scope to have uptodate values cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { -// if (!isStarted()) { -// Timber.v("## CRYPTO | encryptEventContent() : wait after e2e init") -// internalStart(false) -// } val userIds = getRoomUserIds(roomId) var alg = roomEncryptorsStore.get(roomId) if (alg == null) { @@ -720,14 +687,7 @@ internal class DefaultCryptoService @Inject constructor( * @param callback the callback to return data or null */ override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback) { - cryptoCoroutineScope.launch { - val result = runCatching { - withContext(coroutineDispatchers.crypto) { - internalDecryptEvent(event, timeline) - } - } - result.foldToCallback(callback) - } + eventDecryptor.decryptEventAsync(event, timeline, callback) } /** @@ -739,42 +699,7 @@ internal class DefaultCryptoService @Inject constructor( */ @Throws(MXCryptoError::class) private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult { - val eventContent = event.content - if (eventContent == null) { - Timber.e("## CRYPTO | decryptEvent : empty event content") - throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON) - } else { - val algorithm = eventContent["algorithm"]?.toString() - val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm) - if (alg == null) { - val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) - Timber.e("## CRYPTO | decryptEvent() : $reason") - throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason) - } else { - try { - return alg.decryptEvent(event, timeline) - } catch (mxCryptoError: MXCryptoError) { - Timber.d("## CRYPTO | internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError") - if (algorithm == MXCRYPTO_ALGORITHM_OLM) { - if (mxCryptoError is MXCryptoError.Base - && mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE) { - // need to find sending device - val olmContent = event.content.toModel() - cryptoStore.getUserDevices(event.senderId ?: "") - ?.values - ?.firstOrNull { it.identityKey() == olmContent?.senderKey } - ?.let { - markOlmSessionForUnwedging(event.senderId ?: "", it) - } - ?: run { - Timber.v("## CRYPTO | markOlmSessionForUnwedging() : Failed to find sender crypto device") - } - } - } - throw mxCryptoError - } - } - } + return eventDecryptor.decryptEvent(event, timeline) } /** @@ -828,7 +753,7 @@ internal class DefaultCryptoService @Inject constructor( */ private fun onRoomKeyEvent(event: Event) { val roomKeyContent = event.getClearContent().toModel() ?: return - Timber.v("## CRYPTO | GOSSIP onRoomKeyEvent() : type<${event.type}> , sessionId<${roomKeyContent.sessionId}>") + Timber.v("## CRYPTO | GOSSIP onRoomKeyEvent() : type<${event.getClearType()}> , sessionId<${roomKeyContent.sessionId}>") if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) { Timber.e("## CRYPTO | GOSSIP onRoomKeyEvent() : missing fields") return @@ -935,19 +860,9 @@ internal class DefaultCryptoService @Inject constructor( } private fun getRoomUserIds(roomId: String): List { - var userIds: List = emptyList() - monarchy.doWithRealm { realm -> - // Check whether the event content must be encrypted for the invited members. - val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser() - && shouldEncryptForInvitedMembers(roomId) - - userIds = if (encryptForInvitedMembers) { - RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() - } else { - RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds() - } - } - return userIds + val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser() + && shouldEncryptForInvitedMembers(roomId) + return cryptoSessionInfoProvider.getRoomUserIdsForCrypto(roomId, encryptForInvitedMembers) } /** @@ -1257,38 +1172,38 @@ internal class DefaultCryptoService @Inject constructor( incomingGossipingRequestManager.removeRoomKeysRequestListener(listener) } - private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) { - val deviceKey = deviceInfo.identityKey() - - val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0 - val now = System.currentTimeMillis() - if (now - lastForcedDate < CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) { - Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another") - return - } - - Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}") - lastNewSessionForcedDates.setObject(senderId, deviceKey, now) - - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) - - // Now send a blank message on that session so the other side knows about it. - // (The keyshare request is sent in the clear so that won't do) - // We send this first such that, as long as the toDevice messages arrive in the - // same order we sent them, the other end will get this first, set up the new session, - // then get the keyshare request and send the key over this new session (because it - // is the session it has most recently received a message on). - val payloadJson = mapOf("type" to EventType.DUMMY) - - val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) - val sendToDeviceMap = MXUsersDevicesMap() - sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload) - Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}") - val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - sendToDeviceTask.execute(sendToDeviceParams) - } - } +// private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) { +// val deviceKey = deviceInfo.identityKey() +// +// val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0 +// val now = System.currentTimeMillis() +// if (now - lastForcedDate < CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) { +// Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another") +// return +// } +// +// Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}") +// lastNewSessionForcedDates.setObject(senderId, deviceKey, now) +// +// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { +// ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) +// +// // Now send a blank message on that session so the other side knows about it. +// // (The keyshare request is sent in the clear so that won't do) +// // We send this first such that, as long as the toDevice messages arrive in the +// // same order we sent them, the other end will get this first, set up the new session, +// // then get the keyshare request and send the key over this new session (because it +// // is the session it has most recently received a message on). +// val payloadJson = mapOf("type" to EventType.DUMMY) +// +// val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) +// val sendToDeviceMap = MXUsersDevicesMap() +// sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload) +// Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}") +// val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) +// sendToDeviceTask.execute(sendToDeviceParams) +// } +// } /** * Provides the list of unknown devices diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index ab30d3052d..42df6b354b 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -377,7 +377,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM } // Update devices trust for these users - dispatchDeviceChange(downloadUsers) + // dispatchDeviceChange(downloadUsers) return onKeysDownloadSucceed(filteredUsers, response.failures) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt new file mode 100644 index 0000000000..b423b5d738 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.session.crypto.MXCryptoError +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.toModel +import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction +import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter +import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent +import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.extensions.foldToCallback +import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import timber.log.Timber +import javax.inject.Inject +import kotlin.jvm.Throws + +@SessionScope +internal class EventDecryptor @Inject constructor( + private val cryptoCoroutineScope: CoroutineScope, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val roomDecryptorProvider: RoomDecryptorProvider, + private val messageEncrypter: MessageEncrypter, + private val sendToDeviceTask: SendToDeviceTask, + private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, + private val cryptoStore: IMXCryptoStore +) { + + // The date of the last time we forced establishment + // of a new session for each user:device. + private val lastNewSessionForcedDates = MXUsersDevicesMap() + + /** + * Decrypt an event + * + * @param event the raw event. + * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. + * @return the MXEventDecryptionResult data, or throw in case of error + */ + @Throws(MXCryptoError::class) + fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { + return internalDecryptEvent(event, timeline) + } + + /** + * Decrypt an event asynchronously + * + * @param event the raw event. + * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. + * @param callback the callback to return data or null + */ + fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback) { + // is it needed to do that on the crypto scope?? + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + runCatching { + internalDecryptEvent(event, timeline) + }.foldToCallback(callback) + } + } + + /** + * Decrypt an event + * + * @param event the raw event. + * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. + * @return the MXEventDecryptionResult data, or null in case of error + */ + @Throws(MXCryptoError::class) + private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult { + val eventContent = event.content + if (eventContent == null) { + Timber.e("## CRYPTO | decryptEvent : empty event content") + throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON) + } else { + val algorithm = eventContent["algorithm"]?.toString() + val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm) + if (alg == null) { + val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) + Timber.e("## CRYPTO | decryptEvent() : $reason") + throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason) + } else { + try { + return alg.decryptEvent(event, timeline) + } catch (mxCryptoError: MXCryptoError) { + Timber.d("## CRYPTO | internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError") + if (algorithm == MXCRYPTO_ALGORITHM_OLM) { + if (mxCryptoError is MXCryptoError.Base + && mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE) { + // need to find sending device + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + val olmContent = event.content.toModel() + cryptoStore.getUserDevices(event.senderId ?: "") + ?.values + ?.firstOrNull { it.identityKey() == olmContent?.senderKey } + ?.let { + markOlmSessionForUnwedging(event.senderId ?: "", it) + } + ?: run { + Timber.v("## CRYPTO | markOlmSessionForUnwedging() : Failed to find sender crypto device") + } + } + } + } + throw mxCryptoError + } + } + } + } + + // coroutineDispatchers.crypto scope + private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) { + val deviceKey = deviceInfo.identityKey() + + val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0 + val now = System.currentTimeMillis() + if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) { + Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another") + return + } + + Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}") + lastNewSessionForcedDates.setObject(senderId, deviceKey, now) + + // offload this from crypto thread (?) + cryptoCoroutineScope.launch(coroutineDispatchers.computation) { + ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) + + // Now send a blank message on that session so the other side knows about it. + // (The keyshare request is sent in the clear so that won't do) + // We send this first such that, as long as the toDevice messages arrive in the + // same order we sent them, the other end will get this first, set up the new session, + // then get the keyshare request and send the key over this new session (because it + // is the session it has most recently received a message on). + val payloadJson = mapOf("type" to EventType.DUMMY) + + val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) + val sendToDeviceMap = MXUsersDevicesMap() + sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload) + Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}") + withContext(coroutineDispatchers.io) { + val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) + sendToDeviceTask.execute(sendToDeviceParams) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt new file mode 100644 index 0000000000..722fc0d92d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import android.util.LruCache +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import timber.log.Timber +import java.util.Timer +import java.util.TimerTask +import javax.inject.Inject + +/** + * Allows to cache and batch store operations on inbound group session store. + * Because it is used in the decrypt flow, that can be called quite rapidly + */ +internal class InboundGroupSessionStore @Inject constructor( + private val store: IMXCryptoStore, + private val cryptoCoroutineScope: CoroutineScope, + private val coroutineDispatchers: MatrixCoroutineDispatchers) { + + private data class CacheKey( + val sessionId: String, + val senderKey: String + ) + + private val sessionCache = object : LruCache(30) { + override fun entryRemoved(evicted: Boolean, key: CacheKey?, oldValue: OlmInboundGroupSessionWrapper2?, newValue: OlmInboundGroupSessionWrapper2?) { + if (evicted && oldValue != null) { + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + Timber.v("## Inbound: entryRemoved ${oldValue.roomId}-${oldValue.senderKey}") + store.storeInboundGroupSessions(listOf(oldValue)) + } + } + } + } + + private val timer = Timer() + private var timerTask: TimerTask? = null + + private val dirtySession = mutableListOf() + + @Synchronized + fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? { + synchronized(sessionCache) { + val known = sessionCache[CacheKey(sessionId, senderKey)] + Timber.v("## Inbound: getInboundGroupSession in cache ${known != null}") + return known ?: store.getInboundGroupSession(sessionId, senderKey)?.also { + Timber.v("## Inbound: getInboundGroupSession cache populate ${it.roomId}") + sessionCache.put(CacheKey(sessionId, senderKey), it) + } + } + } + + @Synchronized + fun storeInBoundGroupSession(wrapper: OlmInboundGroupSessionWrapper2) { + Timber.v("## Inbound: getInboundGroupSession mark as dirty ${wrapper.roomId}-${wrapper.senderKey}") + // We want to batch this a bit for performances + dirtySession.add(wrapper) + + timerTask?.cancel() + timerTask = object : TimerTask() { + override fun run() { + batchSave() + } + } + timer.schedule(timerTask!!, 2_000) + } + + @Synchronized + private fun batchSave() { + val toSave = mutableListOf().apply { addAll(dirtySession) } + dirtySession.clear() + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + Timber.v("## Inbound: getInboundGroupSession batching save of ${dirtySession.size}") + store.storeInboundGroupSessions(toSave) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt index 7a546993b8..1a4d1136c8 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt @@ -44,7 +44,9 @@ internal class MXOlmDevice @Inject constructor( /** * The store where crypto data is saved. */ - private val store: IMXCryptoStore) { + private val store: IMXCryptoStore, + private val inboundGroupSessionStore: InboundGroupSessionStore + ) { /** * @return the Curve25519 key for the account. @@ -657,7 +659,7 @@ internal class MXOlmDevice @Inject constructor( timelineSet.add(messageIndexKey) } - store.storeInboundGroupSessions(listOf(session)) + inboundGroupSessionStore.storeInBoundGroupSession(session) val payload = try { val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE) val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) @@ -745,7 +747,7 @@ internal class MXOlmDevice @Inject constructor( throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON) } - val session = store.getInboundGroupSession(sessionId, senderKey) + val session = inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey) if (session != null) { // Check that the room id matches the original one for the session. This stops diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt index efda663230..c86f2be0a3 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt @@ -88,7 +88,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor( * @param requestBody requestBody */ fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + cryptoCoroutineScope.launch(coroutineDispatchers.computation) { cancelRoomKeyRequest(requestBody, false) } } @@ -99,7 +99,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor( * @param requestBody requestBody */ fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + cryptoCoroutineScope.launch(coroutineDispatchers.computation) { cancelRoomKeyRequest(requestBody, true) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt index b5056a0efd..1871dba0e2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.internal.crypto.crosssigning import androidx.lifecycle.LiveData +import androidx.work.BackoffPolicy +import androidx.work.ExistingWorkPolicy import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService @@ -39,15 +41,20 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.withoutPrefix import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.internal.di.SessionId +import org.matrix.android.sdk.internal.di.WorkManagerProvider +import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import org.matrix.olm.OlmPkSigning import org.matrix.olm.OlmUtility import timber.log.Timber +import java.util.concurrent.TimeUnit import javax.inject.Inject @SessionScope internal class DefaultCrossSigningService @Inject constructor( @UserId private val userId: String, + @SessionId private val sessionId: String, private val cryptoStore: IMXCryptoStore, private val deviceListManager: DeviceListManager, private val initializeCrossSigningTask: InitializeCrossSigningTask, @@ -55,7 +62,7 @@ internal class DefaultCrossSigningService @Inject constructor( private val taskExecutor: TaskExecutor, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope, - private val eventBus: EventBus) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener { + private val workManagerProvider: WorkManagerProvider) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener { private var olmUtility: OlmUtility? = null @@ -360,6 +367,12 @@ internal class DefaultCrossSigningService @Inject constructor( // First let's get my user key val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) + checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId)) + + return UserTrustResult.Success + } + + fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult { val myUserKey = myCrossSigningInfo?.userKey() ?: return UserTrustResult.CrossSigningNotConfigured(userId) @@ -368,15 +381,15 @@ internal class DefaultCrossSigningService @Inject constructor( } // Let's get the other user master key - val otherMasterKey = cryptoStore.getCrossSigningInfo(otherUserId)?.masterKey() - ?: return UserTrustResult.UnknownCrossSignatureInfo(otherUserId) + val otherMasterKey = otherInfo?.masterKey() + ?: return UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "") val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures ?.get(userId) // Signatures made by me ?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}") if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) { - Timber.d("## CrossSigning checkUserTrust false for $otherUserId, not signed by my UserSigningKey") + Timber.d("## CrossSigning checkUserTrust false for ${otherInfo.userId}, not signed by my UserSigningKey") return UserTrustResult.KeyNotSigned(otherMasterKey) } @@ -396,6 +409,15 @@ internal class DefaultCrossSigningService @Inject constructor( // and that MSK is trusted (i know the private key, or is signed by a trusted device) val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) + return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(userId)) + } + + fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List?): UserTrustResult { + // Special case when it's me, + // I have to check that MSK -> USK -> SSK + // and that MSK is trusted (i know the private key, or is signed by a trusted device) +// val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) + val myMasterKey = myCrossSigningInfo?.masterKey() ?: return UserTrustResult.CrossSigningNotConfigured(userId) @@ -423,7 +445,7 @@ internal class DefaultCrossSigningService @Inject constructor( // Maybe it's signed by a locally trusted device? myMasterKey.signatures?.get(userId)?.forEach { (key, value) -> val potentialDeviceId = key.withoutPrefix("ed25519:") - val potentialDevice = cryptoStore.getUserDevice(userId, potentialDeviceId) + val potentialDevice = myDevices?.firstOrNull { it.deviceId == potentialDeviceId } // cryptoStore.getUserDevice(userId, potentialDeviceId) if (potentialDevice != null && potentialDevice.isVerified) { // Check signature validity? try { @@ -561,6 +583,8 @@ internal class DefaultCrossSigningService @Inject constructor( cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { cryptoStore.markMyMasterKeyAsLocallyTrusted(true) checkSelfTrust() + // re-verify all trusts + onUsersDeviceUpdate(listOf(userId)) } } @@ -666,6 +690,55 @@ internal class DefaultCrossSigningService @Inject constructor( return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted)) } + fun checkDeviceTrust(myKeys: MXCrossSigningInfo?, otherKeys: MXCrossSigningInfo?, otherDevice: CryptoDeviceInfo) : DeviceTrustResult { + val locallyTrusted = otherDevice.trustLevel?.isLocallyVerified() + myKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId)) + + if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys)) + + otherKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(otherDevice.userId)) + + // TODO should we force verification ? + if (!otherKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys)) + + // Check if the trust chain is valid + /* + * ┏━━━━━━━━┓ ┏━━━━━━━━┓ + * ┃ ALICE ┃ ┃ BOB ┃ + * ┗━━━━━━━━┛ ┗━━━━━━━━┛ + * MSK ┌────────────▶MSK + * │ + * │ │ │ + * │ SSK │ └──▶ SSK ──────────────────┐ + * │ │ │ + * │ │ USK │ + * └──▶ USK ────────────┘ (not visible by │ + * Alice) │ + * ▼ + * ┌──────────────┐ + * │ BOB's Device │ + * └──────────────┘ + */ + + val otherSSKSignature = otherDevice.signatures?.get(otherKeys.userId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}") + ?: return legacyFallbackTrust( + locallyTrusted, + DeviceTrustResult.MissingDeviceSignature(otherDevice.deviceId, otherKeys.selfSigningKey() + ?.unpaddedBase64PublicKey + ?: "" + ) + ) + + // Check bob's device is signed by bob's SSK + try { + olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable()) + } catch (e: Throwable) { + return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDevice.deviceId, otherSSKSignature, e)) + } + + return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted)) + } + private fun legacyFallbackTrust(locallyTrusted: Boolean?, crossSignTrustFail: DeviceTrustResult): DeviceTrustResult { return if (locallyTrusted == true) { DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true)) @@ -675,36 +748,18 @@ internal class DefaultCrossSigningService @Inject constructor( } override fun onUsersDeviceUpdate(userIds: List) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - Timber.d("## CrossSigning - onUsersDeviceUpdate for ${userIds.size} users") - userIds.forEach { otherUserId -> - checkUserTrust(otherUserId).let { - Timber.v("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}") - setUserKeysAsTrusted(otherUserId, it.isVerified()) - } - } - } + Timber.d("## CrossSigning - onUsersDeviceUpdate for $userIds") + val workerParams = UpdateTrustWorker.Params(sessionId = sessionId, updatedUserIds = userIds) + val workerData = WorkerParamsFactory.toData(workerParams) - // now check device trust - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - userIds.forEach { otherUserId -> - // TODO if my keys have changes, i should recheck all devices of all users? - val devices = cryptoStore.getUserDeviceList(otherUserId) - devices?.forEach { device -> - val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) - Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") - cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) - } + val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setInputData(workerData) + .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS) + .build() - if (otherUserId == userId) { - // It's me, i should check if a newly trusted device is signing my master key - // In this case it will change my MSK trust, and should then re-trigger a check of all other user trust - setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified()) - } - } - - eventBus.post(CryptoToSessionUserTrustChange(userIds)) - } + workManagerProvider.workManager + .beginUniqueWork("TRUST_UPDATE_QUEUE", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) + .enqueue() } private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/SessionToCryptoRoomMembersUpdate.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/SessionToCryptoRoomMembersUpdate.kt deleted file mode 100644 index 271b9e52d3..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/SessionToCryptoRoomMembersUpdate.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2020 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.crosssigning - -data class SessionToCryptoRoomMembersUpdate( - val roomId: String, - val isDirect: Boolean, - val userIds: List -) - -data class CryptoToSessionUserTrustChange( - val userIds: List -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ShieldTrustUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ShieldTrustUpdater.kt deleted file mode 100644 index 05ceba5965..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ShieldTrustUpdater.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2020 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.crosssigning - -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity -import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.SessionLifecycleObserver -import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater -import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.util.createBackgroundHandler -import io.realm.Realm -import io.realm.RealmConfiguration -import kotlinx.coroutines.android.asCoroutineDispatcher -import kotlinx.coroutines.launch -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import timber.log.Timber -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicReference -import javax.inject.Inject - -internal class ShieldTrustUpdater @Inject constructor( - private val eventBus: EventBus, - private val computeTrustTask: ComputeTrustTask, - private val taskExecutor: TaskExecutor, - @SessionDatabase private val sessionRealmConfiguration: RealmConfiguration, - private val roomSummaryUpdater: RoomSummaryUpdater -) : SessionLifecycleObserver { - - companion object { - private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD") - private val BACKGROUND_HANDLER_DISPATCHER = BACKGROUND_HANDLER.asCoroutineDispatcher() - } - - private val backgroundSessionRealm = AtomicReference() - - private val isStarted = AtomicBoolean() - - override fun onStart() { - if (isStarted.compareAndSet(false, true)) { - eventBus.register(this) - BACKGROUND_HANDLER.post { - backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration)) - } - } - } - - override fun onStop() { - if (isStarted.compareAndSet(true, false)) { - eventBus.unregister(this) - BACKGROUND_HANDLER.post { - backgroundSessionRealm.getAndSet(null).also { - it?.close() - } - } - } - } - - @Subscribe - fun onRoomMemberChange(update: SessionToCryptoRoomMembersUpdate) { - if (!isStarted.get()) { - return - } - taskExecutor.executorScope.launch(BACKGROUND_HANDLER_DISPATCHER) { - val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds, update.isDirect)) - // We need to send that back to session base - backgroundSessionRealm.get()?.executeTransaction { realm -> - roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust) - } - } - } - - @Subscribe - fun onTrustUpdate(update: CryptoToSessionUserTrustChange) { - if (!isStarted.get()) { - return - } - onCryptoDevicesChange(update.userIds) - } - - private fun onCryptoDevicesChange(users: List) { - taskExecutor.executorScope.launch(BACKGROUND_HANDLER_DISPATCHER) { - val realm = backgroundSessionRealm.get() ?: return@launch - val distinctRoomIds = realm.where(RoomMemberSummaryEntity::class.java) - .`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray()) - .distinct(RoomMemberSummaryEntityFields.ROOM_ID) - .findAll() - .map { it.roomId } - - distinctRoomIds.forEach { roomId -> - val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() - if (roomSummary?.isEncrypted.orFalse()) { - val allActiveRoomMembers = RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() - try { - val updatedTrust = computeTrustTask.execute( - ComputeTrustTask.Params(allActiveRoomMembers, roomSummary?.isDirect == true) - ) - realm.executeTransaction { - roomSummaryUpdater.updateShieldTrust(it, roomId, updatedTrust) - } - } catch (failure: Throwable) { - Timber.e(failure) - } - } - } - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt new file mode 100644 index 0000000000..e3ac387ddc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -0,0 +1,318 @@ +/* + * Copyright 2020 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.crosssigning + +import android.content.Context +import androidx.work.WorkerParameters +import com.squareup.moshi.JsonClass +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.kotlin.where +import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper +import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity +import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields +import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper +import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity +import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity +import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.CryptoDatabase +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.SessionComponent +import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper +import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker +import org.matrix.android.sdk.internal.worker.SessionWorkerParams +import timber.log.Timber +import javax.inject.Inject + +internal class UpdateTrustWorker(context: Context, + params: WorkerParameters) + : SessionSafeCoroutineWorker(context, params, Params::class.java) { + + @JsonClass(generateAdapter = true) + internal data class Params( + override val sessionId: String, + override val lastFailureMessage: String? = null, + val updatedUserIds: List + ) : SessionWorkerParams + + @Inject lateinit var crossSigningService: DefaultCrossSigningService + + // It breaks the crypto store contract, but we need to batch things :/ + @CryptoDatabase @Inject lateinit var realmConfiguration: RealmConfiguration + @UserId @Inject lateinit var myUserId: String + @Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper + @SessionDatabase @Inject lateinit var sessionRealmConfiguration: RealmConfiguration + + // @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater + @Inject lateinit var cryptoStore: IMXCryptoStore + + override fun injectWith(injector: SessionComponent) { + injector.inject(this) + } + + override suspend fun doSafeWork(params: Params): Result { + var userList = params.updatedUserIds + // Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user, + // or a new device?) So we check all again :/ + + Timber.d("## CrossSigning - Updating trust for $userList") + + // First we check that the users MSK are trusted by mine + // After that we check the trust chain for each devices of each users + Realm.getInstance(realmConfiguration).use { realm -> + realm.executeTransaction { + // By mapping here to model, this object is not live + // I should update it if needed + var myCrossSigningInfo = realm.where(CrossSigningInfoEntity::class.java) + .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId) + .findFirst()?.let { mapCrossSigningInfoEntity(it) } + + var myTrustResult: UserTrustResult? = null + + if (userList.contains(myUserId)) { + Timber.d("## CrossSigning - Clear all trust as a change on my user was detected") + // i am in the list.. but i don't know exactly the delta of change :/ + // If it's my cross signing keys we should refresh all trust + // do it anyway ? + userList = realm.where(CrossSigningInfoEntity::class.java) + .findAll().mapNotNull { it.userId } + Timber.d("## CrossSigning - Updating trust for all $userList") + + // check right now my keys and mark it as trusted as other trust depends on it + val myDevices = realm.where() + .equalTo(UserEntityFields.USER_ID, myUserId) + .findFirst() + ?.devices + ?.map { deviceInfo -> + CryptoMapper.mapToModel(deviceInfo) + } + myTrustResult = crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices).also { + updateCrossSigningKeysTrust(realm, myUserId, it.isVerified()) + // update model reference + myCrossSigningInfo = realm.where(CrossSigningInfoEntity::class.java) + .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId) + .findFirst()?.let { mapCrossSigningInfoEntity(it) } + } + } + + val otherInfos = userList.map { + it to realm.where(CrossSigningInfoEntity::class.java) + .equalTo(CrossSigningInfoEntityFields.USER_ID, it) + .findFirst()?.let { mapCrossSigningInfoEntity(it) } + } + .toMap() + + val trusts = otherInfos.map { infoEntry -> + infoEntry.key to when (infoEntry.key) { + myUserId -> myTrustResult + else -> { + crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, infoEntry.value).also { + Timber.d("## CrossSigning - user:${infoEntry.key} result:$it") + } + } + } + }.toMap() + + // TODO! if it's me and my keys has changed... I have to reset trust for everyone! + // i have all the new trusts, update DB + trusts.forEach { + val verified = it.value?.isVerified() == true + updateCrossSigningKeysTrust(realm, it.key, verified) + } + + // Ok so now we have to check device trust for all these users.. + Timber.v("## CrossSigning - Updating devices cross trust users ${trusts.keys}") + trusts.keys.forEach { + val devicesEntities = realm.where() + .equalTo(UserEntityFields.USER_ID, it) + .findFirst() + ?.devices + + val trustMap = devicesEntities?.map { device -> + device to crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfos[it], CryptoMapper.mapToModel(device)) + }?.toMap() + + // Update trust if needed + devicesEntities?.forEach { device -> + val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified() + Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}") + if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) { + Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified") + // need to save + val trustEntity = device.trustLevelEntity + if (trustEntity == null) { + realm.createObject(TrustLevelEntity::class.java).let { + it.locallyVerified = false + it.crossSignedVerified = crossSignedVerified + device.trustLevelEntity = it + } + } else { + trustEntity.crossSignedVerified = crossSignedVerified + } + } + } + } + } + } + + // So Cross Signing keys trust is updated, device trust is updated + // We can now update room shields? in the session DB? + + Timber.d("## CrossSigning - Updating shields for impacted rooms...") + Realm.getInstance(sessionRealmConfiguration).use { it -> + it.executeTransaction { realm -> + val distinctRoomIds = realm.where(RoomMemberSummaryEntity::class.java) + .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) + .distinct(RoomMemberSummaryEntityFields.ROOM_ID) + .findAll() + .map { it.roomId } + Timber.d("## CrossSigning - ... impacted rooms $distinctRoomIds") + distinctRoomIds.forEach { roomId -> + val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() + if (roomSummary?.isEncrypted == true) { + Timber.d("## CrossSigning - Check shield state for room $roomId") + val allActiveRoomMembers = RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() + try { + val updatedTrust = computeRoomShield(allActiveRoomMembers, roomSummary) + if (roomSummary.roomEncryptionTrustLevel != updatedTrust) { + Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust") + roomSummary.roomEncryptionTrustLevel = updatedTrust + } + } catch (failure: Throwable) { + Timber.e(failure) + } + } + } + } + } + + return Result.success() + } + + private fun updateCrossSigningKeysTrust(realm: Realm, userId: String, verified: Boolean) { + val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java) + .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) + .findFirst() + xInfoEntity?.crossSigningKeys?.forEach { info -> + // optimization to avoid trigger updates when there is no change.. + if (info.trustLevelEntity?.isVerified() != verified) { + Timber.d("## CrossSigning - Trust change for $userId : $verified") + val level = info.trustLevelEntity + if (level == null) { + val newLevel = realm.createObject(TrustLevelEntity::class.java) + newLevel.locallyVerified = verified + newLevel.crossSignedVerified = verified + info.trustLevelEntity = newLevel + } else { + level.locallyVerified = verified + level.crossSignedVerified = verified + } + } + } + } + + private fun computeRoomShield(activeMemberUserIds: List, roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { + Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> $activeMemberUserIds") + // The set of “all users” depends on the type of room: + // For regular / topic rooms, all users including yourself, are considered when decorating a room + // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room + val listToCheck = if (roomSummaryEntity.isDirect) { + activeMemberUserIds.filter { it != myUserId } + } else { + activeMemberUserIds + } + + val allTrustedUserIds = listToCheck + .filter { userId -> + Realm.getInstance(realmConfiguration).use { + it.where(CrossSigningInfoEntity::class.java) + .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) + .findFirst()?.let { mapCrossSigningInfoEntity(it) }?.isTrusted() == true + } + } + val myCrossKeys = Realm.getInstance(realmConfiguration).use { + it.where(CrossSigningInfoEntity::class.java) + .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId) + .findFirst()?.let { mapCrossSigningInfoEntity(it) } + } + + return if (allTrustedUserIds.isEmpty()) { + RoomEncryptionTrustLevel.Default + } else { + // If one of the verified user as an untrusted device -> warning + // If all devices of all verified users are trusted -> green + // else -> black + allTrustedUserIds + .mapNotNull { uid -> + Realm.getInstance(realmConfiguration).use { + it.where() + .equalTo(UserEntityFields.USER_ID, uid) + .findFirst() + ?.devices + ?.map { + CryptoMapper.mapToModel(it) + } + } + } + .flatten() + .let { allDevices -> + Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { it.deviceId }}") + if (myCrossKeys != null) { + allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() } + } else { + // Legacy method + allDevices.any { !it.isVerified } + } + } + .let { hasWarning -> + if (hasWarning) { + RoomEncryptionTrustLevel.Warning + } else { + if (listToCheck.size == allTrustedUserIds.size) { + // all users are trusted and all devices are verified + RoomEncryptionTrustLevel.Trusted + } else { + RoomEncryptionTrustLevel.Default + } + } + } + } + } + + private fun mapCrossSigningInfoEntity(xsignInfo: CrossSigningInfoEntity): MXCrossSigningInfo { + val userId = xsignInfo.userId ?: "" + return MXCrossSigningInfo( + userId = userId, + crossSigningKeys = xsignInfo.crossSigningKeys.mapNotNull { + crossSigningKeysMapper.map(userId, it) + } + ) + } + + override fun buildErrorParams(params: Params, message: String): Params { + return params.copy(lastFailureMessage = params.lastFailureMessage ?: message) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index 64579c1b67..bbd8ebd000 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -234,7 +234,10 @@ internal class DefaultKeysBackupService @Inject constructor( this.callback = object : MatrixCallback { override fun onSuccess(data: KeysVersion) { // Reset backup markers. - cryptoStore.resetBackupMarkers() + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + // move tx out of UI thread + cryptoStore.resetBackupMarkers() + } val keyBackupVersion = KeysVersionResult( algorithm = createKeysBackupVersionBody.algorithm, @@ -596,7 +599,9 @@ internal class DefaultKeysBackupService @Inject constructor( val importResult = awaitCallback { restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it) } - cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version) + withContext(coroutineDispatchers.crypto) { + cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version) + } Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}") } else { Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}") @@ -1123,7 +1128,9 @@ internal class DefaultKeysBackupService @Inject constructor( if (retrievedMegolmBackupAuthData != null) { keysBackupVersion = keysVersionResult - cryptoStore.setKeyBackupVersion(keysVersionResult.version) + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + cryptoStore.setKeyBackupVersion(keysVersionResult.version) + } onServerDataRetrieved(keysVersionResult.count, keysVersionResult.hash) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt index 1b88fbe9cc..56b267decd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt @@ -33,14 +33,13 @@ internal interface EncryptEventTask : Task { data class Params(val roomId: String, val event: Event, /**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/ - val keepKeys: List? = null, - val crypto: CryptoService + val keepKeys: List? = null ) } internal class DefaultEncryptEventTask @Inject constructor( -// private val crypto: CryptoService - private val localEchoRepository: LocalEchoRepository + private val localEchoRepository: LocalEchoRepository, + private val cryptoService: CryptoService ) : EncryptEventTask { override suspend fun execute(params: EncryptEventTask.Params): Event { // don't want to wait for any query @@ -60,7 +59,7 @@ internal class DefaultEncryptEventTask @Inject constructor( // try { // let it throws awaitCallback { - params.crypto.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it) + cryptoService.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it) }.let { result -> val modifiedContent = HashMap(result.eventContent) params.keepKeys?.forEach { toKeep -> @@ -81,7 +80,7 @@ internal class DefaultEncryptEventTask @Inject constructor( ).toContent(), forwardingCurve25519KeyChain = emptyList(), senderCurve25519Key = result.eventContent["sender_key"] as? String, - claimedEd25519Key = params.crypto.getMyDevice().fingerprint() + claimedEd25519Key = cryptoService.getMyDevice().fingerprint() ) } else { null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt index 1a712036c8..8b739c4b64 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.greenrobot.eventbus.EventBus -import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.network.executeRequest @@ -29,8 +28,7 @@ import javax.inject.Inject internal interface SendEventTask : Task { data class Params( val event: Event, - val encrypt: Boolean, - val cryptoService: CryptoService? + val encrypt: Boolean ) } @@ -68,8 +66,7 @@ internal class DefaultSendEventTask @Inject constructor( return encryptEventTask.execute(EncryptEventTask.Params( params.event.roomId ?: "", params.event, - listOf("m.relates_to"), - params.cryptoService!! + listOf("m.relates_to") )) } return params.event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt index 782300c7b0..cedb7a6618 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt @@ -15,21 +15,20 @@ */ package org.matrix.android.sdk.internal.crypto.tasks -import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.task.Task -import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface SendVerificationMessageTask : Task { data class Params( - val event: Event, - val cryptoService: CryptoService? + val event: Event ) } @@ -37,6 +36,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor( private val localEchoRepository: LocalEchoRepository, private val encryptEventTask: DefaultEncryptEventTask, private val roomAPI: RoomAPI, + private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val eventBus: EventBus) : SendVerificationMessageTask { override suspend fun execute(params: SendVerificationMessageTask.Params): String { @@ -62,13 +62,12 @@ internal class DefaultSendVerificationMessageTask @Inject constructor( } private suspend fun handleEncryption(params: SendVerificationMessageTask.Params): Event { - if (params.cryptoService?.isRoomEncrypted(params.event.roomId ?: "") == true) { + if (cryptoSessionInfoProvider.isRoomEncrypted(params.event.roomId ?: "")) { try { return encryptEventTask.execute(EncryptEventTask.Params( params.event.roomId ?: "", params.event, - listOf("m.relates_to"), - params.cryptoService + listOf("m.relates_to") )) } catch (throwable: Throwable) { // We said it's ok to send verification request in clear diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index c0f4671046..f3a794154c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -20,7 +20,6 @@ import android.os.Handler import android.os.Looper import dagger.Lazy import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -111,9 +110,6 @@ internal class DefaultVerificationService @Inject constructor( private val uiHandler = Handler(Looper.getMainLooper()) - // Cannot be injected in constructor as it creates a dependency cycle - lateinit var cryptoService: CryptoService - // map [sender : [transaction]] private val txMap = HashMap>() @@ -1203,7 +1199,9 @@ internal class DefaultVerificationService @Inject constructor( // TODO refactor this with the DM one Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices") - val targetDevices = otherDevices ?: cryptoService.getUserDevices(otherUserId).map { it.deviceId } + val targetDevices = otherDevices ?: cryptoStore.getUserDevices(otherUserId) + ?.values?.map { it.deviceId } ?: emptyList() + val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() } val transport = verificationTransportToDeviceFactory.createTransport(null) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt index fa7cd2e6f9..538d7b56e9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt @@ -20,7 +20,6 @@ import androidx.work.Data import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.failure.shouldBeRetried -import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker @@ -47,7 +46,6 @@ internal class SendVerificationMessageWorker(context: Context, @Inject lateinit var sendVerificationMessageTask: SendVerificationMessageTask @Inject lateinit var localEchoRepository: LocalEchoRepository - @Inject lateinit var cryptoService: CryptoService @Inject lateinit var cancelSendTracker: CancelSendTracker override fun injectWith(injector: SessionComponent) { @@ -70,8 +68,7 @@ internal class SendVerificationMessageWorker(context: Context, return try { val resultEventId = sendVerificationMessageTask.execute( SendVerificationMessageTask.Params( - event = localEvent, - cryptoService = cryptoService + event = localEvent ) ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt index 4994325625..74827eeb2a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt @@ -15,7 +15,6 @@ */ package org.matrix.android.sdk.internal.crypto.verification -import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.events.model.Event @@ -34,12 +33,13 @@ import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import io.realm.Realm +import org.matrix.android.sdk.internal.crypto.EventDecryptor import timber.log.Timber import java.util.ArrayList import javax.inject.Inject internal class VerificationMessageProcessor @Inject constructor( - private val cryptoService: CryptoService, + private val eventDecryptor: EventDecryptor, private val verificationService: DefaultVerificationService, @UserId private val userId: String, @DeviceId private val deviceId: String? @@ -82,7 +82,7 @@ internal class VerificationMessageProcessor @Inject constructor( // TODO use a global event decryptor? attache to session and that listen to new sessionId? // for now decrypt sync try { - val result = cryptoService.decryptEvent(event, "") + val result = eventDecryptor.decryptEvent(event, "") event.mxDecryptionResult = OlmDecryptionResult( payload = result.clearEvent, senderKey = result.senderCurve25519Key, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index 11a877e7c4..71f978c03c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -17,10 +17,6 @@ package org.matrix.android.sdk.internal.database import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.session.crypto.CryptoService -import org.matrix.android.sdk.api.session.crypto.MXCryptoError -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertEntity @@ -31,12 +27,13 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import io.realm.RealmConfiguration import io.realm.RealmResults import kotlinx.coroutines.launch +import org.matrix.android.sdk.internal.crypto.EventDecryptor import timber.log.Timber import javax.inject.Inject internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>, - private val cryptoService: CryptoService) + private val eventDecryptor: EventDecryptor) : RealmLiveEntityObserver(realmConfiguration) { override val query = Monarchy.Query { @@ -74,7 +71,7 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real return@forEach } val domainEvent = event.asDomain() - decryptIfNeeded(domainEvent) +// decryptIfNeeded(domainEvent) processors.filter { it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType) }.forEach { @@ -89,22 +86,22 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real } } - private fun decryptIfNeeded(event: Event) { - if (event.isEncrypted() && event.mxDecryptionResult == null) { - try { - val result = cryptoService.decryptEvent(event, event.roomId ?: "") - event.mxDecryptionResult = OlmDecryptionResult( - payload = result.clearEvent, - senderKey = result.senderCurve25519Key, - keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, - forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain - ) - } catch (e: MXCryptoError) { - Timber.v("Failed to decrypt event") - // TODO -> we should keep track of this and retry, or some processing will never be handled - } - } - } +// private fun decryptIfNeeded(event: Event) { +// if (event.isEncrypted() && event.mxDecryptionResult == null) { +// try { +// val result = eventDecryptor.decryptEvent(event, event.roomId ?: "") +// event.mxDecryptionResult = OlmDecryptionResult( +// payload = result.clearEvent, +// senderKey = result.senderCurve25519Key, +// keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, +// forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain +// ) +// } catch (e: MXCryptoError) { +// Timber.v("Failed to decrypt event") +// // TODO -> we should keep track of this and retry, or some processing will never be handled +// } +// } +// } private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean { return processors.any { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 359dd265d8..7e182525a9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -65,7 +65,6 @@ import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor -import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDecryptor import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.session.sync.job.SyncThread import org.matrix.android.sdk.internal.session.sync.job.SyncWorker @@ -115,7 +114,6 @@ internal class DefaultSession @Inject constructor( private val accountDataService: Lazy, private val _sharedSecretStorageService: Lazy, private val accountService: Lazy, - private val timelineEventDecryptor: TimelineEventDecryptor, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val defaultIdentityService: DefaultIdentityService, private val integrationManagerService: IntegrationManagerService, @@ -162,7 +160,6 @@ internal class DefaultSession @Inject constructor( lifecycleObservers.forEach { it.onStart() } } eventBus.register(this) - timelineEventDecryptor.start() eventSenderProcessor.start() } @@ -200,7 +197,7 @@ internal class DefaultSession @Inject constructor( override fun close() { assert(isOpen) stopSync() - timelineEventDecryptor.destroy() + // timelineEventDecryptor.destroy() uiHandler.post { lifecycleObservers.forEach { it.onStop() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index ed586d35f8..e6fd5a7a0c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.internal.crypto.CancelGossipRequestWorker import org.matrix.android.sdk.internal.crypto.CryptoModule import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker import org.matrix.android.sdk.internal.crypto.SendGossipWorker +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker import org.matrix.android.sdk.internal.crypto.verification.SendVerificationMessageWorker import org.matrix.android.sdk.internal.di.MatrixComponent import org.matrix.android.sdk.internal.di.SessionAssistedInjectModule @@ -128,6 +129,8 @@ internal interface SessionComponent { fun inject(worker: SendGossipWorker) + fun inject(worker: UpdateTrustWorker) + @Component.Factory interface Factory { fun create( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 102a34d9de..32949d60c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -41,7 +41,6 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.typing.TypingUsersTracker -import org.matrix.android.sdk.internal.crypto.crosssigning.ShieldTrustUpdater import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask @@ -333,10 +332,6 @@ internal abstract class SessionModule { @IntoSet abstract fun bindWidgetUrlFormatter(formatter: DefaultWidgetURLFormatter): SessionLifecycleObserver - @Binds - @IntoSet - abstract fun bindShieldTrustUpdated(updater: ShieldTrustUpdater): SessionLifecycleObserver - @Binds @IntoSet abstract fun bindIdentityService(service: DefaultIdentityService): SessionLifecycleObserver diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 9ff0deec89..d090ba5296 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -15,7 +15,7 @@ */ package org.matrix.android.sdk.internal.session.room -import org.matrix.android.sdk.api.session.crypto.CryptoService +import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType @@ -47,7 +47,6 @@ import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor -import io.realm.Realm import timber.log.Timber import javax.inject.Inject @@ -78,9 +77,8 @@ private fun VerificationState?.toState(newState: VerificationState): Verificatio return newState } -internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String, - private val cryptoService: CryptoService -) : EventInsertLiveProcessor { +internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String) + : EventInsertLiveProcessor { private val allowedTypes = listOf( EventType.MESSAGE, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index 38c542e07e..a7f3f83980 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -21,7 +21,6 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.message.MessageType @@ -31,13 +30,13 @@ import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.task.TaskExecutor @@ -47,11 +46,9 @@ import timber.log.Timber internal class DefaultRelationService @AssistedInject constructor( @Assisted private val roomId: String, - @SessionId private val sessionId: String, -// private val timeLineSendEventWorkCommon: TimelineSendEventWorkCommon, private val eventSenderProcessor: EventSenderProcessor, private val eventFactory: LocalEchoEventFactory, - private val cryptoService: CryptoService, + private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val findReactionEventForUndoTask: FindReactionEventForUndoTask, private val fetchEditHistoryTask: FetchEditHistoryTask, private val timelineEventMapper: TimelineEventMapper, @@ -122,7 +119,7 @@ internal class DefaultRelationService @AssistedInject constructor( val event = eventFactory .createReplaceTextEvent(roomId, targetEventId, newBodyText, newBodyAutoMarkdown, msgType, compatibilityBodyText) .also { saveLocalEcho(it) } - return eventSenderProcessor.postEvent(event, cryptoService.isRoomEncrypted(roomId)) + return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) } override fun editReply(replyToEdit: TimelineEvent, @@ -139,11 +136,11 @@ internal class DefaultRelationService @AssistedInject constructor( compatibilityBodyText ) .also { saveLocalEcho(it) } - return eventSenderProcessor.postEvent(event, cryptoService.isRoomEncrypted(roomId)) + return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) } override fun fetchEditHistory(eventId: String, callback: MatrixCallback>) { - val params = FetchEditHistoryTask.Params(roomId, cryptoService.isRoomEncrypted(roomId), eventId) + val params = FetchEditHistoryTask.Params(roomId, cryptoSessionInfoProvider.isRoomEncrypted(roomId), eventId) fetchEditHistoryTask .configureWith(params) { this.callback = callback @@ -156,7 +153,7 @@ internal class DefaultRelationService @AssistedInject constructor( ?.also { saveLocalEcho(it) } ?: return null - return eventSenderProcessor.postEvent(event, cryptoService.isRoomEncrypted(roomId)) + return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) } override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 5c395c1907..b13ce15da6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -25,7 +25,6 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.content.ContentAttachmentData -import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isTextMessage @@ -45,6 +44,7 @@ import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.CancelableBag import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.NoOpCancellable +import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.session.content.UploadContentWorker @@ -64,7 +64,7 @@ internal class DefaultSendService @AssistedInject constructor( private val workManagerProvider: WorkManagerProvider, @SessionId private val sessionId: String, private val localEchoEventFactory: LocalEchoEventFactory, - private val cryptoService: CryptoService, + private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val taskExecutor: TaskExecutor, private val localEchoRepository: LocalEchoRepository, private val eventSenderProcessor: EventSenderProcessor, @@ -251,7 +251,7 @@ internal class DefaultSendService @AssistedInject constructor( private fun internalSendMedia(allLocalEchoes: List, attachment: ContentAttachmentData, compressBeforeSending: Boolean): Cancelable { val cancelableBag = CancelableBag() - allLocalEchoes.groupBy { cryptoService.isRoomEncrypted(it.roomId!!) } + allLocalEchoes.groupBy { cryptoSessionInfoProvider.isRoomEncrypted(it.roomId!!) } .apply { keys.forEach { isRoomEncrypted -> // Should never be empty @@ -282,7 +282,7 @@ internal class DefaultSendService @AssistedInject constructor( } private fun sendEvent(event: Event): Cancelable { - return eventSenderProcessor.postEvent(event, cryptoService.isRoomEncrypted(event.roomId!!)) + return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(event.roomId!!)) } private fun createLocalEcho(event: Event) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt index 2c835ff56c..37a429d242 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt @@ -87,7 +87,7 @@ internal class SendEventWorker(context: Context, Timber.v("## SendEvent: [${System.currentTimeMillis()}] Send event ${params.eventId}") return try { - sendEventTask.execute(SendEventTask.Params(event, params.isEncrypted ?: cryptoService.isRoomEncrypted(event.roomId), cryptoService)) + sendEventTask.execute(SendEventTask.Params(event, params.isEncrypted ?: cryptoService.isRoomEncrypted(event.roomId))) Result.success() } catch (exception: Throwable) { if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt index 09da0908f9..21a4145a9d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt @@ -38,7 +38,7 @@ internal class SendEventQueuedTask( override fun toString() = "[SendEventRunnableTask ${event.eventId}]" override suspend fun execute() { - sendEventTask.execute(SendEventTask.Params(event, encrypt, cryptoService)) + sendEventTask.execute(SendEventTask.Params(event, encrypt)) } override fun onTaskFailed() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index f9a27c367c..8c71604183 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -16,10 +16,8 @@ package org.matrix.android.sdk.internal.session.room.summary -import dagger.Lazy import io.realm.Realm -import org.greenrobot.eventbus.EventBus -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.Membership @@ -28,9 +26,11 @@ import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomNameContent import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.crypto.EventDecryptor import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.internal.crypto.crosssigning.SessionToCryptoRoomMembersUpdate +import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService import org.matrix.android.sdk.internal.database.mapper.ContentMapper +import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields @@ -46,7 +46,6 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper -import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDecryptor import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications import timber.log.Timber @@ -56,8 +55,8 @@ internal class RoomSummaryUpdater @Inject constructor( @UserId private val userId: String, private val roomDisplayNameResolver: RoomDisplayNameResolver, private val roomAvatarResolver: RoomAvatarResolver, - private val timelineEventDecryptor: Lazy, - private val eventBus: EventBus) { + private val eventDecryptor: EventDecryptor, + private val crossSigningService: DefaultCrossSigningService) { fun update(realm: Realm, roomId: String, @@ -126,9 +125,14 @@ internal class RoomSummaryUpdater @Inject constructor( } roomSummaryEntity.updateHasFailedSending() - if (latestPreviewableEvent?.root?.type == EventType.ENCRYPTED && latestPreviewableEvent.root?.decryptionResultJson == null) { + val root = latestPreviewableEvent?.root + if (root?.type == EventType.ENCRYPTED && root.decryptionResultJson == null) { Timber.v("Should decrypt ${latestPreviewableEvent.eventId}") - timelineEventDecryptor.get().requestDecryption(TimelineEventDecryptor.DecryptionRequest(latestPreviewableEvent.eventId, "")) + // mmm i want to decrypt now or is it ok to do it async? + tryOrNull { + eventDecryptor.decryptEvent(root.asDomain(), "") + // eventDecryptor.decryptEventAsync(root.asDomain(), "", NoOpMatrixCallback()) + } } if (updateMembers) { @@ -142,7 +146,8 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers) if (roomSummaryEntity.isEncrypted) { - eventBus.post(SessionToCryptoRoomMembersUpdate(roomId, roomSummaryEntity.isDirect, roomSummaryEntity.otherMemberIds.toList() + userId)) + // mmm maybe we could only refresh shield instead of checking trust also? + crossSigningService.onUsersDeviceUpdate(roomSummaryEntity.otherMemberIds.toList()) } } } @@ -156,13 +161,4 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.updateHasFailedSending() roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) } - - fun updateShieldTrust(realm: Realm, - roomId: String, - trust: RoomEncryptionTrustLevel?) { - val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) - if (roomSummaryEntity.isEncrypted) { - roomSummaryEntity.roomEncryptionTrustLevel = trust - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 1e5c3600d3..f720a4ede0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -630,7 +630,7 @@ internal class DefaultTimeline( if (timelineEvent.isEncrypted() && timelineEvent.root.mxDecryptionResult == null) { - timelineEvent.root.eventId?.also { eventDecryptor.requestDecryption(TimelineEventDecryptor.DecryptionRequest(it, timelineID)) } + timelineEvent.root.eventId?.also { eventDecryptor.requestDecryption(TimelineEventDecryptor.DecryptionRequest(timelineEvent.root, timelineID)) } } val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt index e91487eab0..3517f26c5d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -15,24 +15,22 @@ */ package org.matrix.android.sdk.internal.session.room.timeline +import io.realm.Realm +import io.realm.RealmConfiguration import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.crypto.NewSessionListener import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent -import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.SessionScope -import io.realm.Realm -import io.realm.RealmConfiguration import timber.log.Timber import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import javax.inject.Inject -@SessionScope internal class TimelineEventDecryptor @Inject constructor( @SessionDatabase private val realmConfiguration: RealmConfiguration, @@ -83,14 +81,14 @@ internal class TimelineEventDecryptor @Inject constructor( synchronized(unknownSessionsFailure) { for (requests in unknownSessionsFailure.values) { if (request in requests) { - Timber.d("Skip Decryption request for event ${request.eventId}, unknown session") + Timber.d("Skip Decryption request for event ${request.event.eventId}, unknown session") return } } } synchronized(existingRequests) { if (!existingRequests.add(request)) { - Timber.d("Skip Decryption request for event ${request.eventId}, already requested") + Timber.d("Skip Decryption request for event ${request.event.eventId}, already requested") return } } @@ -101,25 +99,29 @@ internal class TimelineEventDecryptor @Inject constructor( } } - private fun processDecryptRequest(request: DecryptionRequest, realm: Realm) = realm.executeTransaction { - val eventId = request.eventId + private fun processDecryptRequest(request: DecryptionRequest, realm: Realm) { + val event = request.event val timelineId = request.timelineId - Timber.v("Decryption request for event $eventId") - val eventEntity = EventEntity.where(realm, eventId = eventId).findFirst() - ?: return@executeTransaction Unit.also { - Timber.d("Decryption request for unknown message") - } - val event = eventEntity.asDomain() try { - val result = cryptoService.decryptEvent(event, timelineId) - Timber.v("Successfully decrypted event $eventId") - eventEntity.setDecryptionResult(result) + val result = cryptoService.decryptEvent(request.event, timelineId) + Timber.v("Successfully decrypted event ${event.eventId}") + realm.executeTransaction { + EventEntity.where(it, eventId = event.eventId ?: "") + .findFirst() + ?.setDecryptionResult(result) + } } catch (e: MXCryptoError) { - Timber.v(e, "Failed to decrypt event $eventId") + Timber.v("Failed to decrypt event ${event.eventId} : ${e.localizedMessage}") if (e is MXCryptoError.Base /*&& e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID*/) { // Keep track of unknown sessions to automatically try to decrypt on new session - eventEntity.decryptionErrorCode = e.errorType.name - eventEntity.decryptionErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription + realm.executeTransaction { + EventEntity.where(it, eventId = event.eventId ?: "") + .findFirst() + ?.let { + it.decryptionErrorCode = e.errorType.name + it.decryptionErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription + } + } event.content?.toModel()?.let { content -> content.sessionId?.let { sessionId -> synchronized(unknownSessionsFailure) { @@ -130,7 +132,7 @@ internal class TimelineEventDecryptor @Inject constructor( } } } catch (t: Throwable) { - Timber.e("Failed to decrypt event $eventId, ${t.localizedMessage}") + Timber.e("Failed to decrypt event ${event.eventId}, ${t.localizedMessage}") } finally { synchronized(existingRequests) { existingRequests.remove(request) @@ -139,7 +141,7 @@ internal class TimelineEventDecryptor @Inject constructor( } data class DecryptionRequest( - val eventId: String, + val event: Event, val timelineId: String ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index 8589889b30..b1b2f65dc2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -16,6 +16,9 @@ package org.matrix.android.sdk.internal.session.sync +import io.realm.Realm +import io.realm.kotlin.createObject +import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.R import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Event @@ -54,16 +57,12 @@ import org.matrix.android.sdk.internal.session.room.read.FullyReadContent import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection -import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDecryptor import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync import org.matrix.android.sdk.internal.session.sync.model.RoomSync import org.matrix.android.sdk.internal.session.sync.model.RoomSyncAccountData import org.matrix.android.sdk.internal.session.sync.model.RoomSyncEphemeral import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse -import io.realm.Realm -import io.realm.kotlin.createObject -import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject @@ -76,8 +75,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private val roomTypingUsersHandler: RoomTypingUsersHandler, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, @UserId private val userId: String, - private val eventBus: EventBus, - private val timelineEventDecryptor: TimelineEventDecryptor) { + private val eventBus: EventBus) { sealed class HandlingStrategy { data class JOINED(val data: Map) : HandlingStrategy() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt index cfd7865269..74cba5e796 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt @@ -54,7 +54,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, private val networkConnectivityChecker: NetworkConnectivityChecker, private val backgroundDetectionObserver: BackgroundDetectionObserver, private val activeCallHandler: ActiveCallHandler -) : Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { +) : Thread("SyncThread"), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { private var state: SyncState = SyncState.Idle private var liveState = MutableLiveData(state) From aa238775c63cc88dd66358af136019aec9da5402 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 22 Oct 2020 17:19:13 +0200 Subject: [PATCH 02/23] Fix copyrights --- .../android/sdk/internal/crypto/CryptoSessionInfoProvider.kt | 2 +- .../org/matrix/android/sdk/internal/crypto/EventDecryptor.kt | 2 +- .../android/sdk/internal/crypto/InboundGroupSessionStore.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt index 68cc139521..833168bf3e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright 2020 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index b423b5d738..38488f1ca7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright 2020 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt index 722fc0d92d..a57b66cd4e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright 2020 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. From f39938d17d189d31e34b6bf84259c967c1814dbd Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 23 Oct 2020 13:51:29 +0200 Subject: [PATCH 03/23] Fix / device check was not reading up to date trust --- .../sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt index e3ac387ddc..f28fe7d642 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -152,7 +152,11 @@ internal class UpdateTrustWorker(context: Context, ?.devices val trustMap = devicesEntities?.map { device -> - device to crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfos[it], CryptoMapper.mapToModel(device)) + // get up to date from DB has could have been updated + val otherInfo = realm.where(CrossSigningInfoEntity::class.java) + .equalTo(CrossSigningInfoEntityFields.USER_ID, it) + .findFirst()?.let { mapCrossSigningInfoEntity(it) } + device to crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device)) }?.toMap() // Update trust if needed From b9fdc14e182f019c8b35e3578dcc65da9535d7e0 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 23 Oct 2020 13:51:35 +0200 Subject: [PATCH 04/23] add internal --- .../android/sdk/internal/crypto/CryptoSessionInfoProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt index 833168bf3e..225a9af1ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt @@ -30,7 +30,7 @@ import javax.inject.Inject * The crypto module needs some information regarding rooms that are stored * in the session DB, this class encapsulate this functionality */ -class CryptoSessionInfoProvider @Inject constructor( +internal class CryptoSessionInfoProvider @Inject constructor( @SessionDatabase private val monarchy: Monarchy, ) { From 9bf505963109149ddab09ba12b71116969022c45 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 23 Oct 2020 17:44:33 +0200 Subject: [PATCH 05/23] Fix test helper not working --- .../android/sdk/common/CommonTestHelper.kt | 14 ++++-- .../android/sdk/common/CryptoTestHelper.kt | 46 +++---------------- .../crypto/CryptoSessionInfoProvider.kt | 2 +- 3 files changed, 16 insertions(+), 46 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index 1c912b365f..cbe4cca8a3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -88,7 +88,10 @@ class CommonTestHelper(context: Context) { fun syncSession(session: Session) { val lock = CountDownLatch(1) - GlobalScope.launch(Dispatchers.Main) { session.open() } + val job = GlobalScope.launch(Dispatchers.Main) { + session.open() + } + runBlocking { job.join() } session.startSync(true) @@ -341,7 +344,7 @@ class CommonTestHelper(context: Context) { } // Transform a method with a MatrixCallback to a synchronous method - inline fun doSync(block: (MatrixCallback) -> Unit): T { + inline fun doSync(timeout: Long? = TestConstants.timeOutMillis, block: (MatrixCallback) -> Unit): T { val lock = CountDownLatch(1) var result: T? = null @@ -354,7 +357,7 @@ class CommonTestHelper(context: Context) { block.invoke(callback) - await(lock) + await(lock, timeout) assertNotNull(result) return result!! @@ -366,8 +369,9 @@ class CommonTestHelper(context: Context) { fun Iterable.signOutAndClose() = forEach { signOutAndClose(it) } fun signOutAndClose(session: Session) { - doSync { session.signOut(true, it) } - session.close() + doSync(60_000) { session.signOut(true, it) } + // no need signout will close + // session.close() } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index 370b416f54..f1b5fca5e5 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -32,9 +32,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.api.session.room.timeline.Timeline -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData @@ -197,47 +194,16 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! - val lock = CountDownLatch(1) - - val bobEventsListener = object : Timeline.Listener { - override fun onTimelineFailure(throwable: Throwable) { - // noop - } - - override fun onNewTimelineEvents(eventIds: List) { - // noop - } - - override fun onTimelineUpdated(snapshot: List) { - val messages = snapshot.filter { it.root.getClearType() == EventType.MESSAGE } - .groupBy { it.root.senderId!! } - - // Alice has sent 2 messages and Bob has sent 3 messages - if (messages[aliceSession.myUserId]?.size == 2 && messages[bobSession.myUserId]?.size == 3) { - lock.countDown() - } - } - } - - val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20)) - bobTimeline.start() - bobTimeline.addListener(bobEventsListener) - // Alice sends a message - roomFromAlicePOV.sendTextMessage(messagesFromAlice[0]) + mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1) +// roomFromAlicePOV.sendTextMessage(messagesFromAlice[0]) // Bob send 3 messages - roomFromBobPOV.sendTextMessage(messagesFromBob[0]) - roomFromBobPOV.sendTextMessage(messagesFromBob[1]) - roomFromBobPOV.sendTextMessage(messagesFromBob[2]) - + mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1) + mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1) + mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1) // Alice sends a message - roomFromAlicePOV.sendTextMessage(messagesFromAlice[1]) - - mTestHelper.await(lock) - - bobTimeline.removeListener(bobEventsListener) - bobTimeline.dispose() + mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1) return cryptoTestData } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt index 225a9af1ec..ba0dbda786 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt @@ -31,7 +31,7 @@ import javax.inject.Inject * in the session DB, this class encapsulate this functionality */ internal class CryptoSessionInfoProvider @Inject constructor( - @SessionDatabase private val monarchy: Monarchy, + @SessionDatabase private val monarchy: Monarchy ) { fun isRoomEncrypted(roomId: String): Boolean { From 9e921d8b50e5fc003ab19dfb86c15603859e9680 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 23 Oct 2020 17:49:42 +0200 Subject: [PATCH 06/23] safeguard for save after store closed --- .../android/sdk/internal/crypto/InboundGroupSessionStore.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt index a57b66cd4e..06c667ee4a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto import android.util.LruCache import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers @@ -90,7 +91,9 @@ internal class InboundGroupSessionStore @Inject constructor( dirtySession.clear() cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { Timber.v("## Inbound: getInboundGroupSession batching save of ${dirtySession.size}") - store.storeInboundGroupSessions(toSave) + tryOrNull { + store.storeInboundGroupSessions(toSave) + } } } } From e149be9e0fe66a5fb89e8aeddac69b05f025c378 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 26 Oct 2020 11:24:01 +0100 Subject: [PATCH 07/23] Offload Incoming Gossip to dedicated thread --- .../crypto/IncomingGossipingRequestManager.kt | 68 +++++++++++-------- .../internal/crypto/store/IMXCryptoStore.kt | 1 + .../crypto/store/db/RealmCryptoStore.kt | 22 ++++++ 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index 8869e73432..5882205fca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber +import java.util.concurrent.Executors import javax.inject.Inject @SessionScope @@ -52,6 +53,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope) { + private val executor = Executors.newSingleThreadExecutor() // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations // we received in the current sync. private val receivedGossipingRequests = ArrayList() @@ -108,8 +110,8 @@ internal class IncomingGossipingRequestManager @Inject constructor( // ignore, it was sent by me as * Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo") } else { - // save in DB - cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) +// // save in DB +// cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) receivedGossipingRequests.add(it) } } @@ -119,7 +121,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( // ignore, it was sent by me as * Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo") } else { - cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) +// cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) receivedGossipingRequests.add(it) } } @@ -144,13 +146,8 @@ internal class IncomingGossipingRequestManager @Inject constructor( fun processReceivedGossipingRequests() { val roomKeyRequestsToProcess = receivedGossipingRequests.toList() receivedGossipingRequests.clear() - for (request in roomKeyRequestsToProcess) { - if (request is IncomingRoomKeyRequest) { - processIncomingRoomKeyRequest(request) - } else if (request is IncomingSecretShareRequest) { - processIncomingSecretShareRequest(request) - } - } + + Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : ${roomKeyRequestsToProcess.size} request to process") var receivedRequestCancellations: List? = null @@ -161,27 +158,42 @@ internal class IncomingGossipingRequestManager @Inject constructor( } } - receivedRequestCancellations?.forEach { request -> - Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : m.room_key_request cancellation $request") - // we should probably only notify the app of cancellations we told it - // about, but we don't currently have a record of that, so we just pass - // everything through. - if (request.userId == credentials.userId && request.deviceId == credentials.deviceId) { - // ignore remote echo - return@forEach - } - val matchingIncoming = cryptoStore.getIncomingRoomKeyRequest(request.userId ?: "", request.deviceId ?: "", request.requestId ?: "") - if (matchingIncoming == null) { - // ignore that? - return@forEach - } else { - // If it was accepted from this device, keep the information, do not mark as cancelled - if (matchingIncoming.state != GossipingRequestState.ACCEPTED) { - onRoomKeyRequestCancellation(request) - cryptoStore.updateGossipingRequestState(request, GossipingRequestState.CANCELLED_BY_REQUESTER) + + + executor.execute { + cryptoStore.storeIncomingGossipingRequests(roomKeyRequestsToProcess) + for (request in roomKeyRequestsToProcess) { + if (request is IncomingRoomKeyRequest) { + processIncomingRoomKeyRequest(request) + } else if (request is IncomingSecretShareRequest) { + processIncomingSecretShareRequest(request) } } + + receivedRequestCancellations?.forEach { request -> + Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : m.room_key_request cancellation $request") + // we should probably only notify the app of cancellations we told it + // about, but we don't currently have a record of that, so we just pass + // everything through. + if (request.userId == credentials.userId && request.deviceId == credentials.deviceId) { + // ignore remote echo + return@forEach + } + val matchingIncoming = cryptoStore.getIncomingRoomKeyRequest(request.userId ?: "", request.deviceId ?: "", request.requestId ?: "") + if (matchingIncoming == null) { + // ignore that? + return@forEach + } else { + // If it was accepted from this device, keep the information, do not mark as cancelled + if (matchingIncoming.state != GossipingRequestState.ACCEPTED) { + onRoomKeyRequestCancellation(request) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.CANCELLED_BY_REQUESTER) + } + } + } + } + } private fun processIncomingRoomKeyRequest(request: IncomingRoomKeyRequest) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 0ae1e69124..a43faa2cd8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -127,6 +127,7 @@ internal interface IMXCryptoStore { fun getPendingIncomingGossipingRequests(): List fun storeIncomingGossipingRequest(request: IncomingShareRequestCommon, ageLocalTS: Long?) + fun storeIncomingGossipingRequests(request: List) // fun getPendingIncomingSecretShareRequests(): List /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index b25349cba9..c0b538963d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -1284,6 +1284,28 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun storeIncomingGossipingRequests(requests: List) { + doRealmTransactionAsync(realmConfiguration) { realm -> + requests.forEach { request -> + // After a clear cache, we might have a + realm.createObject(IncomingGossipingRequestEntity::class.java).let { + it.otherDeviceId = request.deviceId + it.otherUserId = request.userId + it.requestId = request.requestId ?: "" + it.requestState = GossipingRequestState.PENDING + it.localCreationTimestamp = request.localCreationTimestamp ?: System.currentTimeMillis() + if (request is IncomingSecretShareRequest) { + it.type = GossipRequestType.SECRET + it.requestedInfoStr = request.secretName + } else if (request is IncomingRoomKeyRequest) { + it.type = GossipRequestType.KEY + it.requestedInfoStr = request.requestBody?.toJson() + } + } + } + } + } + // override fun getPendingIncomingSecretShareRequests(): List { // return doRealmQueryAndCopyList(realmConfiguration) { // it.where() From 2144879e7390bc0e63aaadab9c840961d6be8567 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 26 Oct 2020 11:24:37 +0100 Subject: [PATCH 08/23] Offload sending keywithheld to computation --- .../algorithms/megolm/MXMegolmEncryption.kt | 27 ++++++++++++------- .../megolm/MXMegolmEncryptionFactory.kt | 10 +++++-- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 466722788d..74909f6297 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -16,6 +16,9 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.session.crypto.MXCryptoError @@ -39,6 +42,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.JsonCanonicalizer +import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.convertToUTF8 import timber.log.Timber @@ -54,7 +58,9 @@ internal class MXMegolmEncryption( private val sendToDeviceTask: SendToDeviceTask, private val messageEncrypter: MessageEncrypter, private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository, - private val taskExecutor: TaskExecutor + private val taskExecutor: TaskExecutor, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val cryptoCoroutineScope: CoroutineScope ) : IMXEncrypting { // OutboundSessionInfo. Null if we haven't yet started setting one up. Note @@ -84,15 +90,18 @@ internal class MXMegolmEncryption( } private fun notifyWithheldForSession(devices: MXUsersDevicesMap, outboundSession: MXOutboundSessionInfo) { - mutableListOf>().apply { - devices.forEach { userId, deviceId, withheldCode -> - this.add(UserDevice(userId, deviceId) to withheldCode) + // offload to computation thread + cryptoCoroutineScope.launch(coroutineDispatchers.computation) { + mutableListOf>().apply { + devices.forEach { userId, deviceId, withheldCode -> + this.add(UserDevice(userId, deviceId) to withheldCode) + } + }.groupBy( + { it.second }, + { it.first } + ).forEach { (code, targets) -> + notifyKeyWithHeld(targets, outboundSession.sessionId, olmDevice.deviceCurve25519Key, code) } - }.groupBy( - { it.second }, - { it.first } - ).forEach { (code, targets) -> - notifyKeyWithHeld(targets, outboundSession.sessionId, olmDevice.deviceCurve25519Key, code) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt index 8f651692fc..f0cc15fb63 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm +import kotlinx.coroutines.CoroutineScope import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXOlmDevice @@ -26,6 +27,7 @@ import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepo import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.task.TaskExecutor +import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject internal class MXMegolmEncryptionFactory @Inject constructor( @@ -38,7 +40,9 @@ internal class MXMegolmEncryptionFactory @Inject constructor( private val sendToDeviceTask: SendToDeviceTask, private val messageEncrypter: MessageEncrypter, private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository, - private val taskExecutor: TaskExecutor) { + private val taskExecutor: TaskExecutor, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val cryptoCoroutineScope: CoroutineScope) { fun create(roomId: String): MXMegolmEncryption { return MXMegolmEncryption( @@ -52,7 +56,9 @@ internal class MXMegolmEncryptionFactory @Inject constructor( sendToDeviceTask, messageEncrypter, warnOnUnknownDevicesRepository, - taskExecutor + taskExecutor, + coroutineDispatchers, + cryptoCoroutineScope ) } } From dbb14e631305ece5956b66b9d85f53699dfea7dd Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 26 Oct 2020 11:24:53 +0100 Subject: [PATCH 09/23] offload megolm backup import off crypto thread --- .../sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index bbd8ebd000..3abbf9b16e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -690,7 +690,7 @@ internal class DefaultKeysBackupService @Inject constructor( // Get backed up keys from the homeserver val data = getKeys(sessionId, roomId, keysVersionResult.version!!) - withContext(coroutineDispatchers.crypto) { + withContext(coroutineDispatchers.computation) { val sessionsData = ArrayList() // Restore that data var sessionsFromHsCount = 0 From 6d2b3a6940465d07c61b9c86813d92ed928ecada Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 26 Oct 2020 11:25:31 +0100 Subject: [PATCH 10/23] cleaning --- .../sdk/internal/crypto/IncomingGossipingRequestManager.kt | 4 ---- .../internal/crypto/algorithms/megolm/MXMegolmEncryption.kt | 1 - 2 files changed, 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index 5882205fca..6f135ceb49 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -158,8 +158,6 @@ internal class IncomingGossipingRequestManager @Inject constructor( } } - - executor.execute { cryptoStore.storeIncomingGossipingRequests(roomKeyRequestsToProcess) for (request in roomKeyRequestsToProcess) { @@ -191,9 +189,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( } } } - } - } private fun processIncomingRoomKeyRequest(request: IncomingRoomKeyRequest) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 74909f6297..a7c438bb31 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.data.Credentials From bb000f77f867b534e3b43038de18af4cff4407d9 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 26 Oct 2020 14:54:44 +0100 Subject: [PATCH 11/23] Offload self verif todevice off crypto thread --- .../crypto/verification/DefaultVerificationService.kt | 9 +++++++-- .../verification/IncomingVerificationRequestHandler.kt | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index f3a794154c..7f02750359 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -125,7 +125,8 @@ internal class DefaultVerificationService @Inject constructor( // Event received from the sync fun onToDeviceEvent(event: Event) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + Timber.d("## SAS onToDeviceEvent ${event.getClearType()}") + cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) { when (event.getClearType()) { EventType.KEY_VERIFICATION_START -> { onStartRequestReceived(event) @@ -159,7 +160,7 @@ internal class DefaultVerificationService @Inject constructor( } fun onRoomEvent(event: Event) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) { when (event.getClearType()) { EventType.KEY_VERIFICATION_START -> { onRoomStartRequestReceived(event) @@ -236,6 +237,7 @@ internal class DefaultVerificationService @Inject constructor( } private fun dispatchRequestAdded(tx: PendingVerificationRequest) { + Timber.v("## SAS dispatchRequestAdded txId:${tx.transactionId}") uiHandler.post { listeners.forEach { try { @@ -299,11 +301,14 @@ internal class DefaultVerificationService @Inject constructor( // We don't want to block here val otherDeviceId = validRequestInfo.fromDevice + Timber.v("## SAS onRequestReceived from $senderId and device $otherDeviceId, txId:${validRequestInfo.transactionId}") + cryptoCoroutineScope.launch { if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) { Timber.e("## Verification device $otherDeviceId is not known") } } + Timber.v("## SAS onRequestReceived .. checkKeysAreDownloaded launched") // Remember this request val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt index 0a1b631344..e42eb6de6f 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import org.matrix.android.sdk.api.util.toMatrixItem +import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -116,6 +117,7 @@ class IncomingVerificationRequestHandler @Inject constructor( } override fun verificationRequestCreated(pr: PendingVerificationRequest) { + Timber.v("## SAS verificationRequestCreated ${pr.transactionId}") // For incoming request we should prompt (if not in activity where this request apply) if (pr.isIncoming) { val name = session?.getUser(pr.otherUserId)?.displayName From 5a111af2fe50682fc018ac62345a1789f8d36d5c Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 27 Oct 2020 00:03:49 +0100 Subject: [PATCH 12/23] Fix / add close to IncomingRequestManager --- .../sdk/internal/crypto/DefaultCryptoService.kt | 2 +- .../crypto/IncomingGossipingRequestManager.kt | 4 ++++ .../java/im/vector/app/features/MainActivity.kt | 17 ++++++++++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 0a18aba8ba..f4ec7acd88 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -386,7 +386,7 @@ internal class DefaultCryptoService @Inject constructor( */ fun close() = runBlocking(coroutineDispatchers.crypto) { cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module")) - + incomingGossipingRequestManager.close() olmDevice.release() cryptoStore.close() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index 6f135ceb49..ae017b33f5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -66,6 +66,10 @@ internal class IncomingGossipingRequestManager @Inject constructor( receivedGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests()) } + fun close() { + executor.shutdownNow() + } + // Recently verified devices (map of deviceId and timestamp) private val recentlyVerifiedDevices = HashMap() diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index b5552e4d62..e553b5e0d3 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Bundle import android.os.Parcelable import androidx.appcompat.app.AlertDialog +import androidx.lifecycle.Lifecycle import com.bumptech.glide.Glide import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder @@ -205,13 +206,15 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity { } private fun displayError(failure: Throwable) { - AlertDialog.Builder(this) - .setTitle(R.string.dialog_title_error) - .setMessage(errorFormatter.toHumanReadable(failure)) - .setPositiveButton(R.string.global_retry) { _, _ -> doCleanUp() } - .setNegativeButton(R.string.cancel) { _, _ -> startNextActivityAndFinish() } - .setCancelable(false) - .show() + if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { + AlertDialog.Builder(this) + .setTitle(R.string.dialog_title_error) + .setMessage(errorFormatter.toHumanReadable(failure)) + .setPositiveButton(R.string.global_retry) { _, _ -> doCleanUp() } + .setNegativeButton(R.string.cancel) { _, _ -> startNextActivityAndFinish() } + .setCancelable(false) + .show() + } } private fun startNextActivityAndFinish() { From c2027be0ee50e32f0e67a19653722bdbbb9ccead Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 28 Oct 2020 17:40:30 +0100 Subject: [PATCH 13/23] Fix audit freeze, add export, and buffer gossip saves --- .../sdk/api/session/crypto/CryptoService.kt | 7 +- .../internal/crypto/DefaultCryptoService.kt | 30 ++- .../algorithms/megolm/MXMegolmEncryption.kt | 10 + .../internal/crypto/store/IMXCryptoStore.kt | 7 +- .../crypto/store/db/RealmCryptoStore.kt | 116 +++++++-- .../GossipingEventsEpoxyController.kt | 235 ------------------ .../GossipingEventsPaperTrailFragment.kt | 9 +- .../GossipingEventsPaperTrailViewModel.kt | 17 +- .../GossipingTrailPagedEpoxyController.kt | 168 +++++++++++++ .../IncomingKeyRequestListFragment.kt | 10 +- .../IncomingKeyRequestPagedController.kt | 64 +++++ .../devtools/KeyRequestEpoxyController.kt | 164 ------------ .../devtools/KeyRequestListViewModel.kt | 31 ++- .../settings/devtools/KeyRequestViewModel.kt | 154 ++++++++++++ .../settings/devtools/KeyRequestsFragment.kt | 62 ++++- .../OutgoingKeyRequestListFragment.kt | 6 +- .../OutgoingKeyRequestPagedController.kt | 63 +++++ .../layout/fragment_devtool_keyrequests.xml | 20 +- vector/src/main/res/menu/menu_audit.xml | 10 + vector/src/main/res/values/strings.xml | 1 + 20 files changed, 719 insertions(+), 465 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsEpoxyController.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt delete mode 100644 vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestEpoxyController.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt create mode 100644 vector/src/main/res/menu/menu_audit.xml diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 121d9fb401..34be1b8d05 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.crypto import android.content.Context import androidx.lifecycle.LiveData +import androidx.paging.PagedList import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService @@ -40,6 +41,7 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody +import kotlin.jvm.Throws interface CryptoService { @@ -142,10 +144,13 @@ interface CryptoService { fun removeSessionListener(listener: NewSessionListener) fun getOutgoingRoomKeyRequests(): List + fun getOutgoingRoomKeyRequestsPaged(): LiveData> fun getIncomingRoomKeyRequests(): List + fun getIncomingRoomKeyRequestsPaged(): LiveData> - fun getGossipingEventsTrail(): List + fun getGossipingEventsTrail(): LiveData> + fun getGossipingEvents(): List // For testing shared session fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index f4ec7acd88..07545c50bc 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto import android.content.Context import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData +import androidx.paging.PagedList import com.squareup.moshi.Types import dagger.Lazy import kotlinx.coroutines.CancellationException @@ -185,6 +186,8 @@ internal class DefaultCryptoService @Inject constructor( } } + val gossipingBuffer = mutableListOf() + override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback) { setDeviceNameTask .configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) { @@ -428,6 +431,13 @@ internal class DefaultCryptoService @Inject constructor( incomingGossipingRequestManager.processReceivedGossipingRequests() } } + + tryOrNull { + gossipingBuffer.toList().let { + cryptoStore.saveGossipingEvents(it) + } + gossipingBuffer.clear() + } } } @@ -721,19 +731,19 @@ internal class DefaultCryptoService @Inject constructor( cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { when (event.getClearType()) { EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> { - cryptoStore.saveGossipingEvent(event) + gossipingBuffer.add(event) // Keys are imported directly, not waiting for end of sync onRoomKeyEvent(event) } EventType.REQUEST_SECRET, EventType.ROOM_KEY_REQUEST -> { // save audit trail - cryptoStore.saveGossipingEvent(event) + gossipingBuffer.add(event) // Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete) incomingGossipingRequestManager.onGossipingRequestEvent(event) } EventType.SEND_SECRET -> { - cryptoStore.saveGossipingEvent(event) + gossipingBuffer.add(event) onSecretSendReceived(event) } EventType.ROOM_KEY_WITHHELD -> { @@ -1254,14 +1264,26 @@ internal class DefaultCryptoService @Inject constructor( return cryptoStore.getOutgoingRoomKeyRequests() } + override fun getOutgoingRoomKeyRequestsPaged(): LiveData> { + return cryptoStore.getOutgoingRoomKeyRequestsPaged() + } + + override fun getIncomingRoomKeyRequestsPaged(): LiveData> { + return cryptoStore.getIncomingRoomKeyRequestsPaged() + } + override fun getIncomingRoomKeyRequests(): List { return cryptoStore.getIncomingRoomKeyRequests() } - override fun getGossipingEventsTrail(): List { + override fun getGossipingEventsTrail(): LiveData> { return cryptoStore.getGossipingEventsTrail() } + override fun getGossipingEvents(): List { + return cryptoStore.getGossipingEvents() + } + override fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap { return cryptoStore.getSharedWithInfo(roomId, sessionId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index a7c438bb31..e55cf37118 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM @@ -255,6 +256,15 @@ internal class MXMegolmEncryption( for ((userId, devicesToShareWith) in devicesByUser) { for ((deviceId) in devicesToShareWith) { session.sharedWithHelper.markedSessionAsShared(userId, deviceId, chainIndex) + cryptoStore.saveGossipingEvent(Event( + type = EventType.ROOM_KEY, + senderId = credentials.userId, + content = submap.apply { + this["session_key"] = "" + // we add a fake key for trail + this["_dest"] = "$userId|$deviceId" + } + )) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index a43faa2cd8..74773384ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.store import androidx.lifecycle.LiveData +import androidx.paging.PagedList import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.util.Optional @@ -365,6 +366,7 @@ internal interface IMXCryptoStore { fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map>): OutgoingSecretRequest? fun saveGossipingEvent(event: Event) + fun saveGossipingEvents(events: List) fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) { updateGossipingRequestState( @@ -442,10 +444,13 @@ internal interface IMXCryptoStore { // Dev tools fun getOutgoingRoomKeyRequests(): List + fun getOutgoingRoomKeyRequestsPaged(): LiveData> fun getOutgoingSecretKeyRequests(): List fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest? fun getIncomingRoomKeyRequests(): List - fun getGossipingEventsTrail(): List + fun getIncomingRoomKeyRequestsPaged(): LiveData> + fun getGossipingEventsTrail(): LiveData> + fun getGossipingEvents(): List fun setDeviceKeysUploaded(uploaded: Boolean) fun getDeviceKeysUploaded(): Boolean diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index c0b538963d..7644ab5cc2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.crypto.store.db import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations +import androidx.paging.LivePagedListBuilder +import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmConfiguration @@ -62,6 +64,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFie import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntity +import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntity import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity @@ -998,7 +1001,50 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun getGossipingEventsTrail(): List { + override fun getIncomingRoomKeyRequestsPaged(): LiveData> { + val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> + realm.where() + .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) + .sort(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, Sort.DESCENDING) + } + val dataSourceFactory = realmDataSourceFactory.map { + it.toIncomingGossipingRequest() as? IncomingRoomKeyRequest + ?: IncomingRoomKeyRequest( + requestBody = null, + deviceId = "", + userId = "", + requestId = "", + state = GossipingRequestState.NONE, + localCreationTimestamp = 0 + ) + } + return monarchy.findAllPagedWithChanges(realmDataSourceFactory, + LivePagedListBuilder(dataSourceFactory, + PagedList.Config.Builder() + .setPageSize(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(1) + .build()) + ) + } + + override fun getGossipingEventsTrail(): LiveData> { + val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> + realm.where().sort(GossipingEventEntityFields.AGE_LOCAL_TS, Sort.DESCENDING) + } + val dataSourceFactory = realmDataSourceFactory.map { it.toModel() } + val trail = monarchy.findAllPagedWithChanges(realmDataSourceFactory, + LivePagedListBuilder(dataSourceFactory, + PagedList.Config.Builder() + .setPageSize(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(1) + .build()) + ) + return trail + } + + override fun getGossipingEvents(): List { return monarchy.fetchAllCopiedSync { realm -> realm.where() }.map { @@ -1066,24 +1112,43 @@ internal class RealmCryptoStore @Inject constructor( return request } - override fun saveGossipingEvent(event: Event) { + override fun saveGossipingEvents(events: List) { val now = System.currentTimeMillis() - val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now - val entity = GossipingEventEntity( - type = event.type, - sender = event.senderId, - ageLocalTs = ageLocalTs, - content = ContentMapper.map(event.content) - ).apply { - sendState = SendState.SYNCED - decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult) - decryptionErrorCode = event.mCryptoError?.name - } - doRealmTransaction(realmConfiguration) { realm -> - realm.insertOrUpdate(entity) + monarchy.writeAsync { realm -> + events.forEach { event -> + val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now + val entity = GossipingEventEntity( + type = event.type, + sender = event.senderId, + ageLocalTs = ageLocalTs, + content = ContentMapper.map(event.content) + ).apply { + sendState = SendState.SYNCED + decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult) + decryptionErrorCode = event.mCryptoError?.name + } + realm.insertOrUpdate(entity) + } } } + override fun saveGossipingEvent(event: Event) { + monarchy.writeAsync { realm -> + val now = System.currentTimeMillis() + val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now + val entity = GossipingEventEntity( + type = event.type, + sender = event.senderId, + ageLocalTs = ageLocalTs, + content = ContentMapper.map(event.content) + ).apply { + sendState = SendState.SYNCED + decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult) + decryptionErrorCode = event.mCryptoError?.name + } + realm.insertOrUpdate(entity) + } + } // override fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? { // val statesIndex = states.map { it.ordinal }.toTypedArray() // return doRealmQueryAndCopy(realmConfiguration) { realm -> @@ -1439,6 +1504,27 @@ internal class RealmCryptoStore @Inject constructor( .filterNotNull() } + override fun getOutgoingRoomKeyRequestsPaged(): LiveData> { + val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> + realm + .where(OutgoingGossipingRequestEntity::class.java) + .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) + } + val dataSourceFactory = realmDataSourceFactory.map { + it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest + ?: OutgoingRoomKeyRequest(requestBody = null, requestId = "?", recipients = emptyMap(), state = OutgoingGossipingRequestState.CANCELLED) + } + val trail = monarchy.findAllPagedWithChanges(realmDataSourceFactory, + LivePagedListBuilder(dataSourceFactory, + PagedList.Config.Builder() + .setPageSize(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(1) + .build()) + ) + return trail + } + override fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? { return doWithRealm(realmConfiguration) { realm -> val crossSigningInfo = realm.where(CrossSigningInfoEntity::class.java) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsEpoxyController.kt deleted file mode 100644 index cf93bc14e7..0000000000 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsEpoxyController.kt +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.settings.devtools - -import com.airbnb.epoxy.TypedEpoxyController -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized -import im.vector.app.R -import im.vector.app.core.date.DateFormatKind -import im.vector.app.core.date.VectorDateFormatter -import im.vector.app.core.epoxy.loadingItem -import im.vector.app.core.extensions.exhaustive -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.resources.StringProvider -import im.vector.app.core.ui.list.GenericItem -import im.vector.app.core.ui.list.genericFooterItem -import im.vector.app.core.ui.list.genericItem -import im.vector.app.core.ui.list.genericItemHeader -import me.gujun.android.span.span -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.toModel -import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent -import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent -import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent -import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest -import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest -import javax.inject.Inject - -class GossipingEventsEpoxyController @Inject constructor( - private val stringProvider: StringProvider, - private val vectorDateFormatter: VectorDateFormatter, - private val colorProvider: ColorProvider -) : TypedEpoxyController() { - - interface InteractionListener { - fun didTap(event: Event) - } - - var interactionListener: InteractionListener? = null - - override fun buildModels(data: GossipingEventsPaperTrailState?) { - when (val async = data?.events) { - is Uninitialized, - is Loading -> { - loadingItem { - id("loadingOutgoing") - loadingText(stringProvider.getString(R.string.loading)) - } - } - is Fail -> { - genericItem { - id("failOutgoing") - title(async.error.localizedMessage) - } - } - is Success -> { - val eventList = async.invoke() - if (eventList.isEmpty()) { - genericFooterItem { - id("empty") - text(stringProvider.getString(R.string.no_result_placeholder)) - } - return - } - - eventList.forEachIndexed { _, event -> - genericItem { - id(event.hashCode()) - itemClickAction(GenericItem.Action("view").apply { perform = Runnable { interactionListener?.didTap(event) } }) - title( - if (event.isEncrypted()) { - "${event.getClearType()} [encrypted]" - } else { - event.type - } - ) - description( - span { - +vectorDateFormatter.format(event.ageLocalTs, DateFormatKind.DEFAULT_DATE_AND_TIME) - span("\nfrom: ") { - textStyle = "bold" - } - +"${event.senderId}" - apply { - if (event.getClearType() == EventType.ROOM_KEY_REQUEST) { - val content = event.getClearContent().toModel() - span("\nreqId:") { - textStyle = "bold" - } - +" ${content?.requestId}" - span("\naction:") { - textStyle = "bold" - } - +" ${content?.action}" - if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { - span("\nsessionId:") { - textStyle = "bold" - } - +" ${content.body?.sessionId}" - } - span("\nrequestedBy: ") { - textStyle = "bold" - } - +"${content?.requestingDeviceId}" - } else if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { - val encryptedContent = event.content.toModel() - val content = event.getClearContent().toModel() - if (event.mxDecryptionResult == null) { - span("**Failed to Decrypt** ${event.mCryptoError}") { - textColor = colorProvider.getColor(R.color.vector_error_color) - } - } - span("\nsessionId:") { - textStyle = "bold" - } - +" ${content?.sessionId}" - span("\nFrom Device (sender key):") { - textStyle = "bold" - } - +" ${encryptedContent?.senderKey}" - } else if (event.getClearType() == EventType.SEND_SECRET) { - val content = event.getClearContent().toModel() - - span("\nrequestId:") { - textStyle = "bold" - } - +" ${content?.requestId}" - span("\nFrom Device:") { - textStyle = "bold" - } - +" ${event.mxDecryptionResult?.payload?.get("sender_device")}" - } else if (event.getClearType() == EventType.REQUEST_SECRET) { - val content = event.getClearContent().toModel() - span("\nreqId:") { - textStyle = "bold" - } - +" ${content?.requestId}" - span("\naction:") { - textStyle = "bold" - } - +" ${content?.action}" - if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { - span("\nsecretName:") { - textStyle = "bold" - } - +" ${content.secretName}" - } - span("\nrequestedBy: ") { - textStyle = "bold" - } - +"${content?.requestingDeviceId}" - } - } - } - ) - } - } - } - } - } - - private fun buildOutgoing(data: KeyRequestListViewState?) { - data?.outgoingRoomKeyRequests?.let { async -> - when (async) { - is Uninitialized, - is Loading -> { - loadingItem { - id("loadingOutgoing") - loadingText(stringProvider.getString(R.string.loading)) - } - } - is Fail -> { - genericItem { - id("failOutgoing") - title(async.error.localizedMessage) - } - } - is Success -> { - if (async.invoke().isEmpty()) { - genericFooterItem { - id("empty") - text(stringProvider.getString(R.string.no_result_placeholder)) - } - return - } - - val requestList = async.invoke().groupBy { it.roomId } - - requestList.forEach { - genericItemHeader { - id(it.key) - text("roomId: ${it.key}") - } - it.value.forEach { roomKeyRequest -> - genericItem { - id(roomKeyRequest.requestId) - title(roomKeyRequest.requestId) - description( - span { - span("sessionId:\n") { - textStyle = "bold" - } - +"${roomKeyRequest.sessionId}" - span("\nstate:") { - textStyle = "bold" - } - +"\n${roomKeyRequest.state.name}" - } - ) - } - } - } - } - }.exhaustive - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt index e2c855a9e3..0ceb8e148d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt @@ -33,16 +33,19 @@ import javax.inject.Inject class GossipingEventsPaperTrailFragment @Inject constructor( val viewModelFactory: GossipingEventsPaperTrailViewModel.Factory, - private val epoxyController: GossipingEventsEpoxyController, + private val epoxyController: GossipingTrailPagedEpoxyController, private val colorProvider: ColorProvider -) : VectorBaseFragment(), GossipingEventsEpoxyController.InteractionListener { +) : VectorBaseFragment(), GossipingTrailPagedEpoxyController.InteractionListener { override fun getLayoutResId() = R.layout.fragment_generic_recycler private val viewModel: GossipingEventsPaperTrailViewModel by fragmentViewModel(GossipingEventsPaperTrailViewModel::class) override fun invalidate() = withState(viewModel) { state -> - epoxyController.setData(state) + state.events.invoke()?.let { + epoxyController.submitList(it) + } + Unit } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt index d903725b22..4249ef09fa 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt @@ -16,13 +16,12 @@ package im.vector.app.features.settings.devtools -import androidx.lifecycle.viewModelScope +import androidx.paging.PagedList import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted @@ -30,12 +29,12 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.rx.asObservable data class GossipingEventsPaperTrailState( - val events: Async> = Uninitialized + val events: Async> = Uninitialized ) : MvRxState class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted initialState: GossipingEventsPaperTrailState, @@ -50,14 +49,10 @@ class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted i setState { copy(events = Loading()) } - viewModelScope.launch { - session.cryptoService().getGossipingEventsTrail().let { - val sorted = it.sortedByDescending { it.ageLocalTs } - setState { - copy(events = Success(sorted)) + session.cryptoService().getGossipingEventsTrail().asObservable() + .execute { + copy(events = it) } - } - } } override fun handle(action: EmptyAction) {} diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt new file mode 100644 index 0000000000..603c67d074 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devtools + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.paging.PagedListEpoxyController +import im.vector.app.R +import im.vector.app.core.date.DateFormatKind +import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.GenericItem +import im.vector.app.core.ui.list.GenericItem_ +import im.vector.app.core.utils.createUIHandler +import me.gujun.android.span.span +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.toModel +import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent +import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent +import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent +import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject +import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest +import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest +import javax.inject.Inject + +class GossipingTrailPagedEpoxyController @Inject constructor( + private val stringProvider: StringProvider, + private val vectorDateFormatter: VectorDateFormatter, + private val colorProvider: ColorProvider +) : PagedListEpoxyController( + // Important it must match the PageList builder notify Looper + modelBuildingHandler = createUIHandler() +) { + + interface InteractionListener { + fun didTap(event: Event) + } + + var interactionListener: InteractionListener? = null + + override fun buildItemModel(currentPosition: Int, item: Event?): EpoxyModel<*> { + val event = item ?: return GenericItem_().apply { id(currentPosition) } + return GenericItem_().apply { + id(event.hashCode()) + itemClickAction(GenericItem.Action("view").apply { perform = Runnable { interactionListener?.didTap(event) } }) + title( + if (event.isEncrypted()) { + "${event.getClearType()} [encrypted]" + } else { + event.type + } + ) + description( + span { + +vectorDateFormatter.format(event.ageLocalTs, DateFormatKind.DEFAULT_DATE_AND_TIME) + span("\nfrom: ") { + textStyle = "bold" + } + +"${event.senderId}" + apply { + if (event.getClearType() == EventType.ROOM_KEY_REQUEST) { + val content = event.getClearContent().toModel() + span("\nreqId:") { + textStyle = "bold" + } + +" ${content?.requestId}" + span("\naction:") { + textStyle = "bold" + } + +" ${content?.action}" + if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { + span("\nsessionId:") { + textStyle = "bold" + } + +" ${content.body?.sessionId}" + } + span("\nrequestedBy: ") { + textStyle = "bold" + } + +"${content?.requestingDeviceId}" + } else if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { + val encryptedContent = event.content.toModel() + val content = event.getClearContent().toModel() + if (event.mxDecryptionResult == null) { + span("**Failed to Decrypt** ${event.mCryptoError}") { + textColor = colorProvider.getColor(R.color.vector_error_color) + } + } + span("\nsessionId:") { + textStyle = "bold" + } + +" ${content?.sessionId}" + span("\nFrom Device (sender key):") { + textStyle = "bold" + } + +" ${encryptedContent?.senderKey}" + } else if (event.getClearType() == EventType.ROOM_KEY) { + // it's a bit of a fake event for trail reasons + val content = event.getClearContent() + span("\nsessionId:") { + textStyle = "bold" + } + +" ${content?.get("session_id")}" + span("\nroomId:") { + textStyle = "bold" + } + +" ${content?.get("room_id")}" + span("\nTo :") { + textStyle = "bold" + } + +" ${content?.get("_dest") ?: "me"}" + } else if (event.getClearType() == EventType.SEND_SECRET) { + val content = event.getClearContent().toModel() + + span("\nrequestId:") { + textStyle = "bold" + } + +" ${content?.requestId}" + span("\nFrom Device:") { + textStyle = "bold" + } + +" ${event.mxDecryptionResult?.payload?.get("sender_device")}" + } else if (event.getClearType() == EventType.REQUEST_SECRET) { + val content = event.getClearContent().toModel() + span("\nreqId:") { + textStyle = "bold" + } + +" ${content?.requestId}" + span("\naction:") { + textStyle = "bold" + } + +" ${content?.action}" + if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { + span("\nsecretName:") { + textStyle = "bold" + } + +" ${content.secretName}" + } + span("\nrequestedBy: ") { + textStyle = "bold" + } + +"${content?.requestingDeviceId}" + } else if (event.getClearType() == EventType.ENCRYPTED) { + span("**Failed to Decrypt** ${event.mCryptoError}") { + textColor = colorProvider.getColor(R.color.vector_error_color) + } + } + } + } + ) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt index c7b95ddf78..35f46d9c74 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt @@ -24,14 +24,12 @@ import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.resources.ColorProvider import kotlinx.android.synthetic.main.fragment_generic_recycler.* import javax.inject.Inject class IncomingKeyRequestListFragment @Inject constructor( val viewModelFactory: KeyRequestListViewModel.Factory, - private val epoxyController: KeyRequestEpoxyController, - private val colorProvider: ColorProvider + private val epoxyController: IncomingKeyRequestPagedController ) : VectorBaseFragment() { override fun getLayoutResId() = R.layout.fragment_generic_recycler @@ -39,8 +37,10 @@ class IncomingKeyRequestListFragment @Inject constructor( private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class) override fun invalidate() = withState(viewModel) { state -> - epoxyController.outgoing = false - epoxyController.setData(state) + state.incomingRequests.invoke()?.let { + epoxyController.submitList(it) + } + Unit } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt new file mode 100644 index 0000000000..1510d47180 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devtools + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.paging.PagedListEpoxyController +import im.vector.app.core.date.DateFormatKind +import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.ui.list.GenericItem_ +import im.vector.app.core.utils.createUIHandler +import me.gujun.android.span.span +import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest +import javax.inject.Inject + +class IncomingKeyRequestPagedController @Inject constructor( + private val vectorDateFormatter: VectorDateFormatter +) : PagedListEpoxyController( + // Important it must match the PageList builder notify Looper + modelBuildingHandler = createUIHandler() +) { + + interface InteractionListener { + // fun didTap(data: UserAccountData) + } + + var interactionListener: InteractionListener? = null + + override fun buildItemModel(currentPosition: Int, item: IncomingRoomKeyRequest?): EpoxyModel<*> { + val roomKeyRequest = item ?: return GenericItem_().apply { id(currentPosition) } + + return GenericItem_().apply { + id(roomKeyRequest.requestId) + title(roomKeyRequest.requestId) + description( + span { + span("From user: ${roomKeyRequest.userId}") + +vectorDateFormatter.format(roomKeyRequest.localCreationTimestamp, DateFormatKind.DEFAULT_DATE_AND_TIME) + span("sessionId:") { + textStyle = "bold" + } + span("\nFrom device:") { + textStyle = "bold" + } + +"${roomKeyRequest.deviceId}" + +"\n${roomKeyRequest.state.name}" + } + ) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestEpoxyController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestEpoxyController.kt deleted file mode 100644 index 5907b55b31..0000000000 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestEpoxyController.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.settings.devtools - -import com.airbnb.epoxy.TypedEpoxyController -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized -import im.vector.app.R -import im.vector.app.core.epoxy.loadingItem -import im.vector.app.core.extensions.exhaustive -import im.vector.app.core.resources.StringProvider -import im.vector.app.core.ui.list.genericFooterItem -import im.vector.app.core.ui.list.genericItem -import im.vector.app.core.ui.list.genericItemHeader -import me.gujun.android.span.span -import javax.inject.Inject - -class KeyRequestEpoxyController @Inject constructor( - private val stringProvider: StringProvider -) : TypedEpoxyController() { - - interface InteractionListener { - // fun didTap(data: UserAccountData) - } - - var outgoing = true - - var interactionListener: InteractionListener? = null - - override fun buildModels(data: KeyRequestListViewState?) { - if (outgoing) { - buildOutgoing(data) - } else { - buildIncoming(data) - } - } - - private fun buildIncoming(data: KeyRequestListViewState?) { - data?.incomingRequests?.let { async -> - when (async) { - is Uninitialized, - is Loading -> { - loadingItem { - id("loadingOutgoing") - loadingText(stringProvider.getString(R.string.loading)) - } - } - is Fail -> { - genericItem { - id("failOutgoing") - title(async.error.localizedMessage) - } - } - is Success -> { - if (async.invoke().isEmpty()) { - genericFooterItem { - id("empty") - text(stringProvider.getString(R.string.no_result_placeholder)) - } - return - } - val requestList = async.invoke().groupBy { it.userId } - - requestList.forEach { - genericItemHeader { - id(it.key) - text("From user: ${it.key}") - } - it.value.forEach { roomKeyRequest -> - genericItem { - id(roomKeyRequest.requestId) - title(roomKeyRequest.requestId) - description( - span { - span("sessionId:") { - textStyle = "bold" - } - span("\nFrom device:") { - textStyle = "bold" - } - +"${roomKeyRequest.deviceId}" - +"\n${roomKeyRequest.state.name}" - } - ) - } - } - } - } - }.exhaustive - } - } - - private fun buildOutgoing(data: KeyRequestListViewState?) { - data?.outgoingRoomKeyRequests?.let { async -> - when (async) { - is Uninitialized, - is Loading -> { - loadingItem { - id("loadingOutgoing") - loadingText(stringProvider.getString(R.string.loading)) - } - } - is Fail -> { - genericItem { - id("failOutgoing") - title(async.error.localizedMessage) - } - } - is Success -> { - if (async.invoke().isEmpty()) { - genericFooterItem { - id("empty") - text(stringProvider.getString(R.string.no_result_placeholder)) - } - return - } - - val requestList = async.invoke().groupBy { it.roomId } - - requestList.forEach { - genericItemHeader { - id(it.key) - text("roomId: ${it.key}") - } - it.value.forEach { roomKeyRequest -> - genericItem { - id(roomKeyRequest.requestId) - title(roomKeyRequest.requestId) - description( - span { - span("sessionId:\n") { - textStyle = "bold" - } - +"${roomKeyRequest.sessionId}" - span("\nstate:") { - textStyle = "bold" - } - +"\n${roomKeyRequest.state.name}" - } - ) - } - } - } - } - }.exhaustive - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt index 22093763e1..0b0b923a48 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt @@ -17,11 +17,11 @@ package im.vector.app.features.settings.devtools import androidx.lifecycle.viewModelScope +import androidx.paging.PagedList import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted @@ -33,10 +33,11 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest +import org.matrix.android.sdk.rx.asObservable data class KeyRequestListViewState( - val incomingRequests: Async> = Uninitialized, - val outgoingRoomKeyRequests: Async> = Uninitialized + val incomingRequests: Async> = Uninitialized, + val outgoingRoomKeyRequests: Async> = Uninitialized ) : MvRxState class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState: KeyRequestListViewState, @@ -49,20 +50,16 @@ class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState fun refresh() { viewModelScope.launch { - session.cryptoService().getOutgoingRoomKeyRequests().let { - setState { - copy( - outgoingRoomKeyRequests = Success(it) - ) - } - } - session.cryptoService().getIncomingRoomKeyRequests().let { - setState { - copy( - incomingRequests = Success(it) - ) - } - } + session.cryptoService().getOutgoingRoomKeyRequestsPaged().asObservable() + .execute { + copy(outgoingRoomKeyRequests = it) + } + + session.cryptoService().getIncomingRoomKeyRequestsPaged() + .asObservable() + .execute { + copy(incomingRequests = it) + } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt new file mode 100644 index 0000000000..be8778e1db --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devtools + +import android.net.Uri +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.core.platform.VectorViewEvents +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.platform.VectorViewModelAction +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import me.gujun.android.span.span +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent +import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent +import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent +import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject +import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest +import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest + +sealed class KeyRequestAction : VectorViewModelAction { + data class ExportAudit(val uri: Uri) : KeyRequestAction() +} + +sealed class KeyRequestEvents : VectorViewEvents { + data class SaveAudit(val uri: Uri, val raw: String) : KeyRequestEvents() +} + +data class KeyRequestViewState( + val exporting: Async = Uninitialized +) : MvRxState + +class KeyRequestViewModel @AssistedInject constructor( + @Assisted initialState: KeyRequestViewState, + private val session: Session) + : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: KeyRequestViewState): KeyRequestViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: KeyRequestViewState): KeyRequestViewModel? { + val fragment: KeyRequestsFragment = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.viewModelFactory.create(state) + } + } + + override fun handle(action: KeyRequestAction) { + when (action) { + is KeyRequestAction.ExportAudit -> { + setState { + copy(exporting = Loading()) + } + viewModelScope.launch(Dispatchers.IO) { + try { + // this can take long + val eventList = session.cryptoService().getGossipingEvents() + // clean it a bit to + val stringBuilder = StringBuilder() + eventList.forEach { + val clearType = it.getClearType() + stringBuilder.append("[${it.ageLocalTs}] : $clearType from:${it.senderId} - ") + when (clearType) { + EventType.ROOM_KEY_REQUEST -> { + val content = it.getClearContent().toModel() + stringBuilder.append("reqId:${content?.requestId} action:${content?.action} ") + if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { + stringBuilder.append("sessionId: ${content.body?.sessionId} ") + } + stringBuilder.append("requestedBy: ${content?.requestingDeviceId} ") + stringBuilder.append("\n") + } + EventType.FORWARDED_ROOM_KEY -> { + val encryptedContent = it.content.toModel() + val content = it.getClearContent().toModel() + + stringBuilder.append("sessionId:${content?.sessionId} From Device (sender key):${encryptedContent?.senderKey} ") + span("\nFrom Device (sender key):") { + textStyle = "bold" + } + stringBuilder.append("\n") + } + EventType.ROOM_KEY -> { + val content = it.getClearContent() + stringBuilder.append("sessionId:${content?.get("session_id")} roomId:${content?.get("room_id")} dest:${content?.get("_dest") ?: "me"}") + stringBuilder.append("\n") + } + EventType.SEND_SECRET -> { + val content = it.getClearContent().toModel() + stringBuilder.append("requestId:${content?.requestId} From Device:${it.mxDecryptionResult?.payload?.get("sender_device")}") + } + EventType.REQUEST_SECRET -> { + val content = it.getClearContent().toModel() + stringBuilder.append("reqId:${content?.requestId} action:${content?.action} ") + if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { + stringBuilder.append("secretName:${content.secretName} ") + } + stringBuilder.append("requestedBy:${content?.requestingDeviceId}") + stringBuilder.append("\n") + } + EventType.ENCRYPTED -> { + stringBuilder.append("Failed to Derypt \n") + } + else -> { + stringBuilder.append("?? \n") + } + } + } + val raw = stringBuilder.toString() + setState { + copy(exporting = Success("")) + } + _viewEvents.post(KeyRequestEvents.SaveAudit(action.uri, raw)) + } catch (error: Throwable) { + setState { + copy(exporting = Fail(error)) + } + } + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt index 08016c66ac..0ea0e9de31 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt @@ -16,20 +16,30 @@ package im.vector.app.features.settings.devtools +import android.app.Activity import android.os.Bundle +import android.view.MenuItem import android.view.View +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState import com.google.android.material.tabs.TabLayoutMediator import im.vector.app.R +import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.utils.selectTxtFileToWrite import kotlinx.android.synthetic.main.fragment_devtool_keyrequests.* +import org.matrix.android.sdk.api.extensions.tryOrNull import javax.inject.Inject -class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { +class KeyRequestsFragment @Inject constructor( + val viewModelFactory: KeyRequestViewModel.Factory) : VectorBaseFragment() { override fun getLayoutResId(): Int = R.layout.fragment_devtool_keyrequests @@ -40,6 +50,10 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { private var mPagerAdapter: KeyReqPagerAdapter? = null + private val viewModel: KeyRequestViewModel by fragmentViewModel() + + override fun getMenuRes(): Int = R.menu.menu_audit + private val pageAdapterListener = object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { invalidateOptionsMenu() @@ -53,6 +67,13 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { } } + override fun invalidate() = withState(viewModel) { + when (it.exporting) { + is Loading -> exportWaitingView.isVisible = true + else -> exportWaitingView.isVisible = false + } + } + override fun onDestroy() { invalidateOptionsMenu() super.onDestroy() @@ -77,6 +98,23 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { } } }.attach() + + viewModel.observeViewEvents { + when (it) { + is KeyRequestEvents.SaveAudit -> { + tryOrNull { + val os = requireContext().contentResolver?.openOutputStream(it.uri) + if (os == null) { + false + } else { + os.write(it.raw.toByteArray()) + os.flush() + true + } + } + } + } + } } override fun onDestroyView() { @@ -85,6 +123,28 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { super.onDestroyView() } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.audit_export) { + selectTxtFileToWrite( + activity = requireActivity(), + activityResultLauncher = epxortAuditForActivityResult, + defaultFileName = "audit-export-json_${System.currentTimeMillis()}.txt", + chooserHint = "Export Audit" + ) + return true + } + return super.onOptionsItemSelected(item) + } + + private val epxortAuditForActivityResult = registerStartForActivityResult { activityResult -> + if (activityResult.resultCode == Activity.RESULT_OK) { + val uri = activityResult.data?.data + if (uri != null) { + viewModel.handle(KeyRequestAction.ExportAudit(uri)) + } + } + } + private inner class KeyReqPagerAdapter(fa: Fragment) : FragmentStateAdapter(fa) { override fun getItemCount(): Int = 3 diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt index 60e73fb74d..a82b5dd6c9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt @@ -24,21 +24,19 @@ import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.resources.ColorProvider import kotlinx.android.synthetic.main.fragment_generic_recycler.* import javax.inject.Inject class OutgoingKeyRequestListFragment @Inject constructor( val viewModelFactory: KeyRequestListViewModel.Factory, - private val epoxyController: KeyRequestEpoxyController, - private val colorProvider: ColorProvider + private val epoxyController: OutgoingKeyRequestPagedController ) : VectorBaseFragment() { override fun getLayoutResId() = R.layout.fragment_generic_recycler private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class) override fun invalidate() = withState(viewModel) { state -> - epoxyController.setData(state) + epoxyController.submitList(state.outgoingRoomKeyRequests.invoke()) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt new file mode 100644 index 0000000000..38c4c8153f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devtools + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.paging.PagedListEpoxyController +import im.vector.app.core.ui.list.GenericItem_ +import im.vector.app.core.utils.createUIHandler +import me.gujun.android.span.span +import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest +import javax.inject.Inject + +class OutgoingKeyRequestPagedController @Inject constructor() : PagedListEpoxyController( + // Important it must match the PageList builder notify Looper + modelBuildingHandler = createUIHandler() +) { + + interface InteractionListener { + // fun didTap(data: UserAccountData) + } + + var interactionListener: InteractionListener? = null + + override fun buildItemModel(currentPosition: Int, item: OutgoingRoomKeyRequest?): EpoxyModel<*> { + val roomKeyRequest = item ?: return GenericItem_().apply { id(currentPosition) } + + return GenericItem_().apply { + id(roomKeyRequest.requestId) + title(roomKeyRequest.requestId) + description( + span { + span("roomId:\n") { + textStyle = "bold" + } + +"${roomKeyRequest.roomId}" + + span("sessionId:\n") { + textStyle = "bold" + } + +"${roomKeyRequest.sessionId}" + span("\nstate:") { + textStyle = "bold" + } + +"\n${roomKeyRequest.state.name}" + } + ) + } + } +} diff --git a/vector/src/main/res/layout/fragment_devtool_keyrequests.xml b/vector/src/main/res/layout/fragment_devtool_keyrequests.xml index ccd3cee660..dd0cbff1b1 100644 --- a/vector/src/main/res/layout/fragment_devtool_keyrequests.xml +++ b/vector/src/main/res/layout/fragment_devtool_keyrequests.xml @@ -1,5 +1,5 @@ - + android:layout_height="0dp" /> - \ No newline at end of file + + + \ No newline at end of file diff --git a/vector/src/main/res/menu/menu_audit.xml b/vector/src/main/res/menu/menu_audit.xml new file mode 100644 index 0000000000..1c0d2f9989 --- /dev/null +++ b/vector/src/main/res/menu/menu_audit.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index dd461123cc..1178e5aeb4 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2304,6 +2304,7 @@ Element Android Key Requests + Export Audit Unlock encrypted messages history From a1ed9bb377b12b5317b86c951d6ea4bacd96d4c3 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 28 Oct 2020 17:47:05 +0100 Subject: [PATCH 14/23] Fix / missing new line in secret trail --- .../vector/app/features/settings/devtools/KeyRequestViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt index be8778e1db..aca374afd9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt @@ -119,6 +119,7 @@ class KeyRequestViewModel @AssistedInject constructor( EventType.SEND_SECRET -> { val content = it.getClearContent().toModel() stringBuilder.append("requestId:${content?.requestId} From Device:${it.mxDecryptionResult?.payload?.get("sender_device")}") + stringBuilder.append("\n") } EventType.REQUEST_SECRET -> { val content = it.getClearContent().toModel() From 794a0bb14b1b2682d6b071e705885ff2e9827666 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 28 Oct 2020 17:48:55 +0100 Subject: [PATCH 15/23] Fix / bad ref to json in filename, it's txt --- .../app/features/settings/devtools/KeyRequestsFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt index 0ea0e9de31..3f4d24c6ea 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt @@ -128,7 +128,7 @@ class KeyRequestsFragment @Inject constructor( selectTxtFileToWrite( activity = requireActivity(), activityResultLauncher = epxortAuditForActivityResult, - defaultFileName = "audit-export-json_${System.currentTimeMillis()}.txt", + defaultFileName = "audit-export_${System.currentTimeMillis()}.txt", chooserHint = "Export Audit" ) return true From f190356934bdbe9f97b622a13b449722f02f9a98 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 29 Oct 2020 13:44:47 +0100 Subject: [PATCH 16/23] Fix compilation warnings --- .../sdk/internal/crypto/IncomingGossipingRequestManager.kt | 2 +- .../android/sdk/internal/crypto/store/IMXCryptoStore.kt | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index ae017b33f5..97ae0b9d83 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -105,7 +105,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( fun onGossipingRequestEvent(event: Event) { Timber.v("## CRYPTO | GOSSIP onGossipingRequestEvent type ${event.type} from user ${event.senderId}") val roomKeyShare = event.getClearContent().toModel() - val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } + // val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } when (roomKeyShare?.action) { GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { if (event.getClearType() == EventType.REQUEST_SECRET) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 74773384ae..72d541d4df 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -127,8 +127,10 @@ internal interface IMXCryptoStore { fun getPendingIncomingRoomKeyRequests(): List fun getPendingIncomingGossipingRequests(): List + fun storeIncomingGossipingRequest(request: IncomingShareRequestCommon, ageLocalTS: Long?) - fun storeIncomingGossipingRequests(request: List) + + fun storeIncomingGossipingRequests(requests: List) // fun getPendingIncomingSecretShareRequests(): List /** From f48d4c021a36e9b07ddb5faeb55ef0570f913aa1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 29 Oct 2020 14:10:41 +0100 Subject: [PATCH 17/23] Cleanup and split long lines --- .../crypto/store/db/RealmCryptoStore.kt | 4 +- .../settings/devtools/KeyRequestViewModel.kt | 129 +++++++++--------- .../settings/devtools/KeyRequestsFragment.kt | 13 +- 3 files changed, 70 insertions(+), 76 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 7644ab5cc2..5d19e6d607 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -1124,7 +1124,7 @@ internal class RealmCryptoStore @Inject constructor( content = ContentMapper.map(event.content) ).apply { sendState = SendState.SYNCED - decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult) + decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult) decryptionErrorCode = event.mCryptoError?.name } realm.insertOrUpdate(entity) @@ -1143,7 +1143,7 @@ internal class RealmCryptoStore @Inject constructor( content = ContentMapper.map(event.content) ).apply { sendState = SendState.SYNCED - decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult) + decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult) decryptionErrorCode = event.mCryptoError?.name } realm.insertOrUpdate(entity) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt index aca374afd9..e64f673309 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt @@ -29,6 +29,7 @@ import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction @@ -54,7 +55,7 @@ sealed class KeyRequestEvents : VectorViewEvents { } data class KeyRequestViewState( - val exporting: Async = Uninitialized + val exporting: Async = Uninitialized ) : MvRxState class KeyRequestViewModel @AssistedInject constructor( @@ -78,77 +79,75 @@ class KeyRequestViewModel @AssistedInject constructor( override fun handle(action: KeyRequestAction) { when (action) { - is KeyRequestAction.ExportAudit -> { - setState { - copy(exporting = Loading()) - } - viewModelScope.launch(Dispatchers.IO) { - try { - // this can take long - val eventList = session.cryptoService().getGossipingEvents() - // clean it a bit to - val stringBuilder = StringBuilder() - eventList.forEach { - val clearType = it.getClearType() - stringBuilder.append("[${it.ageLocalTs}] : $clearType from:${it.senderId} - ") - when (clearType) { - EventType.ROOM_KEY_REQUEST -> { - val content = it.getClearContent().toModel() - stringBuilder.append("reqId:${content?.requestId} action:${content?.action} ") - if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { - stringBuilder.append("sessionId: ${content.body?.sessionId} ") - } - stringBuilder.append("requestedBy: ${content?.requestingDeviceId} ") - stringBuilder.append("\n") - } - EventType.FORWARDED_ROOM_KEY -> { - val encryptedContent = it.content.toModel() - val content = it.getClearContent().toModel() + is KeyRequestAction.ExportAudit -> exportAudit(action) + }.exhaustive + } - stringBuilder.append("sessionId:${content?.sessionId} From Device (sender key):${encryptedContent?.senderKey} ") - span("\nFrom Device (sender key):") { - textStyle = "bold" - } - stringBuilder.append("\n") + private fun exportAudit(action: KeyRequestAction.ExportAudit) { + setState { + copy(exporting = Loading()) + } + viewModelScope.launch(Dispatchers.IO) { + try { + // this can take long + val eventList = session.cryptoService().getGossipingEvents() + // clean it a bit to + val raw = buildString { + eventList.forEach { + val clearType = it.getClearType() + append("[${it.ageLocalTs}] : $clearType from:${it.senderId} - ") + when (clearType) { + EventType.ROOM_KEY_REQUEST -> { + val content = it.getClearContent().toModel() + append("reqId:${content?.requestId} action:${content?.action} ") + if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { + append("sessionId: ${content.body?.sessionId} ") } - EventType.ROOM_KEY -> { - val content = it.getClearContent() - stringBuilder.append("sessionId:${content?.get("session_id")} roomId:${content?.get("room_id")} dest:${content?.get("_dest") ?: "me"}") - stringBuilder.append("\n") - } - EventType.SEND_SECRET -> { - val content = it.getClearContent().toModel() - stringBuilder.append("requestId:${content?.requestId} From Device:${it.mxDecryptionResult?.payload?.get("sender_device")}") - stringBuilder.append("\n") - } - EventType.REQUEST_SECRET -> { - val content = it.getClearContent().toModel() - stringBuilder.append("reqId:${content?.requestId} action:${content?.action} ") - if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { - stringBuilder.append("secretName:${content.secretName} ") - } - stringBuilder.append("requestedBy:${content?.requestingDeviceId}") - stringBuilder.append("\n") - } - EventType.ENCRYPTED -> { - stringBuilder.append("Failed to Derypt \n") - } - else -> { - stringBuilder.append("?? \n") + append("requestedBy: ${content?.requestingDeviceId}") + } + EventType.FORWARDED_ROOM_KEY -> { + val encryptedContent = it.content.toModel() + val content = it.getClearContent().toModel() + + append("sessionId:${content?.sessionId} From Device (sender key):${encryptedContent?.senderKey}") + span("\nFrom Device (sender key):") { + textStyle = "bold" } } + EventType.ROOM_KEY -> { + val content = it.getClearContent() + append("sessionId:${content?.get("session_id")} roomId:${content?.get("room_id")} dest:${content?.get("_dest") ?: "me"}") + } + EventType.SEND_SECRET -> { + val content = it.getClearContent().toModel() + append("requestId:${content?.requestId} From Device:${it.mxDecryptionResult?.payload?.get("sender_device")}") + } + EventType.REQUEST_SECRET -> { + val content = it.getClearContent().toModel() + append("reqId:${content?.requestId} action:${content?.action} ") + if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { + append("secretName:${content.secretName} ") + } + append("requestedBy:${content?.requestingDeviceId}") + } + EventType.ENCRYPTED -> { + append("Failed to Decrypt") + } + else -> { + append("??") + } } - val raw = stringBuilder.toString() - setState { - copy(exporting = Success("")) - } - _viewEvents.post(KeyRequestEvents.SaveAudit(action.uri, raw)) - } catch (error: Throwable) { - setState { - copy(exporting = Fail(error)) - } + append("\n") } } + setState { + copy(exporting = Success(Unit)) + } + _viewEvents.post(KeyRequestEvents.SaveAudit(action.uri, raw)) + } catch (error: Throwable) { + setState { + copy(exporting = Fail(error)) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt index 3f4d24c6ea..ff91d9bba3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt @@ -30,6 +30,7 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.tabs.TabLayoutMediator import im.vector.app.R +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseFragment @@ -103,17 +104,11 @@ class KeyRequestsFragment @Inject constructor( when (it) { is KeyRequestEvents.SaveAudit -> { tryOrNull { - val os = requireContext().contentResolver?.openOutputStream(it.uri) - if (os == null) { - false - } else { - os.write(it.raw.toByteArray()) - os.flush() - true - } + requireContext().contentResolver?.openOutputStream(it.uri) + ?.use { os -> os.write(it.raw.toByteArray()) } } } - } + }.exhaustive } } From f6ec7bc323813e83561428942e6a46d0558ae0b7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 29 Oct 2020 14:11:34 +0100 Subject: [PATCH 18/23] values-v21 does not exist anymore --- tools/check/check_code_quality.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/check/check_code_quality.sh b/tools/check/check_code_quality.sh index e855440e81..0b4272cbfe 100755 --- a/tools/check/check_code_quality.sh +++ b/tools/check/check_code_quality.sh @@ -91,7 +91,6 @@ ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_resources.txt ./vector/src/main/res/color \ ./vector/src/main/res/layout \ ./vector/src/main/res/values \ - ./vector/src/main/res/values-v21 \ ./vector/src/main/res/xml resultForbiddenStringInResource=$? From 909ee2cc85dce43a169d606bd56dd741a2a4b6d4 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 29 Oct 2020 14:57:17 +0100 Subject: [PATCH 19/23] Fix / bad format of trail item --- .../devtools/IncomingKeyRequestPagedController.kt | 15 +++++++++++---- .../devtools/OutgoingKeyRequestPagedController.kt | 8 ++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt index 1510d47180..c2bdcfbd04 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt @@ -47,16 +47,23 @@ class IncomingKeyRequestPagedController @Inject constructor( title(roomKeyRequest.requestId) description( span { - span("From user: ${roomKeyRequest.userId}") - +vectorDateFormatter.format(roomKeyRequest.localCreationTimestamp, DateFormatKind.DEFAULT_DATE_AND_TIME) - span("sessionId:") { + span("From: ") { textStyle = "bold" } + span("${roomKeyRequest.userId}") + +vectorDateFormatter.format(roomKeyRequest.localCreationTimestamp, DateFormatKind.DEFAULT_DATE_AND_TIME) + span("\nsessionId:") { + textStyle = "bold" + } + +"${roomKeyRequest.requestBody?.sessionId}" span("\nFrom device:") { textStyle = "bold" } +"${roomKeyRequest.deviceId}" - +"\n${roomKeyRequest.state.name}" + span("\nstate: ") { + textStyle = "bold" + } + +roomKeyRequest.state.name } ) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt index 38c4c8153f..c2a3bc9827 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt @@ -43,19 +43,19 @@ class OutgoingKeyRequestPagedController @Inject constructor() : PagedListEpoxyCo title(roomKeyRequest.requestId) description( span { - span("roomId:\n") { + span("roomId: ") { textStyle = "bold" } +"${roomKeyRequest.roomId}" - span("sessionId:\n") { + span("\nsessionId: ") { textStyle = "bold" } +"${roomKeyRequest.sessionId}" - span("\nstate:") { + span("\nstate: ") { textStyle = "bold" } - +"\n${roomKeyRequest.state.name}" + +roomKeyRequest.state.name } ) } From e8dcdc71827ad2c579d9adbd7f2152564dbbc677 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 29 Oct 2020 14:46:42 +0100 Subject: [PATCH 20/23] Format date in the exported trail --- .../settings/devtools/KeyRequestViewModel.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt index e64f673309..4385cb2c09 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt @@ -33,6 +33,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction +import im.vector.app.core.resources.DateProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import me.gujun.android.span.span @@ -45,6 +46,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest +import org.threeten.bp.format.DateTimeFormatter sealed class KeyRequestAction : VectorViewModelAction { data class ExportAudit(val uri: Uri) : KeyRequestAction() @@ -68,6 +70,10 @@ class KeyRequestViewModel @AssistedInject constructor( fun create(initialState: KeyRequestViewState): KeyRequestViewModel } + private val full24DateFormatter by lazy { + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS") + } + companion object : MvRxViewModelFactory { @JvmStatic @@ -95,7 +101,7 @@ class KeyRequestViewModel @AssistedInject constructor( val raw = buildString { eventList.forEach { val clearType = it.getClearType() - append("[${it.ageLocalTs}] : $clearType from:${it.senderId} - ") + append("[${getFormattedDate(it.ageLocalTs)}] $clearType from:${it.senderId} - ") when (clearType) { EventType.ROOM_KEY_REQUEST -> { val content = it.getClearContent().toModel() @@ -151,4 +157,11 @@ class KeyRequestViewModel @AssistedInject constructor( } } } + + private fun getFormattedDate(ageLocalTs: Long?): String { + return ageLocalTs + ?.let { DateProvider.toLocalDateTime(it) } + ?.let { full24DateFormatter.format(it) } + ?: "?" + } } From 41e3ff381f2433b5f89cc578debb050283028091 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 29 Oct 2020 16:02:21 +0100 Subject: [PATCH 21/23] Rename method --- .../android/sdk/internal/crypto/CryptoSessionInfoProvider.kt | 2 +- .../matrix/android/sdk/internal/crypto/DefaultCryptoService.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt index ba0dbda786..e8a70615e1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt @@ -47,7 +47,7 @@ internal class CryptoSessionInfoProvider @Inject constructor( /** * @param allActive if true return joined as well as invited, if false, only joined */ - fun getRoomUserIdsForCrypto(roomId: String, allActive: Boolean): List { + fun getRoomUserIds(roomId: String, allActive: Boolean): List { var userIds: List = emptyList() monarchy.doWithRealm { realm -> userIds = if (allActive) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 07545c50bc..326eac8f91 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -872,7 +872,7 @@ internal class DefaultCryptoService @Inject constructor( private fun getRoomUserIds(roomId: String): List { val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser() && shouldEncryptForInvitedMembers(roomId) - return cryptoSessionInfoProvider.getRoomUserIdsForCrypto(roomId, encryptForInvitedMembers) + return cryptoSessionInfoProvider.getRoomUserIds(roomId, encryptForInvitedMembers) } /** From 077bcb3f2ac6e3a45eb76a6934c9f4a4dde5466a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 29 Oct 2020 16:04:07 +0100 Subject: [PATCH 22/23] Export the key share trail in rageshake (opt-in) --- .../features/rageshake/BugReportActivity.kt | 4 + .../app/features/rageshake/BugReporter.kt | 20 ++++ .../devtools/GossipingEventsSerializer.kt | 92 +++++++++++++++++++ .../settings/devtools/KeyRequestViewModel.kt | 60 +----------- .../main/res/layout/activity_bug_report.xml | 9 ++ vector/src/main/res/values/strings.xml | 1 + 6 files changed, 127 insertions(+), 59 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt index e26736de7e..3f38e4ef15 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt @@ -69,6 +69,9 @@ class BugReportActivity : VectorBaseActivity() { bug_report_button_include_crash_logs.isChecked = false bug_report_button_include_crash_logs.isVisible = false + bug_report_button_include_key_share_history.isChecked = false + bug_report_button_include_key_share_history.isVisible = false + // Keep the screenshot } else { supportActionBar?.setTitle(R.string.title_activity_bug_report) @@ -121,6 +124,7 @@ class BugReportActivity : VectorBaseActivity() { forSuggestion, bug_report_button_include_logs.isChecked, bug_report_button_include_crash_logs.isChecked, + bug_report_button_include_key_share_history.isChecked, bug_report_button_include_screenshot.isChecked, bug_report_edit_text.text.toString(), object : BugReporter.IMXBugReportListener { diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index e7382a17e9..95fe16ea51 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -33,6 +33,7 @@ import im.vector.app.core.extensions.getAllChildFragments import im.vector.app.core.extensions.toOnOff import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.settings.devtools.GossipingEventsSerializer import im.vector.app.features.settings.locale.SystemLocaleProvider import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.version.VersionProvider @@ -74,6 +75,7 @@ class BugReporter @Inject constructor( private const val LOG_CAT_FILENAME = "logcat.log" private const val LOG_CAT_SCREENSHOT_FILENAME = "screenshot.png" private const val CRASH_FILENAME = "crash.log" + private const val KEY_REQUESTS_FILENAME = "keyRequests.log" private const val BUFFER_SIZE = 1024 * 1024 * 50 } @@ -143,6 +145,7 @@ class BugReporter @Inject constructor( * @param forSuggestion true to send a suggestion * @param withDevicesLogs true to include the device log * @param withCrashLogs true to include the crash logs + * @param withKeyRequestHistory true to include the crash logs * @param withScreenshot true to include the screenshot * @param theBugDescription the bug description * @param listener the listener @@ -152,6 +155,7 @@ class BugReporter @Inject constructor( forSuggestion: Boolean, withDevicesLogs: Boolean, withCrashLogs: Boolean, + withKeyRequestHistory: Boolean, withScreenshot: Boolean, theBugDescription: String, listener: IMXBugReportListener?) { @@ -207,6 +211,22 @@ class BugReporter @Inject constructor( } } + activeSessionHolder.getSafeActiveSession() + ?.takeIf { !mIsCancelled && withKeyRequestHistory } + ?.cryptoService() + ?.getGossipingEvents() + ?.let { GossipingEventsSerializer().serialize(it) } + ?.toByteArray() + ?.let { rawByteArray -> + File(context.cacheDir.absolutePath, KEY_REQUESTS_FILENAME) + .also { + it.outputStream() + .use { os -> os.write(rawByteArray) } + } + } + ?.let { compressFile(it) } + ?.let { gzippedFiles.add(it) } + var deviceId = "undefined" var userId = "undefined" var olmVersion = "undefined" diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt new file mode 100644 index 0000000000..d18a6c2ba8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devtools + +import im.vector.app.core.resources.DateProvider +import me.gujun.android.span.span +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.toModel +import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent +import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent +import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent +import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject +import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest +import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest +import org.threeten.bp.format.DateTimeFormatter + +class GossipingEventsSerializer { + private val full24DateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS") + + fun serialize(eventList: List): String { + return buildString { + eventList.forEach { + val clearType = it.getClearType() + append("[${getFormattedDate(it.ageLocalTs)}] $clearType from:${it.senderId} - ") + when (clearType) { + EventType.ROOM_KEY_REQUEST -> { + val content = it.getClearContent().toModel() + append("reqId:${content?.requestId} action:${content?.action} ") + if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { + append("sessionId: ${content.body?.sessionId} ") + } + append("requestedBy: ${content?.requestingDeviceId}") + } + EventType.FORWARDED_ROOM_KEY -> { + val encryptedContent = it.content.toModel() + val content = it.getClearContent().toModel() + + append("sessionId:${content?.sessionId} From Device (sender key):${encryptedContent?.senderKey}") + span("\nFrom Device (sender key):") { + textStyle = "bold" + } + } + EventType.ROOM_KEY -> { + val content = it.getClearContent() + append("sessionId:${content?.get("session_id")} roomId:${content?.get("room_id")} dest:${content?.get("_dest") ?: "me"}") + } + EventType.SEND_SECRET -> { + val content = it.getClearContent().toModel() + append("requestId:${content?.requestId} From Device:${it.mxDecryptionResult?.payload?.get("sender_device")}") + } + EventType.REQUEST_SECRET -> { + val content = it.getClearContent().toModel() + append("reqId:${content?.requestId} action:${content?.action} ") + if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { + append("secretName:${content.secretName} ") + } + append("requestedBy:${content?.requestingDeviceId}") + } + EventType.ENCRYPTED -> { + append("Failed to Decrypt") + } + else -> { + append("??") + } + } + append("\n") + } + } + } + + private fun getFormattedDate(ageLocalTs: Long?): String { + return ageLocalTs + ?.let { DateProvider.toLocalDateTime(it) } + ?.let { full24DateFormatter.format(it) } + ?: "?" + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt index 4385cb2c09..d49d3dc0b6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt @@ -70,10 +70,6 @@ class KeyRequestViewModel @AssistedInject constructor( fun create(initialState: KeyRequestViewState): KeyRequestViewModel } - private val full24DateFormatter by lazy { - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS") - } - companion object : MvRxViewModelFactory { @JvmStatic @@ -98,54 +94,7 @@ class KeyRequestViewModel @AssistedInject constructor( // this can take long val eventList = session.cryptoService().getGossipingEvents() // clean it a bit to - val raw = buildString { - eventList.forEach { - val clearType = it.getClearType() - append("[${getFormattedDate(it.ageLocalTs)}] $clearType from:${it.senderId} - ") - when (clearType) { - EventType.ROOM_KEY_REQUEST -> { - val content = it.getClearContent().toModel() - append("reqId:${content?.requestId} action:${content?.action} ") - if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { - append("sessionId: ${content.body?.sessionId} ") - } - append("requestedBy: ${content?.requestingDeviceId}") - } - EventType.FORWARDED_ROOM_KEY -> { - val encryptedContent = it.content.toModel() - val content = it.getClearContent().toModel() - - append("sessionId:${content?.sessionId} From Device (sender key):${encryptedContent?.senderKey}") - span("\nFrom Device (sender key):") { - textStyle = "bold" - } - } - EventType.ROOM_KEY -> { - val content = it.getClearContent() - append("sessionId:${content?.get("session_id")} roomId:${content?.get("room_id")} dest:${content?.get("_dest") ?: "me"}") - } - EventType.SEND_SECRET -> { - val content = it.getClearContent().toModel() - append("requestId:${content?.requestId} From Device:${it.mxDecryptionResult?.payload?.get("sender_device")}") - } - EventType.REQUEST_SECRET -> { - val content = it.getClearContent().toModel() - append("reqId:${content?.requestId} action:${content?.action} ") - if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { - append("secretName:${content.secretName} ") - } - append("requestedBy:${content?.requestingDeviceId}") - } - EventType.ENCRYPTED -> { - append("Failed to Decrypt") - } - else -> { - append("??") - } - } - append("\n") - } - } + val raw = GossipingEventsSerializer().serialize(eventList) setState { copy(exporting = Success(Unit)) } @@ -157,11 +106,4 @@ class KeyRequestViewModel @AssistedInject constructor( } } } - - private fun getFormattedDate(ageLocalTs: Long?): String { - return ageLocalTs - ?.let { DateProvider.toLocalDateTime(it) } - ?.let { full24DateFormatter.format(it) } - ?: "?" - } } diff --git a/vector/src/main/res/layout/activity_bug_report.xml b/vector/src/main/res/layout/activity_bug_report.xml index 3bfb1f5429..34169f44f8 100644 --- a/vector/src/main/res/layout/activity_bug_report.xml +++ b/vector/src/main/res/layout/activity_bug_report.xml @@ -125,6 +125,15 @@ android:checked="true" android:text="@string/send_bug_report_include_crash_logs" /> + + Send logs Send crash logs + Send key share requests history Send screenshot Report bug Please describe the bug. What did you do? What did you expect to happen? What actually happened? From 47a90746952059741d258e319183a3bfe7cfb351 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 30 Oct 2020 10:59:11 +0100 Subject: [PATCH 23/23] Cleanup --- .../features/settings/devtools/KeyRequestViewModel.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt index d49d3dc0b6..a7d5e8f1ac 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt @@ -33,20 +33,9 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction -import im.vector.app.core.resources.DateProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent -import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent -import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent -import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest -import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest -import org.threeten.bp.format.DateTimeFormatter sealed class KeyRequestAction : VectorViewModelAction { data class ExportAudit(val uri: Uri) : KeyRequestAction()