Merge pull request #2283 from vector-im/feature/bca/crypto_perf
Crypto cleaning and perf improvement
This commit is contained in:
commit
4791eb85af
@ -88,7 +88,10 @@ class CommonTestHelper(context: Context) {
|
|||||||
fun syncSession(session: Session) {
|
fun syncSession(session: Session) {
|
||||||
val lock = CountDownLatch(1)
|
val lock = CountDownLatch(1)
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.Main) { session.open() }
|
val job = GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
session.open()
|
||||||
|
}
|
||||||
|
runBlocking { job.join() }
|
||||||
|
|
||||||
session.startSync(true)
|
session.startSync(true)
|
||||||
|
|
||||||
@ -341,7 +344,7 @@ class CommonTestHelper(context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Transform a method with a MatrixCallback to a synchronous method
|
// Transform a method with a MatrixCallback to a synchronous method
|
||||||
inline fun <reified T> doSync(block: (MatrixCallback<T>) -> Unit): T {
|
inline fun <reified T> doSync(timeout: Long? = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): T {
|
||||||
val lock = CountDownLatch(1)
|
val lock = CountDownLatch(1)
|
||||||
var result: T? = null
|
var result: T? = null
|
||||||
|
|
||||||
@ -354,7 +357,7 @@ class CommonTestHelper(context: Context) {
|
|||||||
|
|
||||||
block.invoke(callback)
|
block.invoke(callback)
|
||||||
|
|
||||||
await(lock)
|
await(lock, timeout)
|
||||||
|
|
||||||
assertNotNull(result)
|
assertNotNull(result)
|
||||||
return result!!
|
return result!!
|
||||||
@ -366,8 +369,9 @@ class CommonTestHelper(context: Context) {
|
|||||||
fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) }
|
fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) }
|
||||||
|
|
||||||
fun signOutAndClose(session: Session) {
|
fun signOutAndClose(session: Session) {
|
||||||
doSync<Unit> { session.signOut(true, it) }
|
doSync<Unit>(60_000) { session.signOut(true, it) }
|
||||||
session.close()
|
// no need signout will close
|
||||||
|
// session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
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.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
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
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 roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||||
val roomFromAlicePOV = aliceSession.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<String>) {
|
|
||||||
// noop
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
|
||||||
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
|
// Alice sends a message
|
||||||
roomFromAlicePOV.sendTextMessage(messagesFromAlice[0])
|
mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1)
|
||||||
|
// roomFromAlicePOV.sendTextMessage(messagesFromAlice[0])
|
||||||
|
|
||||||
// Bob send 3 messages
|
// Bob send 3 messages
|
||||||
roomFromBobPOV.sendTextMessage(messagesFromBob[0])
|
mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1)
|
||||||
roomFromBobPOV.sendTextMessage(messagesFromBob[1])
|
mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1)
|
||||||
roomFromBobPOV.sendTextMessage(messagesFromBob[2])
|
mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1)
|
||||||
|
|
||||||
// Alice sends a message
|
// Alice sends a message
|
||||||
roomFromAlicePOV.sendTextMessage(messagesFromAlice[1])
|
mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1)
|
||||||
|
|
||||||
mTestHelper.await(lock)
|
|
||||||
|
|
||||||
bobTimeline.removeListener(bobEventsListener)
|
|
||||||
bobTimeline.dispose()
|
|
||||||
|
|
||||||
return cryptoTestData
|
return cryptoTestData
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.crypto
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
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.DeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
|
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
|
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
import kotlin.jvm.Throws
|
||||||
|
|
||||||
interface CryptoService {
|
interface CryptoService {
|
||||||
|
|
||||||
@ -142,10 +144,13 @@ interface CryptoService {
|
|||||||
fun removeSessionListener(listener: NewSessionListener)
|
fun removeSessionListener(listener: NewSessionListener)
|
||||||
|
|
||||||
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
||||||
|
fun getOutgoingRoomKeyRequestsPaged(): LiveData<PagedList<OutgoingRoomKeyRequest>>
|
||||||
|
|
||||||
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||||
|
fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>>
|
||||||
|
|
||||||
fun getGossipingEventsTrail(): List<Event>
|
fun getGossipingEventsTrail(): LiveData<PagedList<Event>>
|
||||||
|
fun getGossipingEvents(): List<Event>
|
||||||
|
|
||||||
// For testing shared session
|
// For testing shared session
|
||||||
fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int>
|
fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int>
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
internal 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 getRoomUserIds(roomId: String, allActive: Boolean): List<String> {
|
||||||
|
var userIds: List<String> = emptyList()
|
||||||
|
monarchy.doWithRealm { realm ->
|
||||||
|
userIds = if (allActive) {
|
||||||
|
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
|
||||||
|
} else {
|
||||||
|
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userIds
|
||||||
|
}
|
||||||
|
}
|
@ -17,12 +17,10 @@
|
|||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -51,9 +49,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.RoomHistoryVisibility
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
|
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.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.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.actions.SetDeviceVerificationAction
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
|
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension
|
import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension
|
||||||
@ -68,7 +64,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.MXEncryptEventContentResult
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
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.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.RoomKeyContent
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
|
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
|
||||||
@ -82,21 +77,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.DeleteDeviceWithUserPasswordTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
|
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.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.SetDeviceNameTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
|
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
|
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.DeviceId
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
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.di.UserId
|
||||||
import org.matrix.android.sdk.internal.extensions.foldToCallback
|
import org.matrix.android.sdk.internal.extensions.foldToCallback
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.SyncResponse
|
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.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.task.TaskThread
|
import org.matrix.android.sdk.internal.task.TaskThread
|
||||||
@ -104,11 +93,11 @@ import org.matrix.android.sdk.internal.task.configureWith
|
|||||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.internal.util.fetchCopied
|
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.jvm.Throws
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,28 +160,16 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
private val setDeviceNameTask: SetDeviceNameTask,
|
private val setDeviceNameTask: SetDeviceNameTask,
|
||||||
private val uploadKeysTask: UploadKeysTask,
|
private val uploadKeysTask: UploadKeysTask,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val cryptoCoroutineScope: CoroutineScope,
|
private val cryptoCoroutineScope: CoroutineScope,
|
||||||
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
private val eventDecryptor: EventDecryptor
|
||||||
private val sendToDeviceTask: SendToDeviceTask,
|
|
||||||
private val messageEncrypter: MessageEncrypter
|
|
||||||
) : CryptoService {
|
) : CryptoService {
|
||||||
|
|
||||||
init {
|
|
||||||
verificationService.cryptoService = this
|
|
||||||
}
|
|
||||||
|
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
|
||||||
|
|
||||||
private val isStarting = AtomicBoolean(false)
|
private val isStarting = AtomicBoolean(false)
|
||||||
private val isStarted = 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<Long>()
|
|
||||||
|
|
||||||
fun onStateEvent(roomId: String, event: Event) {
|
fun onStateEvent(roomId: String, event: Event) {
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||||
@ -209,6 +186,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val gossipingBuffer = mutableListOf<Event>()
|
||||||
|
|
||||||
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
||||||
setDeviceNameTask
|
setDeviceNameTask
|
||||||
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
||||||
@ -410,7 +389,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
fun close() = runBlocking(coroutineDispatchers.crypto) {
|
fun close() = runBlocking(coroutineDispatchers.crypto) {
|
||||||
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
|
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
|
||||||
|
incomingGossipingRequestManager.close()
|
||||||
olmDevice.release()
|
olmDevice.release()
|
||||||
cryptoStore.close()
|
cryptoStore.close()
|
||||||
}
|
}
|
||||||
@ -452,6 +431,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
incomingGossipingRequestManager.processReceivedGossipingRequests()
|
incomingGossipingRequestManager.processReceivedGossipingRequests()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tryOrNull {
|
||||||
|
gossipingBuffer.toList().let {
|
||||||
|
cryptoStore.saveGossipingEvents(it)
|
||||||
|
}
|
||||||
|
gossipingBuffer.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -612,13 +598,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM
|
* @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
*/
|
*/
|
||||||
override fun isRoomEncrypted(roomId: String): Boolean {
|
override fun isRoomEncrypted(roomId: String): Boolean {
|
||||||
val encryptionEvent = monarchy.fetchCopied { realm ->
|
return cryptoSessionInfoProvider.isRoomEncrypted(roomId)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -660,11 +640,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
eventType: String,
|
eventType: String,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
||||||
|
// moved to crypto scope to have uptodate values
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
// if (!isStarted()) {
|
|
||||||
// Timber.v("## CRYPTO | encryptEventContent() : wait after e2e init")
|
|
||||||
// internalStart(false)
|
|
||||||
// }
|
|
||||||
val userIds = getRoomUserIds(roomId)
|
val userIds = getRoomUserIds(roomId)
|
||||||
var alg = roomEncryptorsStore.get(roomId)
|
var alg = roomEncryptorsStore.get(roomId)
|
||||||
if (alg == null) {
|
if (alg == null) {
|
||||||
@ -720,14 +697,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param callback the callback to return data or null
|
* @param callback the callback to return data or null
|
||||||
*/
|
*/
|
||||||
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
||||||
cryptoCoroutineScope.launch {
|
eventDecryptor.decryptEventAsync(event, timeline, callback)
|
||||||
val result = runCatching {
|
|
||||||
withContext(coroutineDispatchers.crypto) {
|
|
||||||
internalDecryptEvent(event, timeline)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.foldToCallback(callback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -739,42 +709,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
@Throws(MXCryptoError::class)
|
||||||
private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||||
val eventContent = event.content
|
return eventDecryptor.decryptEvent(event, timeline)
|
||||||
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<OlmEventContent>()
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -796,19 +731,19 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
||||||
cryptoStore.saveGossipingEvent(event)
|
gossipingBuffer.add(event)
|
||||||
// Keys are imported directly, not waiting for end of sync
|
// Keys are imported directly, not waiting for end of sync
|
||||||
onRoomKeyEvent(event)
|
onRoomKeyEvent(event)
|
||||||
}
|
}
|
||||||
EventType.REQUEST_SECRET,
|
EventType.REQUEST_SECRET,
|
||||||
EventType.ROOM_KEY_REQUEST -> {
|
EventType.ROOM_KEY_REQUEST -> {
|
||||||
// save audit trail
|
// 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)
|
// Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
|
||||||
incomingGossipingRequestManager.onGossipingRequestEvent(event)
|
incomingGossipingRequestManager.onGossipingRequestEvent(event)
|
||||||
}
|
}
|
||||||
EventType.SEND_SECRET -> {
|
EventType.SEND_SECRET -> {
|
||||||
cryptoStore.saveGossipingEvent(event)
|
gossipingBuffer.add(event)
|
||||||
onSecretSendReceived(event)
|
onSecretSendReceived(event)
|
||||||
}
|
}
|
||||||
EventType.ROOM_KEY_WITHHELD -> {
|
EventType.ROOM_KEY_WITHHELD -> {
|
||||||
@ -828,7 +763,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
private fun onRoomKeyEvent(event: Event) {
|
private fun onRoomKeyEvent(event: Event) {
|
||||||
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
|
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: 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()) {
|
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) {
|
||||||
Timber.e("## CRYPTO | GOSSIP onRoomKeyEvent() : missing fields")
|
Timber.e("## CRYPTO | GOSSIP onRoomKeyEvent() : missing fields")
|
||||||
return
|
return
|
||||||
@ -935,19 +870,9 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getRoomUserIds(roomId: String): List<String> {
|
private fun getRoomUserIds(roomId: String): List<String> {
|
||||||
var userIds: List<String> = emptyList()
|
|
||||||
monarchy.doWithRealm { realm ->
|
|
||||||
// Check whether the event content must be encrypted for the invited members.
|
|
||||||
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
|
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
|
||||||
&& shouldEncryptForInvitedMembers(roomId)
|
&& shouldEncryptForInvitedMembers(roomId)
|
||||||
|
return cryptoSessionInfoProvider.getRoomUserIds(roomId, encryptForInvitedMembers)
|
||||||
userIds = if (encryptForInvitedMembers) {
|
|
||||||
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
|
|
||||||
} else {
|
|
||||||
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return userIds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1257,38 +1182,38 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
incomingGossipingRequestManager.removeRoomKeysRequestListener(listener)
|
incomingGossipingRequestManager.removeRoomKeysRequestListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) {
|
// private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) {
|
||||||
val deviceKey = deviceInfo.identityKey()
|
// val deviceKey = deviceInfo.identityKey()
|
||||||
|
//
|
||||||
val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
|
// val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
|
||||||
val now = System.currentTimeMillis()
|
// val now = System.currentTimeMillis()
|
||||||
if (now - lastForcedDate < CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
|
// if (now - lastForcedDate < CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
|
||||||
Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
|
// Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
|
// Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
|
||||||
lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
|
// lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
|
||||||
|
//
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true)
|
// ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true)
|
||||||
|
//
|
||||||
// Now send a blank message on that session so the other side knows about it.
|
// // 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)
|
// // (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
|
// // 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,
|
// // 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
|
// // 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).
|
// // is the session it has most recently received a message on).
|
||||||
val payloadJson = mapOf<String, Any>("type" to EventType.DUMMY)
|
// val payloadJson = mapOf<String, Any>("type" to EventType.DUMMY)
|
||||||
|
//
|
||||||
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
|
// val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
|
||||||
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
// val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||||
sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload)
|
// sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload)
|
||||||
Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}")
|
// Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}")
|
||||||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
|
// val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
|
||||||
sendToDeviceTask.execute(sendToDeviceParams)
|
// sendToDeviceTask.execute(sendToDeviceParams)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the list of unknown devices
|
* Provides the list of unknown devices
|
||||||
@ -1339,14 +1264,26 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return cryptoStore.getOutgoingRoomKeyRequests()
|
return cryptoStore.getOutgoingRoomKeyRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getOutgoingRoomKeyRequestsPaged(): LiveData<PagedList<OutgoingRoomKeyRequest>> {
|
||||||
|
return cryptoStore.getOutgoingRoomKeyRequestsPaged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>> {
|
||||||
|
return cryptoStore.getIncomingRoomKeyRequestsPaged()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest> {
|
override fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest> {
|
||||||
return cryptoStore.getIncomingRoomKeyRequests()
|
return cryptoStore.getIncomingRoomKeyRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getGossipingEventsTrail(): List<Event> {
|
override fun getGossipingEventsTrail(): LiveData<PagedList<Event>> {
|
||||||
return cryptoStore.getGossipingEventsTrail()
|
return cryptoStore.getGossipingEventsTrail()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getGossipingEvents(): List<Event> {
|
||||||
|
return cryptoStore.getGossipingEvents()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int> {
|
override fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int> {
|
||||||
return cryptoStore.getSharedWithInfo(roomId, sessionId)
|
return cryptoStore.getSharedWithInfo(roomId, sessionId)
|
||||||
}
|
}
|
||||||
|
@ -377,7 +377,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update devices trust for these users
|
// Update devices trust for these users
|
||||||
dispatchDeviceChange(downloadUsers)
|
// dispatchDeviceChange(downloadUsers)
|
||||||
|
|
||||||
return onKeysDownloadSucceed(filteredUsers, response.failures)
|
return onKeysDownloadSucceed(filteredUsers, response.failures)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
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<Long>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<MXEventDecryptionResult>) {
|
||||||
|
// 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<OlmEventContent>()
|
||||||
|
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<String, Any>("type" to EventType.DUMMY)
|
||||||
|
|
||||||
|
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
|
||||||
|
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
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
|
||||||
|
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<CacheKey, OlmInboundGroupSessionWrapper2>(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<OlmInboundGroupSessionWrapper2>()
|
||||||
|
|
||||||
|
@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<OlmInboundGroupSessionWrapper2>().apply { addAll(dirtySession) }
|
||||||
|
dirtySession.clear()
|
||||||
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
|
Timber.v("## Inbound: getInboundGroupSession batching save of ${dirtySession.size}")
|
||||||
|
tryOrNull {
|
||||||
|
store.storeInboundGroupSessions(toSave)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.util.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
@ -52,6 +53,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val cryptoCoroutineScope: CoroutineScope) {
|
private val cryptoCoroutineScope: CoroutineScope) {
|
||||||
|
|
||||||
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
// list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
|
// list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
|
||||||
// we received in the current sync.
|
// we received in the current sync.
|
||||||
private val receivedGossipingRequests = ArrayList<IncomingShareRequestCommon>()
|
private val receivedGossipingRequests = ArrayList<IncomingShareRequestCommon>()
|
||||||
@ -64,6 +66,10 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||||||
receivedGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests())
|
receivedGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun close() {
|
||||||
|
executor.shutdownNow()
|
||||||
|
}
|
||||||
|
|
||||||
// Recently verified devices (map of deviceId and timestamp)
|
// Recently verified devices (map of deviceId and timestamp)
|
||||||
private val recentlyVerifiedDevices = HashMap<String, Long>()
|
private val recentlyVerifiedDevices = HashMap<String, Long>()
|
||||||
|
|
||||||
@ -99,7 +105,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||||||
fun onGossipingRequestEvent(event: Event) {
|
fun onGossipingRequestEvent(event: Event) {
|
||||||
Timber.v("## CRYPTO | GOSSIP onGossipingRequestEvent type ${event.type} from user ${event.senderId}")
|
Timber.v("## CRYPTO | GOSSIP onGossipingRequestEvent type ${event.type} from user ${event.senderId}")
|
||||||
val roomKeyShare = event.getClearContent().toModel<GossipingDefaultContent>()
|
val roomKeyShare = event.getClearContent().toModel<GossipingDefaultContent>()
|
||||||
val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
|
// val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
|
||||||
when (roomKeyShare?.action) {
|
when (roomKeyShare?.action) {
|
||||||
GossipingToDeviceObject.ACTION_SHARE_REQUEST -> {
|
GossipingToDeviceObject.ACTION_SHARE_REQUEST -> {
|
||||||
if (event.getClearType() == EventType.REQUEST_SECRET) {
|
if (event.getClearType() == EventType.REQUEST_SECRET) {
|
||||||
@ -108,8 +114,8 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||||||
// ignore, it was sent by me as *
|
// ignore, it was sent by me as *
|
||||||
Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
|
Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
|
||||||
} else {
|
} else {
|
||||||
// save in DB
|
// // save in DB
|
||||||
cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
|
// cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
|
||||||
receivedGossipingRequests.add(it)
|
receivedGossipingRequests.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +125,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||||||
// ignore, it was sent by me as *
|
// ignore, it was sent by me as *
|
||||||
Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
|
Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
|
||||||
} else {
|
} else {
|
||||||
cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
|
// cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
|
||||||
receivedGossipingRequests.add(it)
|
receivedGossipingRequests.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,13 +150,8 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||||||
fun processReceivedGossipingRequests() {
|
fun processReceivedGossipingRequests() {
|
||||||
val roomKeyRequestsToProcess = receivedGossipingRequests.toList()
|
val roomKeyRequestsToProcess = receivedGossipingRequests.toList()
|
||||||
receivedGossipingRequests.clear()
|
receivedGossipingRequests.clear()
|
||||||
for (request in roomKeyRequestsToProcess) {
|
|
||||||
if (request is IncomingRoomKeyRequest) {
|
Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : ${roomKeyRequestsToProcess.size} request to process")
|
||||||
processIncomingRoomKeyRequest(request)
|
|
||||||
} else if (request is IncomingSecretShareRequest) {
|
|
||||||
processIncomingSecretShareRequest(request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var receivedRequestCancellations: List<IncomingRequestCancellation>? = null
|
var receivedRequestCancellations: List<IncomingRequestCancellation>? = null
|
||||||
|
|
||||||
@ -161,6 +162,16 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ->
|
receivedRequestCancellations?.forEach { request ->
|
||||||
Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : m.room_key_request cancellation $request")
|
Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : m.room_key_request cancellation $request")
|
||||||
// we should probably only notify the app of cancellations we told it
|
// we should probably only notify the app of cancellations we told it
|
||||||
@ -183,6 +194,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun processIncomingRoomKeyRequest(request: IncomingRoomKeyRequest) {
|
private fun processIncomingRoomKeyRequest(request: IncomingRoomKeyRequest) {
|
||||||
val userId = request.userId ?: return
|
val userId = request.userId ?: return
|
||||||
|
@ -44,7 +44,9 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* The store where crypto data is saved.
|
* 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.
|
* @return the Curve25519 key for the account.
|
||||||
@ -657,7 +659,7 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
timelineSet.add(messageIndexKey)
|
timelineSet.add(messageIndexKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
store.storeInboundGroupSessions(listOf(session))
|
inboundGroupSessionStore.storeInBoundGroupSession(session)
|
||||||
val payload = try {
|
val payload = try {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||||
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
|
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)
|
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) {
|
if (session != null) {
|
||||||
// Check that the room id matches the original one for the session. This stops
|
// Check that the room id matches the original one for the session. This stops
|
||||||
|
@ -88,7 +88,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
|
|||||||
* @param requestBody requestBody
|
* @param requestBody requestBody
|
||||||
*/
|
*/
|
||||||
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) {
|
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
|
||||||
cancelRoomKeyRequest(requestBody, false)
|
cancelRoomKeyRequest(requestBody, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
|
|||||||
* @param requestBody requestBody
|
* @param requestBody requestBody
|
||||||
*/
|
*/
|
||||||
fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) {
|
fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
|
||||||
cancelRoomKeyRequest(requestBody, true)
|
cancelRoomKeyRequest(requestBody, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,13 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.algorithms.megolm
|
package org.matrix.android.sdk.internal.crypto.algorithms.megolm
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
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.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
@ -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.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
import org.matrix.android.sdk.internal.task.configureWith
|
||||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
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 org.matrix.android.sdk.internal.util.convertToUTF8
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@ -54,7 +58,9 @@ internal class MXMegolmEncryption(
|
|||||||
private val sendToDeviceTask: SendToDeviceTask,
|
private val sendToDeviceTask: SendToDeviceTask,
|
||||||
private val messageEncrypter: MessageEncrypter,
|
private val messageEncrypter: MessageEncrypter,
|
||||||
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
|
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
|
||||||
private val taskExecutor: TaskExecutor
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
private val cryptoCoroutineScope: CoroutineScope
|
||||||
) : IMXEncrypting {
|
) : IMXEncrypting {
|
||||||
|
|
||||||
// OutboundSessionInfo. Null if we haven't yet started setting one up. Note
|
// OutboundSessionInfo. Null if we haven't yet started setting one up. Note
|
||||||
@ -84,6 +90,8 @@ internal class MXMegolmEncryption(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyWithheldForSession(devices: MXUsersDevicesMap<WithHeldCode>, outboundSession: MXOutboundSessionInfo) {
|
private fun notifyWithheldForSession(devices: MXUsersDevicesMap<WithHeldCode>, outboundSession: MXOutboundSessionInfo) {
|
||||||
|
// offload to computation thread
|
||||||
|
cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
|
||||||
mutableListOf<Pair<UserDevice, WithHeldCode>>().apply {
|
mutableListOf<Pair<UserDevice, WithHeldCode>>().apply {
|
||||||
devices.forEach { userId, deviceId, withheldCode ->
|
devices.forEach { userId, deviceId, withheldCode ->
|
||||||
this.add(UserDevice(userId, deviceId) to withheldCode)
|
this.add(UserDevice(userId, deviceId) to withheldCode)
|
||||||
@ -95,6 +103,7 @@ internal class MXMegolmEncryption(
|
|||||||
notifyKeyWithHeld(targets, outboundSession.sessionId, olmDevice.deviceCurve25519Key, code)
|
notifyKeyWithHeld(targets, outboundSession.sessionId, olmDevice.deviceCurve25519Key, code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun discardSessionKey() {
|
override fun discardSessionKey() {
|
||||||
outboundSession = null
|
outboundSession = null
|
||||||
@ -247,6 +256,15 @@ internal class MXMegolmEncryption(
|
|||||||
for ((userId, devicesToShareWith) in devicesByUser) {
|
for ((userId, devicesToShareWith) in devicesByUser) {
|
||||||
for ((deviceId) in devicesToShareWith) {
|
for ((deviceId) in devicesToShareWith) {
|
||||||
session.sharedWithHelper.markedSessionAsShared(userId, deviceId, chainIndex)
|
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"
|
||||||
|
}
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.algorithms.megolm
|
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.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||||
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
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.store.IMXCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
|
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class MXMegolmEncryptionFactory @Inject constructor(
|
internal class MXMegolmEncryptionFactory @Inject constructor(
|
||||||
@ -38,7 +40,9 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
|
|||||||
private val sendToDeviceTask: SendToDeviceTask,
|
private val sendToDeviceTask: SendToDeviceTask,
|
||||||
private val messageEncrypter: MessageEncrypter,
|
private val messageEncrypter: MessageEncrypter,
|
||||||
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
|
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 {
|
fun create(roomId: String): MXMegolmEncryption {
|
||||||
return MXMegolmEncryption(
|
return MXMegolmEncryption(
|
||||||
@ -52,7 +56,9 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
|
|||||||
sendToDeviceTask,
|
sendToDeviceTask,
|
||||||
messageEncrypter,
|
messageEncrypter,
|
||||||
warnOnUnknownDevicesRepository,
|
warnOnUnknownDevicesRepository,
|
||||||
taskExecutor
|
taskExecutor,
|
||||||
|
coroutineDispatchers,
|
||||||
|
cryptoCoroutineScope
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
package org.matrix.android.sdk.internal.crypto.crosssigning
|
package org.matrix.android.sdk.internal.crypto.crosssigning
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
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.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
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 org.matrix.android.sdk.internal.util.withoutPrefix
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
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.OlmPkSigning
|
||||||
import org.matrix.olm.OlmUtility
|
import org.matrix.olm.OlmUtility
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class DefaultCrossSigningService @Inject constructor(
|
internal class DefaultCrossSigningService @Inject constructor(
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
|
@SessionId private val sessionId: String,
|
||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
private val deviceListManager: DeviceListManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
private val initializeCrossSigningTask: InitializeCrossSigningTask,
|
private val initializeCrossSigningTask: InitializeCrossSigningTask,
|
||||||
@ -55,7 +62,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val cryptoCoroutineScope: CoroutineScope,
|
private val cryptoCoroutineScope: CoroutineScope,
|
||||||
private val eventBus: EventBus) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
|
private val workManagerProvider: WorkManagerProvider) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
|
||||||
|
|
||||||
private var olmUtility: OlmUtility? = null
|
private var olmUtility: OlmUtility? = null
|
||||||
|
|
||||||
@ -360,6 +367,12 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
// First let's get my user key
|
// First let's get my user key
|
||||||
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId)
|
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId)
|
||||||
|
|
||||||
|
checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId))
|
||||||
|
|
||||||
|
return UserTrustResult.Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult {
|
||||||
val myUserKey = myCrossSigningInfo?.userKey()
|
val myUserKey = myCrossSigningInfo?.userKey()
|
||||||
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||||
|
|
||||||
@ -368,15 +381,15 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Let's get the other user master key
|
// Let's get the other user master key
|
||||||
val otherMasterKey = cryptoStore.getCrossSigningInfo(otherUserId)?.masterKey()
|
val otherMasterKey = otherInfo?.masterKey()
|
||||||
?: return UserTrustResult.UnknownCrossSignatureInfo(otherUserId)
|
?: return UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "")
|
||||||
|
|
||||||
val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures
|
val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures
|
||||||
?.get(userId) // Signatures made by me
|
?.get(userId) // Signatures made by me
|
||||||
?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}")
|
?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}")
|
||||||
|
|
||||||
if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) {
|
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)
|
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)
|
// and that MSK is trusted (i know the private key, or is signed by a trusted device)
|
||||||
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId)
|
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId)
|
||||||
|
|
||||||
|
return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(userId))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List<CryptoDeviceInfo>?): 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()
|
val myMasterKey = myCrossSigningInfo?.masterKey()
|
||||||
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||||
|
|
||||||
@ -423,7 +445,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
// Maybe it's signed by a locally trusted device?
|
// Maybe it's signed by a locally trusted device?
|
||||||
myMasterKey.signatures?.get(userId)?.forEach { (key, value) ->
|
myMasterKey.signatures?.get(userId)?.forEach { (key, value) ->
|
||||||
val potentialDeviceId = key.withoutPrefix("ed25519:")
|
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) {
|
if (potentialDevice != null && potentialDevice.isVerified) {
|
||||||
// Check signature validity?
|
// Check signature validity?
|
||||||
try {
|
try {
|
||||||
@ -561,6 +583,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
||||||
checkSelfTrust()
|
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))
|
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 {
|
private fun legacyFallbackTrust(locallyTrusted: Boolean?, crossSignTrustFail: DeviceTrustResult): DeviceTrustResult {
|
||||||
return if (locallyTrusted == true) {
|
return if (locallyTrusted == true) {
|
||||||
DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true))
|
DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true))
|
||||||
@ -675,36 +748,18 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onUsersDeviceUpdate(userIds: List<String>) {
|
override fun onUsersDeviceUpdate(userIds: List<String>) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
Timber.d("## CrossSigning - onUsersDeviceUpdate for $userIds")
|
||||||
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${userIds.size} users")
|
val workerParams = UpdateTrustWorker.Params(sessionId = sessionId, updatedUserIds = userIds)
|
||||||
userIds.forEach { otherUserId ->
|
val workerData = WorkerParamsFactory.toData(workerParams)
|
||||||
checkUserTrust(otherUserId).let {
|
|
||||||
Timber.v("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
|
|
||||||
setUserKeysAsTrusted(otherUserId, it.isVerified())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now check device trust
|
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<UpdateTrustWorker>()
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
.setInputData(workerData)
|
||||||
userIds.forEach { otherUserId ->
|
.setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS)
|
||||||
// TODO if my keys have changes, i should recheck all devices of all users?
|
.build()
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherUserId == userId) {
|
workManagerProvider.workManager
|
||||||
// It's me, i should check if a newly trusted device is signing my master key
|
.beginUniqueWork("TRUST_UPDATE_QUEUE", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
||||||
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
|
.enqueue()
|
||||||
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eventBus.post(CryptoToSessionUserTrustChange(userIds))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
||||||
|
@ -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<String>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class CryptoToSessionUserTrustChange(
|
|
||||||
val userIds: List<String>
|
|
||||||
)
|
|
@ -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<Realm>()
|
|
||||||
|
|
||||||
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<String>) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,322 @@
|
|||||||
|
/*
|
||||||
|
* 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<UpdateTrustWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class Params(
|
||||||
|
override val sessionId: String,
|
||||||
|
override val lastFailureMessage: String? = null,
|
||||||
|
val updatedUserIds: List<String>
|
||||||
|
) : 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<UserEntity>()
|
||||||
|
.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<UserEntity>()
|
||||||
|
.equalTo(UserEntityFields.USER_ID, it)
|
||||||
|
.findFirst()
|
||||||
|
?.devices
|
||||||
|
|
||||||
|
val trustMap = devicesEntities?.map { 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
|
||||||
|
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<String>, 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<UserEntity>()
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
}
|
@ -234,7 +234,10 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
this.callback = object : MatrixCallback<KeysVersion> {
|
this.callback = object : MatrixCallback<KeysVersion> {
|
||||||
override fun onSuccess(data: KeysVersion) {
|
override fun onSuccess(data: KeysVersion) {
|
||||||
// Reset backup markers.
|
// Reset backup markers.
|
||||||
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
|
// move tx out of UI thread
|
||||||
cryptoStore.resetBackupMarkers()
|
cryptoStore.resetBackupMarkers()
|
||||||
|
}
|
||||||
|
|
||||||
val keyBackupVersion = KeysVersionResult(
|
val keyBackupVersion = KeysVersionResult(
|
||||||
algorithm = createKeysBackupVersionBody.algorithm,
|
algorithm = createKeysBackupVersionBody.algorithm,
|
||||||
@ -596,7 +599,9 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
val importResult = awaitCallback<ImportRoomKeysResult> {
|
val importResult = awaitCallback<ImportRoomKeysResult> {
|
||||||
restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it)
|
restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it)
|
||||||
}
|
}
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version)
|
cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version)
|
||||||
|
}
|
||||||
Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}")
|
Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}")
|
||||||
} else {
|
} else {
|
||||||
Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}")
|
Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}")
|
||||||
@ -685,7 +690,7 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
// Get backed up keys from the homeserver
|
// Get backed up keys from the homeserver
|
||||||
val data = getKeys(sessionId, roomId, keysVersionResult.version!!)
|
val data = getKeys(sessionId, roomId, keysVersionResult.version!!)
|
||||||
|
|
||||||
withContext(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.computation) {
|
||||||
val sessionsData = ArrayList<MegolmSessionData>()
|
val sessionsData = ArrayList<MegolmSessionData>()
|
||||||
// Restore that data
|
// Restore that data
|
||||||
var sessionsFromHsCount = 0
|
var sessionsFromHsCount = 0
|
||||||
@ -1123,7 +1128,9 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
|
|
||||||
if (retrievedMegolmBackupAuthData != null) {
|
if (retrievedMegolmBackupAuthData != null) {
|
||||||
keysBackupVersion = keysVersionResult
|
keysBackupVersion = keysVersionResult
|
||||||
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
cryptoStore.setKeyBackupVersion(keysVersionResult.version)
|
cryptoStore.setKeyBackupVersion(keysVersionResult.version)
|
||||||
|
}
|
||||||
|
|
||||||
onServerDataRetrieved(keysVersionResult.count, keysVersionResult.hash)
|
onServerDataRetrieved(keysVersionResult.count, keysVersionResult.hash)
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package org.matrix.android.sdk.internal.crypto.store
|
package org.matrix.android.sdk.internal.crypto.store
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
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.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
@ -126,7 +127,10 @@ internal interface IMXCryptoStore {
|
|||||||
fun getPendingIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
fun getPendingIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||||
|
|
||||||
fun getPendingIncomingGossipingRequests(): List<IncomingShareRequestCommon>
|
fun getPendingIncomingGossipingRequests(): List<IncomingShareRequestCommon>
|
||||||
|
|
||||||
fun storeIncomingGossipingRequest(request: IncomingShareRequestCommon, ageLocalTS: Long?)
|
fun storeIncomingGossipingRequest(request: IncomingShareRequestCommon, ageLocalTS: Long?)
|
||||||
|
|
||||||
|
fun storeIncomingGossipingRequests(requests: List<IncomingShareRequestCommon>)
|
||||||
// fun getPendingIncomingSecretShareRequests(): List<IncomingSecretShareRequest>
|
// fun getPendingIncomingSecretShareRequests(): List<IncomingSecretShareRequest>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -364,6 +368,7 @@ internal interface IMXCryptoStore {
|
|||||||
fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map<String, List<String>>): OutgoingSecretRequest?
|
fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map<String, List<String>>): OutgoingSecretRequest?
|
||||||
|
|
||||||
fun saveGossipingEvent(event: Event)
|
fun saveGossipingEvent(event: Event)
|
||||||
|
fun saveGossipingEvents(events: List<Event>)
|
||||||
|
|
||||||
fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) {
|
fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) {
|
||||||
updateGossipingRequestState(
|
updateGossipingRequestState(
|
||||||
@ -441,10 +446,13 @@ internal interface IMXCryptoStore {
|
|||||||
// Dev tools
|
// Dev tools
|
||||||
|
|
||||||
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
||||||
|
fun getOutgoingRoomKeyRequestsPaged(): LiveData<PagedList<OutgoingRoomKeyRequest>>
|
||||||
fun getOutgoingSecretKeyRequests(): List<OutgoingSecretRequest>
|
fun getOutgoingSecretKeyRequests(): List<OutgoingSecretRequest>
|
||||||
fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest?
|
fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest?
|
||||||
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||||
fun getGossipingEventsTrail(): List<Event>
|
fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>>
|
||||||
|
fun getGossipingEventsTrail(): LiveData<PagedList<Event>>
|
||||||
|
fun getGossipingEvents(): List<Event>
|
||||||
|
|
||||||
fun setDeviceKeysUploaded(uploaded: Boolean)
|
fun setDeviceKeysUploaded(uploaded: Boolean)
|
||||||
fun getDeviceKeysUploaded(): Boolean
|
fun getDeviceKeysUploaded(): Boolean
|
||||||
|
@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.crypto.store.db
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
|
import androidx.paging.LivePagedListBuilder
|
||||||
|
import androidx.paging.PagedList
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
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.DeviceInfoEntity
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
|
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.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.IncomingGossipingRequestEntity
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
|
import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
|
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
|
||||||
@ -998,7 +1001,50 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getGossipingEventsTrail(): List<Event> {
|
override fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>> {
|
||||||
|
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||||
|
realm.where<IncomingGossipingRequestEntity>()
|
||||||
|
.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<PagedList<Event>> {
|
||||||
|
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||||
|
realm.where<GossipingEventEntity>().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<Event> {
|
||||||
return monarchy.fetchAllCopiedSync { realm ->
|
return monarchy.fetchAllCopiedSync { realm ->
|
||||||
realm.where<GossipingEventEntity>()
|
realm.where<GossipingEventEntity>()
|
||||||
}.map {
|
}.map {
|
||||||
@ -1066,7 +1112,28 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun saveGossipingEvents(events: List<Event>) {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
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) {
|
override fun saveGossipingEvent(event: Event) {
|
||||||
|
monarchy.writeAsync { realm ->
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now
|
val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now
|
||||||
val entity = GossipingEventEntity(
|
val entity = GossipingEventEntity(
|
||||||
@ -1076,14 +1143,12 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
content = ContentMapper.map(event.content)
|
content = ContentMapper.map(event.content)
|
||||||
).apply {
|
).apply {
|
||||||
sendState = SendState.SYNCED
|
sendState = SendState.SYNCED
|
||||||
decryptionResultJson = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult)
|
decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult)
|
||||||
decryptionErrorCode = event.mCryptoError?.name
|
decryptionErrorCode = event.mCryptoError?.name
|
||||||
}
|
}
|
||||||
doRealmTransaction(realmConfiguration) { realm ->
|
|
||||||
realm.insertOrUpdate(entity)
|
realm.insertOrUpdate(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// override fun getOutgoingRoomKeyRequestByState(states: Set<ShareRequestState>): OutgoingRoomKeyRequest? {
|
// override fun getOutgoingRoomKeyRequestByState(states: Set<ShareRequestState>): OutgoingRoomKeyRequest? {
|
||||||
// val statesIndex = states.map { it.ordinal }.toTypedArray()
|
// val statesIndex = states.map { it.ordinal }.toTypedArray()
|
||||||
// return doRealmQueryAndCopy(realmConfiguration) { realm ->
|
// return doRealmQueryAndCopy(realmConfiguration) { realm ->
|
||||||
@ -1284,6 +1349,28 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun storeIncomingGossipingRequests(requests: List<IncomingShareRequestCommon>) {
|
||||||
|
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<IncomingSecretShareRequest> {
|
// override fun getPendingIncomingSecretShareRequests(): List<IncomingSecretShareRequest> {
|
||||||
// return doRealmQueryAndCopyList(realmConfiguration) {
|
// return doRealmQueryAndCopyList(realmConfiguration) {
|
||||||
// it.where<GossipingEventEntity>()
|
// it.where<GossipingEventEntity>()
|
||||||
@ -1417,6 +1504,27 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getOutgoingRoomKeyRequestsPaged(): LiveData<PagedList<OutgoingRoomKeyRequest>> {
|
||||||
|
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? {
|
override fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? {
|
||||||
return doWithRealm(realmConfiguration) { realm ->
|
return doWithRealm(realmConfiguration) { realm ->
|
||||||
val crossSigningInfo = realm.where(CrossSigningInfoEntity::class.java)
|
val crossSigningInfo = realm.where(CrossSigningInfoEntity::class.java)
|
||||||
|
@ -33,14 +33,13 @@ internal interface EncryptEventTask : Task<EncryptEventTask.Params, Event> {
|
|||||||
data class Params(val roomId: String,
|
data class Params(val roomId: String,
|
||||||
val event: Event,
|
val event: Event,
|
||||||
/**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/
|
/**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/
|
||||||
val keepKeys: List<String>? = null,
|
val keepKeys: List<String>? = null
|
||||||
val crypto: CryptoService
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultEncryptEventTask @Inject constructor(
|
internal class DefaultEncryptEventTask @Inject constructor(
|
||||||
// private val crypto: CryptoService
|
private val localEchoRepository: LocalEchoRepository,
|
||||||
private val localEchoRepository: LocalEchoRepository
|
private val cryptoService: CryptoService
|
||||||
) : EncryptEventTask {
|
) : EncryptEventTask {
|
||||||
override suspend fun execute(params: EncryptEventTask.Params): Event {
|
override suspend fun execute(params: EncryptEventTask.Params): Event {
|
||||||
// don't want to wait for any query
|
// don't want to wait for any query
|
||||||
@ -60,7 +59,7 @@ internal class DefaultEncryptEventTask @Inject constructor(
|
|||||||
// try {
|
// try {
|
||||||
// let it throws
|
// let it throws
|
||||||
awaitCallback<MXEncryptEventContentResult> {
|
awaitCallback<MXEncryptEventContentResult> {
|
||||||
params.crypto.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it)
|
cryptoService.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it)
|
||||||
}.let { result ->
|
}.let { result ->
|
||||||
val modifiedContent = HashMap(result.eventContent)
|
val modifiedContent = HashMap(result.eventContent)
|
||||||
params.keepKeys?.forEach { toKeep ->
|
params.keepKeys?.forEach { toKeep ->
|
||||||
@ -81,7 +80,7 @@ internal class DefaultEncryptEventTask @Inject constructor(
|
|||||||
).toContent(),
|
).toContent(),
|
||||||
forwardingCurve25519KeyChain = emptyList(),
|
forwardingCurve25519KeyChain = emptyList(),
|
||||||
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
||||||
claimedEd25519Key = params.crypto.getMyDevice().fingerprint()
|
claimedEd25519Key = cryptoService.getMyDevice().fingerprint()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
package org.matrix.android.sdk.internal.crypto.tasks
|
package org.matrix.android.sdk.internal.crypto.tasks
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
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.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
@ -29,8 +28,7 @@ import javax.inject.Inject
|
|||||||
internal interface SendEventTask : Task<SendEventTask.Params, String> {
|
internal interface SendEventTask : Task<SendEventTask.Params, String> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val event: Event,
|
val event: Event,
|
||||||
val encrypt: Boolean,
|
val encrypt: Boolean
|
||||||
val cryptoService: CryptoService?
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,8 +66,7 @@ internal class DefaultSendEventTask @Inject constructor(
|
|||||||
return encryptEventTask.execute(EncryptEventTask.Params(
|
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||||
params.event.roomId ?: "",
|
params.event.roomId ?: "",
|
||||||
params.event,
|
params.event,
|
||||||
listOf("m.relates_to"),
|
listOf("m.relates_to")
|
||||||
params.cryptoService!!
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
return params.event
|
return params.event
|
||||||
|
@ -15,21 +15,20 @@
|
|||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.crypto.tasks
|
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.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
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.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
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.LocalEchoRepository
|
||||||
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.greenrobot.eventbus.EventBus
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, String> {
|
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, String> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val event: Event,
|
val event: Event
|
||||||
val cryptoService: CryptoService?
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +36,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||||||
private val localEchoRepository: LocalEchoRepository,
|
private val localEchoRepository: LocalEchoRepository,
|
||||||
private val encryptEventTask: DefaultEncryptEventTask,
|
private val encryptEventTask: DefaultEncryptEventTask,
|
||||||
private val roomAPI: RoomAPI,
|
private val roomAPI: RoomAPI,
|
||||||
|
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||||
private val eventBus: EventBus) : SendVerificationMessageTask {
|
private val eventBus: EventBus) : SendVerificationMessageTask {
|
||||||
|
|
||||||
override suspend fun execute(params: SendVerificationMessageTask.Params): String {
|
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 {
|
private suspend fun handleEncryption(params: SendVerificationMessageTask.Params): Event {
|
||||||
if (params.cryptoService?.isRoomEncrypted(params.event.roomId ?: "") == true) {
|
if (cryptoSessionInfoProvider.isRoomEncrypted(params.event.roomId ?: "")) {
|
||||||
try {
|
try {
|
||||||
return encryptEventTask.execute(EncryptEventTask.Params(
|
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||||
params.event.roomId ?: "",
|
params.event.roomId ?: "",
|
||||||
params.event,
|
params.event,
|
||||||
listOf("m.relates_to"),
|
listOf("m.relates_to")
|
||||||
params.cryptoService
|
|
||||||
))
|
))
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
// We said it's ok to send verification request in clear
|
// We said it's ok to send verification request in clear
|
||||||
|
@ -20,7 +20,6 @@ import android.os.Handler
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
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.CrossSigningService
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
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
|
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())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
// Cannot be injected in constructor as it creates a dependency cycle
|
|
||||||
lateinit var cryptoService: CryptoService
|
|
||||||
|
|
||||||
// map [sender : [transaction]]
|
// map [sender : [transaction]]
|
||||||
private val txMap = HashMap<String, HashMap<String, DefaultVerificationTransaction>>()
|
private val txMap = HashMap<String, HashMap<String, DefaultVerificationTransaction>>()
|
||||||
|
|
||||||
@ -129,7 +125,8 @@ internal class DefaultVerificationService @Inject constructor(
|
|||||||
|
|
||||||
// Event received from the sync
|
// Event received from the sync
|
||||||
fun onToDeviceEvent(event: Event) {
|
fun onToDeviceEvent(event: Event) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
Timber.d("## SAS onToDeviceEvent ${event.getClearType()}")
|
||||||
|
cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) {
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.KEY_VERIFICATION_START -> {
|
EventType.KEY_VERIFICATION_START -> {
|
||||||
onStartRequestReceived(event)
|
onStartRequestReceived(event)
|
||||||
@ -163,7 +160,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onRoomEvent(event: Event) {
|
fun onRoomEvent(event: Event) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) {
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.KEY_VERIFICATION_START -> {
|
EventType.KEY_VERIFICATION_START -> {
|
||||||
onRoomStartRequestReceived(event)
|
onRoomStartRequestReceived(event)
|
||||||
@ -240,6 +237,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun dispatchRequestAdded(tx: PendingVerificationRequest) {
|
private fun dispatchRequestAdded(tx: PendingVerificationRequest) {
|
||||||
|
Timber.v("## SAS dispatchRequestAdded txId:${tx.transactionId}")
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
try {
|
try {
|
||||||
@ -303,11 +301,14 @@ internal class DefaultVerificationService @Inject constructor(
|
|||||||
// We don't want to block here
|
// We don't want to block here
|
||||||
val otherDeviceId = validRequestInfo.fromDevice
|
val otherDeviceId = validRequestInfo.fromDevice
|
||||||
|
|
||||||
|
Timber.v("## SAS onRequestReceived from $senderId and device $otherDeviceId, txId:${validRequestInfo.transactionId}")
|
||||||
|
|
||||||
cryptoCoroutineScope.launch {
|
cryptoCoroutineScope.launch {
|
||||||
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
|
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
|
||||||
Timber.e("## Verification device $otherDeviceId is not known")
|
Timber.e("## Verification device $otherDeviceId is not known")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Timber.v("## SAS onRequestReceived .. checkKeysAreDownloaded launched")
|
||||||
|
|
||||||
// Remember this request
|
// Remember this request
|
||||||
val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
|
val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
|
||||||
@ -1203,7 +1204,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||||||
// TODO refactor this with the DM one
|
// TODO refactor this with the DM one
|
||||||
Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices")
|
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 requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() }
|
||||||
|
|
||||||
val transport = verificationTransportToDeviceFactory.createTransport(null)
|
val transport = verificationTransportToDeviceFactory.createTransport(null)
|
||||||
|
@ -20,7 +20,6 @@ import androidx.work.Data
|
|||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
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.crypto.tasks.SendVerificationMessageTask
|
||||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
|
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 sendVerificationMessageTask: SendVerificationMessageTask
|
||||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||||
@Inject lateinit var cryptoService: CryptoService
|
|
||||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||||
|
|
||||||
override fun injectWith(injector: SessionComponent) {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
@ -70,8 +68,7 @@ internal class SendVerificationMessageWorker(context: Context,
|
|||||||
return try {
|
return try {
|
||||||
val resultEventId = sendVerificationMessageTask.execute(
|
val resultEventId = sendVerificationMessageTask.execute(
|
||||||
SendVerificationMessageTask.Params(
|
SendVerificationMessageTask.Params(
|
||||||
event = localEvent,
|
event = localEvent
|
||||||
cryptoService = cryptoService
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
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.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
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.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class VerificationMessageProcessor @Inject constructor(
|
internal class VerificationMessageProcessor @Inject constructor(
|
||||||
private val cryptoService: CryptoService,
|
private val eventDecryptor: EventDecryptor,
|
||||||
private val verificationService: DefaultVerificationService,
|
private val verificationService: DefaultVerificationService,
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
@DeviceId private val deviceId: 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?
|
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
||||||
// for now decrypt sync
|
// for now decrypt sync
|
||||||
try {
|
try {
|
||||||
val result = cryptoService.decryptEvent(event, "")
|
val result = eventDecryptor.decryptEvent(event, "")
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
payload = result.clearEvent,
|
payload = result.clearEvent,
|
||||||
senderKey = result.senderCurve25519Key,
|
senderKey = result.senderCurve25519Key,
|
||||||
|
@ -17,10 +17,6 @@
|
|||||||
package org.matrix.android.sdk.internal.database
|
package org.matrix.android.sdk.internal.database
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
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.mapper.asDomain
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertEntity
|
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.RealmConfiguration
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
|
internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
|
||||||
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>,
|
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>,
|
||||||
private val cryptoService: CryptoService)
|
private val eventDecryptor: EventDecryptor)
|
||||||
: RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
|
: RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
|
||||||
|
|
||||||
override val query = Monarchy.Query<EventInsertEntity> {
|
override val query = Monarchy.Query<EventInsertEntity> {
|
||||||
@ -74,7 +71,7 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
|
|||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
val domainEvent = event.asDomain()
|
val domainEvent = event.asDomain()
|
||||||
decryptIfNeeded(domainEvent)
|
// decryptIfNeeded(domainEvent)
|
||||||
processors.filter {
|
processors.filter {
|
||||||
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
|
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
|
||||||
}.forEach {
|
}.forEach {
|
||||||
@ -89,22 +86,22 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptIfNeeded(event: Event) {
|
// private fun decryptIfNeeded(event: Event) {
|
||||||
if (event.isEncrypted() && event.mxDecryptionResult == null) {
|
// if (event.isEncrypted() && event.mxDecryptionResult == null) {
|
||||||
try {
|
// try {
|
||||||
val result = cryptoService.decryptEvent(event, event.roomId ?: "")
|
// val result = eventDecryptor.decryptEvent(event, event.roomId ?: "")
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
// event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
payload = result.clearEvent,
|
// payload = result.clearEvent,
|
||||||
senderKey = result.senderCurve25519Key,
|
// senderKey = result.senderCurve25519Key,
|
||||||
keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
|
// keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
// forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||||
)
|
// )
|
||||||
} catch (e: MXCryptoError) {
|
// } catch (e: MXCryptoError) {
|
||||||
Timber.v("Failed to decrypt event")
|
// Timber.v("Failed to decrypt event")
|
||||||
// TODO -> we should keep track of this and retry, or some processing will never be handled
|
// // TODO -> we should keep track of this and retry, or some processing will never be handled
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
|
private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
|
||||||
return processors.any {
|
return processors.any {
|
||||||
|
@ -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.di.WorkManagerProvider
|
||||||
import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
|
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.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.SyncTokenStore
|
||||||
import org.matrix.android.sdk.internal.session.sync.job.SyncThread
|
import org.matrix.android.sdk.internal.session.sync.job.SyncThread
|
||||||
import org.matrix.android.sdk.internal.session.sync.job.SyncWorker
|
import org.matrix.android.sdk.internal.session.sync.job.SyncWorker
|
||||||
@ -115,7 +114,6 @@ internal class DefaultSession @Inject constructor(
|
|||||||
private val accountDataService: Lazy<AccountDataService>,
|
private val accountDataService: Lazy<AccountDataService>,
|
||||||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||||
private val accountService: Lazy<AccountService>,
|
private val accountService: Lazy<AccountService>,
|
||||||
private val timelineEventDecryptor: TimelineEventDecryptor,
|
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val defaultIdentityService: DefaultIdentityService,
|
private val defaultIdentityService: DefaultIdentityService,
|
||||||
private val integrationManagerService: IntegrationManagerService,
|
private val integrationManagerService: IntegrationManagerService,
|
||||||
@ -162,7 +160,6 @@ internal class DefaultSession @Inject constructor(
|
|||||||
lifecycleObservers.forEach { it.onStart() }
|
lifecycleObservers.forEach { it.onStart() }
|
||||||
}
|
}
|
||||||
eventBus.register(this)
|
eventBus.register(this)
|
||||||
timelineEventDecryptor.start()
|
|
||||||
eventSenderProcessor.start()
|
eventSenderProcessor.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +197,7 @@ internal class DefaultSession @Inject constructor(
|
|||||||
override fun close() {
|
override fun close() {
|
||||||
assert(isOpen)
|
assert(isOpen)
|
||||||
stopSync()
|
stopSync()
|
||||||
timelineEventDecryptor.destroy()
|
// timelineEventDecryptor.destroy()
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
lifecycleObservers.forEach { it.onStop() }
|
lifecycleObservers.forEach { it.onStop() }
|
||||||
}
|
}
|
||||||
|
@ -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.CryptoModule
|
||||||
import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker
|
import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker
|
||||||
import org.matrix.android.sdk.internal.crypto.SendGossipWorker
|
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.crypto.verification.SendVerificationMessageWorker
|
||||||
import org.matrix.android.sdk.internal.di.MatrixComponent
|
import org.matrix.android.sdk.internal.di.MatrixComponent
|
||||||
import org.matrix.android.sdk.internal.di.SessionAssistedInjectModule
|
import org.matrix.android.sdk.internal.di.SessionAssistedInjectModule
|
||||||
@ -128,6 +129,8 @@ internal interface SessionComponent {
|
|||||||
|
|
||||||
fun inject(worker: SendGossipWorker)
|
fun inject(worker: SendGossipWorker)
|
||||||
|
|
||||||
|
fun inject(worker: UpdateTrustWorker)
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(
|
fun create(
|
||||||
|
@ -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.SecureStorageService
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
||||||
import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
|
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.secrets.DefaultSharedSecretStorageService
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
|
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
|
||||||
@ -333,10 +332,6 @@ internal abstract class SessionModule {
|
|||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindWidgetUrlFormatter(formatter: DefaultWidgetURLFormatter): SessionLifecycleObserver
|
abstract fun bindWidgetUrlFormatter(formatter: DefaultWidgetURLFormatter): SessionLifecycleObserver
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoSet
|
|
||||||
abstract fun bindShieldTrustUpdated(updater: ShieldTrustUpdater): SessionLifecycleObserver
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindIdentityService(service: DefaultIdentityService): SessionLifecycleObserver
|
abstract fun bindIdentityService(service: DefaultIdentityService): SessionLifecycleObserver
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.session.room
|
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.AggregatedAnnotation
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
@ -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.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
||||||
import io.realm.Realm
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -78,9 +77,8 @@ private fun VerificationState?.toState(newState: VerificationState): Verificatio
|
|||||||
return newState
|
return newState
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String,
|
internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String)
|
||||||
private val cryptoService: CryptoService
|
: EventInsertLiveProcessor {
|
||||||
) : EventInsertLiveProcessor {
|
|
||||||
|
|
||||||
private val allowedTypes = listOf(
|
private val allowedTypes = listOf(
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
|
@ -21,7 +21,6 @@ import com.squareup.inject.assisted.Assisted
|
|||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
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.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
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.NoOpCancellable
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
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.TimelineEventMapper
|
||||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
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.EventAnnotationsSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
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.LocalEchoEventFactory
|
||||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
@ -47,11 +46,9 @@ import timber.log.Timber
|
|||||||
|
|
||||||
internal class DefaultRelationService @AssistedInject constructor(
|
internal class DefaultRelationService @AssistedInject constructor(
|
||||||
@Assisted private val roomId: String,
|
@Assisted private val roomId: String,
|
||||||
@SessionId private val sessionId: String,
|
|
||||||
// private val timeLineSendEventWorkCommon: TimelineSendEventWorkCommon,
|
|
||||||
private val eventSenderProcessor: EventSenderProcessor,
|
private val eventSenderProcessor: EventSenderProcessor,
|
||||||
private val eventFactory: LocalEchoEventFactory,
|
private val eventFactory: LocalEchoEventFactory,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||||
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
||||||
private val fetchEditHistoryTask: FetchEditHistoryTask,
|
private val fetchEditHistoryTask: FetchEditHistoryTask,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
@ -122,7 +119,7 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||||||
val event = eventFactory
|
val event = eventFactory
|
||||||
.createReplaceTextEvent(roomId, targetEventId, newBodyText, newBodyAutoMarkdown, msgType, compatibilityBodyText)
|
.createReplaceTextEvent(roomId, targetEventId, newBodyText, newBodyAutoMarkdown, msgType, compatibilityBodyText)
|
||||||
.also { saveLocalEcho(it) }
|
.also { saveLocalEcho(it) }
|
||||||
return eventSenderProcessor.postEvent(event, cryptoService.isRoomEncrypted(roomId))
|
return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun editReply(replyToEdit: TimelineEvent,
|
override fun editReply(replyToEdit: TimelineEvent,
|
||||||
@ -139,11 +136,11 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||||||
compatibilityBodyText
|
compatibilityBodyText
|
||||||
)
|
)
|
||||||
.also { saveLocalEcho(it) }
|
.also { saveLocalEcho(it) }
|
||||||
return eventSenderProcessor.postEvent(event, cryptoService.isRoomEncrypted(roomId))
|
return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
|
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
|
||||||
val params = FetchEditHistoryTask.Params(roomId, cryptoService.isRoomEncrypted(roomId), eventId)
|
val params = FetchEditHistoryTask.Params(roomId, cryptoSessionInfoProvider.isRoomEncrypted(roomId), eventId)
|
||||||
fetchEditHistoryTask
|
fetchEditHistoryTask
|
||||||
.configureWith(params) {
|
.configureWith(params) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
@ -156,7 +153,7 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||||||
?.also { saveLocalEcho(it) }
|
?.also { saveLocalEcho(it) }
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
return eventSenderProcessor.postEvent(event, cryptoService.isRoomEncrypted(roomId))
|
return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? {
|
override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? {
|
||||||
|
@ -25,7 +25,6 @@ import com.squareup.inject.assisted.Assisted
|
|||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
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.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
|
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
|
||||||
import org.matrix.android.sdk.api.session.events.model.isTextMessage
|
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.CancelableBag
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||||
|
import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
|
||||||
import org.matrix.android.sdk.internal.di.SessionId
|
import org.matrix.android.sdk.internal.di.SessionId
|
||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
||||||
@ -64,7 +64,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
private val workManagerProvider: WorkManagerProvider,
|
private val workManagerProvider: WorkManagerProvider,
|
||||||
@SessionId private val sessionId: String,
|
@SessionId private val sessionId: String,
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val localEchoRepository: LocalEchoRepository,
|
private val localEchoRepository: LocalEchoRepository,
|
||||||
private val eventSenderProcessor: EventSenderProcessor,
|
private val eventSenderProcessor: EventSenderProcessor,
|
||||||
@ -251,7 +251,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
private fun internalSendMedia(allLocalEchoes: List<Event>, attachment: ContentAttachmentData, compressBeforeSending: Boolean): Cancelable {
|
private fun internalSendMedia(allLocalEchoes: List<Event>, attachment: ContentAttachmentData, compressBeforeSending: Boolean): Cancelable {
|
||||||
val cancelableBag = CancelableBag()
|
val cancelableBag = CancelableBag()
|
||||||
|
|
||||||
allLocalEchoes.groupBy { cryptoService.isRoomEncrypted(it.roomId!!) }
|
allLocalEchoes.groupBy { cryptoSessionInfoProvider.isRoomEncrypted(it.roomId!!) }
|
||||||
.apply {
|
.apply {
|
||||||
keys.forEach { isRoomEncrypted ->
|
keys.forEach { isRoomEncrypted ->
|
||||||
// Should never be empty
|
// Should never be empty
|
||||||
@ -282,7 +282,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun sendEvent(event: Event): Cancelable {
|
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) {
|
private fun createLocalEcho(event: Event) {
|
||||||
|
@ -87,7 +87,7 @@ internal class SendEventWorker(context: Context,
|
|||||||
|
|
||||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Send event ${params.eventId}")
|
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Send event ${params.eventId}")
|
||||||
return try {
|
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()
|
Result.success()
|
||||||
} catch (exception: Throwable) {
|
} catch (exception: Throwable) {
|
||||||
if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) {
|
if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) {
|
||||||
|
@ -38,7 +38,7 @@ internal class SendEventQueuedTask(
|
|||||||
override fun toString() = "[SendEventRunnableTask ${event.eventId}]"
|
override fun toString() = "[SendEventRunnableTask ${event.eventId}]"
|
||||||
|
|
||||||
override suspend fun execute() {
|
override suspend fun execute() {
|
||||||
sendEventTask.execute(SendEventTask.Params(event, encrypt, cryptoService))
|
sendEventTask.execute(SendEventTask.Params(event, encrypt))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTaskFailed() {
|
override fun onTaskFailed() {
|
||||||
|
@ -16,10 +16,8 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.summary
|
package org.matrix.android.sdk.internal.session.room.summary
|
||||||
|
|
||||||
import dagger.Lazy
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
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.RoomNameContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
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.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.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.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.CurrentStateEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
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.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.RoomAvatarResolver
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
|
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.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.RoomSyncSummary
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
|
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -56,8 +55,8 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
private val roomDisplayNameResolver: RoomDisplayNameResolver,
|
private val roomDisplayNameResolver: RoomDisplayNameResolver,
|
||||||
private val roomAvatarResolver: RoomAvatarResolver,
|
private val roomAvatarResolver: RoomAvatarResolver,
|
||||||
private val timelineEventDecryptor: Lazy<TimelineEventDecryptor>,
|
private val eventDecryptor: EventDecryptor,
|
||||||
private val eventBus: EventBus) {
|
private val crossSigningService: DefaultCrossSigningService) {
|
||||||
|
|
||||||
fun update(realm: Realm,
|
fun update(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
@ -126,9 +125,14 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||||||
}
|
}
|
||||||
roomSummaryEntity.updateHasFailedSending()
|
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}")
|
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) {
|
if (updateMembers) {
|
||||||
@ -142,7 +146,8 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||||||
roomSummaryEntity.otherMemberIds.clear()
|
roomSummaryEntity.otherMemberIds.clear()
|
||||||
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
|
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
|
||||||
if (roomSummaryEntity.isEncrypted) {
|
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.updateHasFailedSending()
|
||||||
roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -630,7 +630,7 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
if (timelineEvent.isEncrypted()
|
if (timelineEvent.isEncrypted()
|
||||||
&& timelineEvent.root.mxDecryptionResult == null) {
|
&& 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
|
val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size
|
||||||
|
@ -15,24 +15,22 @@
|
|||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.session.room.timeline
|
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.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
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.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.internal.crypto.NewSessionListener
|
import org.matrix.android.sdk.internal.crypto.NewSessionListener
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
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.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
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 timber.log.Timber
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
|
||||||
internal class TimelineEventDecryptor @Inject constructor(
|
internal class TimelineEventDecryptor @Inject constructor(
|
||||||
@SessionDatabase
|
@SessionDatabase
|
||||||
private val realmConfiguration: RealmConfiguration,
|
private val realmConfiguration: RealmConfiguration,
|
||||||
@ -83,14 +81,14 @@ internal class TimelineEventDecryptor @Inject constructor(
|
|||||||
synchronized(unknownSessionsFailure) {
|
synchronized(unknownSessionsFailure) {
|
||||||
for (requests in unknownSessionsFailure.values) {
|
for (requests in unknownSessionsFailure.values) {
|
||||||
if (request in requests) {
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
synchronized(existingRequests) {
|
synchronized(existingRequests) {
|
||||||
if (!existingRequests.add(request)) {
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,25 +99,29 @@ internal class TimelineEventDecryptor @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processDecryptRequest(request: DecryptionRequest, realm: Realm) = realm.executeTransaction {
|
private fun processDecryptRequest(request: DecryptionRequest, realm: Realm) {
|
||||||
val eventId = request.eventId
|
val event = request.event
|
||||||
val timelineId = request.timelineId
|
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 {
|
try {
|
||||||
val result = cryptoService.decryptEvent(event, timelineId)
|
val result = cryptoService.decryptEvent(request.event, timelineId)
|
||||||
Timber.v("Successfully decrypted event $eventId")
|
Timber.v("Successfully decrypted event ${event.eventId}")
|
||||||
eventEntity.setDecryptionResult(result)
|
realm.executeTransaction {
|
||||||
|
EventEntity.where(it, eventId = event.eventId ?: "")
|
||||||
|
.findFirst()
|
||||||
|
?.setDecryptionResult(result)
|
||||||
|
}
|
||||||
} catch (e: MXCryptoError) {
|
} 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*/) {
|
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
|
// Keep track of unknown sessions to automatically try to decrypt on new session
|
||||||
eventEntity.decryptionErrorCode = e.errorType.name
|
realm.executeTransaction {
|
||||||
eventEntity.decryptionErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
|
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<EncryptedEventContent>()?.let { content ->
|
event.content?.toModel<EncryptedEventContent>()?.let { content ->
|
||||||
content.sessionId?.let { sessionId ->
|
content.sessionId?.let { sessionId ->
|
||||||
synchronized(unknownSessionsFailure) {
|
synchronized(unknownSessionsFailure) {
|
||||||
@ -130,7 +132,7 @@ internal class TimelineEventDecryptor @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Timber.e("Failed to decrypt event $eventId, ${t.localizedMessage}")
|
Timber.e("Failed to decrypt event ${event.eventId}, ${t.localizedMessage}")
|
||||||
} finally {
|
} finally {
|
||||||
synchronized(existingRequests) {
|
synchronized(existingRequests) {
|
||||||
existingRequests.remove(request)
|
existingRequests.remove(request)
|
||||||
@ -139,7 +141,7 @@ internal class TimelineEventDecryptor @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class DecryptionRequest(
|
data class DecryptionRequest(
|
||||||
val eventId: String,
|
val event: Event,
|
||||||
val timelineId: String
|
val timelineId: String
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.sync
|
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.R
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
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.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.summary.RoomSummaryUpdater
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline
|
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.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.room.typing.TypingEventContent
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync
|
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.RoomSync
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncAccountData
|
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.RoomSyncEphemeral
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse
|
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 timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -76,8 +75,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||||||
private val roomTypingUsersHandler: RoomTypingUsersHandler,
|
private val roomTypingUsersHandler: RoomTypingUsersHandler,
|
||||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
private val eventBus: EventBus,
|
private val eventBus: EventBus) {
|
||||||
private val timelineEventDecryptor: TimelineEventDecryptor) {
|
|
||||||
|
|
||||||
sealed class HandlingStrategy {
|
sealed class HandlingStrategy {
|
||||||
data class JOINED(val data: Map<String, RoomSync>) : HandlingStrategy()
|
data class JOINED(val data: Map<String, RoomSync>) : HandlingStrategy()
|
||||||
|
@ -54,7 +54,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||||||
private val networkConnectivityChecker: NetworkConnectivityChecker,
|
private val networkConnectivityChecker: NetworkConnectivityChecker,
|
||||||
private val backgroundDetectionObserver: BackgroundDetectionObserver,
|
private val backgroundDetectionObserver: BackgroundDetectionObserver,
|
||||||
private val activeCallHandler: ActiveCallHandler
|
private val activeCallHandler: ActiveCallHandler
|
||||||
) : Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
|
) : Thread("SyncThread"), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
|
||||||
|
|
||||||
private var state: SyncState = SyncState.Idle
|
private var state: SyncState = SyncState.Idle
|
||||||
private var liveState = MutableLiveData<SyncState>(state)
|
private var liveState = MutableLiveData<SyncState>(state)
|
||||||
|
@ -91,7 +91,6 @@ ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_resources.txt
|
|||||||
./vector/src/main/res/color \
|
./vector/src/main/res/color \
|
||||||
./vector/src/main/res/layout \
|
./vector/src/main/res/layout \
|
||||||
./vector/src/main/res/values \
|
./vector/src/main/res/values \
|
||||||
./vector/src/main/res/values-v21 \
|
|
||||||
./vector/src/main/res/xml
|
./vector/src/main/res/xml
|
||||||
|
|
||||||
resultForbiddenStringInResource=$?
|
resultForbiddenStringInResource=$?
|
||||||
|
@ -21,6 +21,7 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
@ -205,6 +206,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun displayError(failure: Throwable) {
|
private fun displayError(failure: Throwable) {
|
||||||
|
if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle(R.string.dialog_title_error)
|
.setTitle(R.string.dialog_title_error)
|
||||||
.setMessage(errorFormatter.toHumanReadable(failure))
|
.setMessage(errorFormatter.toHumanReadable(failure))
|
||||||
@ -213,6 +215,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity {
|
|||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun startNextActivityAndFinish() {
|
private fun startNextActivityAndFinish() {
|
||||||
val intent = when {
|
val intent = when {
|
||||||
|
@ -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.VerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -116,6 +117,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
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)
|
// For incoming request we should prompt (if not in activity where this request apply)
|
||||||
if (pr.isIncoming) {
|
if (pr.isIncoming) {
|
||||||
val name = session?.getUser(pr.otherUserId)?.displayName
|
val name = session?.getUser(pr.otherUserId)?.displayName
|
||||||
|
@ -69,6 +69,9 @@ class BugReportActivity : VectorBaseActivity() {
|
|||||||
bug_report_button_include_crash_logs.isChecked = false
|
bug_report_button_include_crash_logs.isChecked = false
|
||||||
bug_report_button_include_crash_logs.isVisible = 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
|
// Keep the screenshot
|
||||||
} else {
|
} else {
|
||||||
supportActionBar?.setTitle(R.string.title_activity_bug_report)
|
supportActionBar?.setTitle(R.string.title_activity_bug_report)
|
||||||
@ -121,6 +124,7 @@ class BugReportActivity : VectorBaseActivity() {
|
|||||||
forSuggestion,
|
forSuggestion,
|
||||||
bug_report_button_include_logs.isChecked,
|
bug_report_button_include_logs.isChecked,
|
||||||
bug_report_button_include_crash_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_button_include_screenshot.isChecked,
|
||||||
bug_report_edit_text.text.toString(),
|
bug_report_edit_text.text.toString(),
|
||||||
object : BugReporter.IMXBugReportListener {
|
object : BugReporter.IMXBugReportListener {
|
||||||
|
@ -33,6 +33,7 @@ import im.vector.app.core.extensions.getAllChildFragments
|
|||||||
import im.vector.app.core.extensions.toOnOff
|
import im.vector.app.core.extensions.toOnOff
|
||||||
import im.vector.app.features.settings.VectorLocale
|
import im.vector.app.features.settings.VectorLocale
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
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.settings.locale.SystemLocaleProvider
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
import im.vector.app.features.version.VersionProvider
|
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_FILENAME = "logcat.log"
|
||||||
private const val LOG_CAT_SCREENSHOT_FILENAME = "screenshot.png"
|
private const val LOG_CAT_SCREENSHOT_FILENAME = "screenshot.png"
|
||||||
private const val CRASH_FILENAME = "crash.log"
|
private const val CRASH_FILENAME = "crash.log"
|
||||||
|
private const val KEY_REQUESTS_FILENAME = "keyRequests.log"
|
||||||
|
|
||||||
private const val BUFFER_SIZE = 1024 * 1024 * 50
|
private const val BUFFER_SIZE = 1024 * 1024 * 50
|
||||||
}
|
}
|
||||||
@ -143,6 +145,7 @@ class BugReporter @Inject constructor(
|
|||||||
* @param forSuggestion true to send a suggestion
|
* @param forSuggestion true to send a suggestion
|
||||||
* @param withDevicesLogs true to include the device log
|
* @param withDevicesLogs true to include the device log
|
||||||
* @param withCrashLogs true to include the crash logs
|
* @param withCrashLogs true to include the crash logs
|
||||||
|
* @param withKeyRequestHistory true to include the crash logs
|
||||||
* @param withScreenshot true to include the screenshot
|
* @param withScreenshot true to include the screenshot
|
||||||
* @param theBugDescription the bug description
|
* @param theBugDescription the bug description
|
||||||
* @param listener the listener
|
* @param listener the listener
|
||||||
@ -152,6 +155,7 @@ class BugReporter @Inject constructor(
|
|||||||
forSuggestion: Boolean,
|
forSuggestion: Boolean,
|
||||||
withDevicesLogs: Boolean,
|
withDevicesLogs: Boolean,
|
||||||
withCrashLogs: Boolean,
|
withCrashLogs: Boolean,
|
||||||
|
withKeyRequestHistory: Boolean,
|
||||||
withScreenshot: Boolean,
|
withScreenshot: Boolean,
|
||||||
theBugDescription: String,
|
theBugDescription: String,
|
||||||
listener: IMXBugReportListener?) {
|
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 deviceId = "undefined"
|
||||||
var userId = "undefined"
|
var userId = "undefined"
|
||||||
var olmVersion = "undefined"
|
var olmVersion = "undefined"
|
||||||
|
@ -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<GossipingEventsPaperTrailState>() {
|
|
||||||
|
|
||||||
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<RoomKeyShareRequest>()
|
|
||||||
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<OlmEventContent>()
|
|
||||||
val content = event.getClearContent().toModel<ForwardedRoomKeyContent>()
|
|
||||||
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<SecretSendEventContent>()
|
|
||||||
|
|
||||||
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<SecretShareRequest>()
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -33,16 +33,19 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class GossipingEventsPaperTrailFragment @Inject constructor(
|
class GossipingEventsPaperTrailFragment @Inject constructor(
|
||||||
val viewModelFactory: GossipingEventsPaperTrailViewModel.Factory,
|
val viewModelFactory: GossipingEventsPaperTrailViewModel.Factory,
|
||||||
private val epoxyController: GossipingEventsEpoxyController,
|
private val epoxyController: GossipingTrailPagedEpoxyController,
|
||||||
private val colorProvider: ColorProvider
|
private val colorProvider: ColorProvider
|
||||||
) : VectorBaseFragment(), GossipingEventsEpoxyController.InteractionListener {
|
) : VectorBaseFragment(), GossipingTrailPagedEpoxyController.InteractionListener {
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||||
|
|
||||||
private val viewModel: GossipingEventsPaperTrailViewModel by fragmentViewModel(GossipingEventsPaperTrailViewModel::class)
|
private val viewModel: GossipingEventsPaperTrailViewModel by fragmentViewModel(GossipingEventsPaperTrailViewModel::class)
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
epoxyController.setData(state)
|
state.events.invoke()?.let {
|
||||||
|
epoxyController.submitList(it)
|
||||||
|
}
|
||||||
|
Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -16,13 +16,12 @@
|
|||||||
|
|
||||||
package im.vector.app.features.settings.devtools
|
package im.vector.app.features.settings.devtools
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.paging.PagedList
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
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.EmptyAction
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
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.Session
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.rx.asObservable
|
||||||
|
|
||||||
data class GossipingEventsPaperTrailState(
|
data class GossipingEventsPaperTrailState(
|
||||||
val events: Async<List<Event>> = Uninitialized
|
val events: Async<PagedList<Event>> = Uninitialized
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
||||||
class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted initialState: GossipingEventsPaperTrailState,
|
class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted initialState: GossipingEventsPaperTrailState,
|
||||||
@ -50,13 +49,9 @@ class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted i
|
|||||||
setState {
|
setState {
|
||||||
copy(events = Loading())
|
copy(events = Loading())
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
session.cryptoService().getGossipingEventsTrail().asObservable()
|
||||||
session.cryptoService().getGossipingEventsTrail().let {
|
.execute {
|
||||||
val sorted = it.sortedByDescending { it.ageLocalTs }
|
copy(events = it)
|
||||||
setState {
|
|
||||||
copy(events = Success(sorted))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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<Event>): 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<RoomKeyShareRequest>()
|
||||||
|
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<OlmEventContent>()
|
||||||
|
val content = it.getClearContent().toModel<ForwardedRoomKeyContent>()
|
||||||
|
|
||||||
|
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<SecretSendEventContent>()
|
||||||
|
append("requestId:${content?.requestId} From Device:${it.mxDecryptionResult?.payload?.get("sender_device")}")
|
||||||
|
}
|
||||||
|
EventType.REQUEST_SECRET -> {
|
||||||
|
val content = it.getClearContent().toModel<SecretShareRequest>()
|
||||||
|
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) }
|
||||||
|
?: "?"
|
||||||
|
}
|
||||||
|
}
|
@ -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<Event>(
|
||||||
|
// 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<RoomKeyShareRequest>()
|
||||||
|
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<OlmEventContent>()
|
||||||
|
val content = event.getClearContent().toModel<ForwardedRoomKeyContent>()
|
||||||
|
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<SecretSendEventContent>()
|
||||||
|
|
||||||
|
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<SecretShareRequest>()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,14 +24,12 @@ import im.vector.app.R
|
|||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.ColorProvider
|
|
||||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class IncomingKeyRequestListFragment @Inject constructor(
|
class IncomingKeyRequestListFragment @Inject constructor(
|
||||||
val viewModelFactory: KeyRequestListViewModel.Factory,
|
val viewModelFactory: KeyRequestListViewModel.Factory,
|
||||||
private val epoxyController: KeyRequestEpoxyController,
|
private val epoxyController: IncomingKeyRequestPagedController
|
||||||
private val colorProvider: ColorProvider
|
|
||||||
) : VectorBaseFragment() {
|
) : VectorBaseFragment() {
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||||
@ -39,8 +37,10 @@ class IncomingKeyRequestListFragment @Inject constructor(
|
|||||||
private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class)
|
private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class)
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
epoxyController.outgoing = false
|
state.incomingRequests.invoke()?.let {
|
||||||
epoxyController.setData(state)
|
epoxyController.submitList(it)
|
||||||
|
}
|
||||||
|
Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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<IncomingRoomKeyRequest>(
|
||||||
|
// 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: ") {
|
||||||
|
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}"
|
||||||
|
span("\nstate: ") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+roomKeyRequest.state.name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<KeyRequestListViewState>() {
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,11 +17,11 @@
|
|||||||
package im.vector.app.features.settings.devtools
|
package im.vector.app.features.settings.devtools
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.paging.PagedList
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
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.api.session.Session
|
||||||
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest
|
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest
|
||||||
|
import org.matrix.android.sdk.rx.asObservable
|
||||||
|
|
||||||
data class KeyRequestListViewState(
|
data class KeyRequestListViewState(
|
||||||
val incomingRequests: Async<List<IncomingRoomKeyRequest>> = Uninitialized,
|
val incomingRequests: Async<PagedList<IncomingRoomKeyRequest>> = Uninitialized,
|
||||||
val outgoingRoomKeyRequests: Async<List<OutgoingRoomKeyRequest>> = Uninitialized
|
val outgoingRoomKeyRequests: Async<PagedList<OutgoingRoomKeyRequest>> = Uninitialized
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
||||||
class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState: KeyRequestListViewState,
|
class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState: KeyRequestListViewState,
|
||||||
@ -49,19 +50,15 @@ class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState
|
|||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
session.cryptoService().getOutgoingRoomKeyRequests().let {
|
session.cryptoService().getOutgoingRoomKeyRequestsPaged().asObservable()
|
||||||
setState {
|
.execute {
|
||||||
copy(
|
copy(outgoingRoomKeyRequests = it)
|
||||||
outgoingRoomKeyRequests = Success(it)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
session.cryptoService().getIncomingRoomKeyRequests().let {
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
incomingRequests = Success(it)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.cryptoService().getIncomingRoomKeyRequestsPaged()
|
||||||
|
.asObservable()
|
||||||
|
.execute {
|
||||||
|
copy(incomingRequests = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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.extensions.exhaustive
|
||||||
|
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 org.matrix.android.sdk.api.session.Session
|
||||||
|
|
||||||
|
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<Unit> = Uninitialized
|
||||||
|
) : MvRxState
|
||||||
|
|
||||||
|
class KeyRequestViewModel @AssistedInject constructor(
|
||||||
|
@Assisted initialState: KeyRequestViewState,
|
||||||
|
private val session: Session)
|
||||||
|
: VectorViewModel<KeyRequestViewState, KeyRequestAction, KeyRequestEvents>(initialState) {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: KeyRequestViewState): KeyRequestViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<KeyRequestViewModel, KeyRequestViewState> {
|
||||||
|
|
||||||
|
@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 -> exportAudit(action)
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = GossipingEventsSerializer().serialize(eventList)
|
||||||
|
setState {
|
||||||
|
copy(exporting = Success(Unit))
|
||||||
|
}
|
||||||
|
_viewEvents.post(KeyRequestEvents.SaveAudit(action.uri, raw))
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(exporting = Fail(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,20 +16,31 @@
|
|||||||
|
|
||||||
package im.vector.app.features.settings.devtools
|
package im.vector.app.features.settings.devtools
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE
|
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 com.google.android.material.tabs.TabLayoutMediator
|
||||||
import im.vector.app.R
|
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.VectorBaseActivity
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.utils.selectTxtFileToWrite
|
||||||
import kotlinx.android.synthetic.main.fragment_devtool_keyrequests.*
|
import kotlinx.android.synthetic.main.fragment_devtool_keyrequests.*
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import javax.inject.Inject
|
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
|
override fun getLayoutResId(): Int = R.layout.fragment_devtool_keyrequests
|
||||||
|
|
||||||
@ -40,6 +51,10 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() {
|
|||||||
|
|
||||||
private var mPagerAdapter: KeyReqPagerAdapter? = null
|
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() {
|
private val pageAdapterListener = object : ViewPager2.OnPageChangeCallback() {
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
@ -53,6 +68,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() {
|
override fun onDestroy() {
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
@ -77,6 +99,17 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
|
viewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
is KeyRequestEvents.SaveAudit -> {
|
||||||
|
tryOrNull {
|
||||||
|
requireContext().contentResolver?.openOutputStream(it.uri)
|
||||||
|
?.use { os -> os.write(it.raw.toByteArray()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
@ -85,6 +118,28 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() {
|
|||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == R.id.audit_export) {
|
||||||
|
selectTxtFileToWrite(
|
||||||
|
activity = requireActivity(),
|
||||||
|
activityResultLauncher = epxortAuditForActivityResult,
|
||||||
|
defaultFileName = "audit-export_${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) {
|
private inner class KeyReqPagerAdapter(fa: Fragment) : FragmentStateAdapter(fa) {
|
||||||
override fun getItemCount(): Int = 3
|
override fun getItemCount(): Int = 3
|
||||||
|
|
||||||
|
@ -24,21 +24,19 @@ import im.vector.app.R
|
|||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.ColorProvider
|
|
||||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class OutgoingKeyRequestListFragment @Inject constructor(
|
class OutgoingKeyRequestListFragment @Inject constructor(
|
||||||
val viewModelFactory: KeyRequestListViewModel.Factory,
|
val viewModelFactory: KeyRequestListViewModel.Factory,
|
||||||
private val epoxyController: KeyRequestEpoxyController,
|
private val epoxyController: OutgoingKeyRequestPagedController
|
||||||
private val colorProvider: ColorProvider
|
|
||||||
) : VectorBaseFragment() {
|
) : VectorBaseFragment() {
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||||
private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class)
|
private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class)
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
epoxyController.setData(state)
|
epoxyController.submitList(state.outgoingRoomKeyRequests.invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -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<OutgoingRoomKeyRequest>(
|
||||||
|
// 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: ") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+"${roomKeyRequest.roomId}"
|
||||||
|
|
||||||
|
span("\nsessionId: ") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+"${roomKeyRequest.sessionId}"
|
||||||
|
span("\nstate: ") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+roomKeyRequest.state.name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -125,6 +125,15 @@
|
|||||||
android:checked="true"
|
android:checked="true"
|
||||||
android:text="@string/send_bug_report_include_crash_logs" />
|
android:text="@string/send_bug_report_include_crash_logs" />
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/bug_report_button_include_key_share_history"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:checked="false"
|
||||||
|
android:text="@string/send_bug_report_include_key_share_history" />
|
||||||
|
|
||||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
android:id="@+id/bug_report_button_include_screenshot"
|
android:id="@+id/bug_report_button_include_screenshot"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -11,12 +11,24 @@
|
|||||||
android:id="@+id/devToolKeyRequestTabs"
|
android:id="@+id/devToolKeyRequestTabs"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:tabMode="scrollable" />
|
app:tabMode="scrollable" />
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/devToolKeyRequestTabs"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:id="@+id/devToolKeyRequestPager"
|
android:id="@+id/devToolKeyRequestPager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp" />
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/exportWaitingView"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
10
vector/src/main/res/menu/menu_audit.xml
Normal file
10
vector/src/main/res/menu/menu_audit.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/audit_export"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_material_save"
|
||||||
|
android:title="@string/settings_export_trail" />
|
||||||
|
|
||||||
|
</menu>
|
@ -192,6 +192,7 @@
|
|||||||
|
|
||||||
<string name="send_bug_report_include_logs">Send logs</string>
|
<string name="send_bug_report_include_logs">Send logs</string>
|
||||||
<string name="send_bug_report_include_crash_logs">Send crash logs</string>
|
<string name="send_bug_report_include_crash_logs">Send crash logs</string>
|
||||||
|
<string name="send_bug_report_include_key_share_history">Send key share requests history</string>
|
||||||
<string name="send_bug_report_include_screenshot">Send screenshot</string>
|
<string name="send_bug_report_include_screenshot">Send screenshot</string>
|
||||||
<string name="send_bug_report">Report bug</string>
|
<string name="send_bug_report">Report bug</string>
|
||||||
<string name="send_bug_report_description">Please describe the bug. What did you do? What did you expect to happen? What actually happened?</string>
|
<string name="send_bug_report_description">Please describe the bug. What did you do? What did you expect to happen? What actually happened?</string>
|
||||||
@ -2305,6 +2306,7 @@
|
|||||||
<string name="login_default_session_public_name">Element Android</string>
|
<string name="login_default_session_public_name">Element Android</string>
|
||||||
|
|
||||||
<string name="settings_key_requests">Key Requests</string>
|
<string name="settings_key_requests">Key Requests</string>
|
||||||
|
<string name="settings_export_trail">Export Audit</string>
|
||||||
|
|
||||||
<string name="e2e_use_keybackup">Unlock encrypted messages history</string>
|
<string name="e2e_use_keybackup">Unlock encrypted messages history</string>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user