Merge pull request #262 from vector-im/feature/update_timeline_when_new_key

Update timeline on new session
This commit is contained in:
Benoit Marty 2019-07-02 16:51:07 +02:00 committed by GitHub
commit f21f4dbe91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 129 additions and 41 deletions

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
@ -107,4 +108,8 @@ interface CryptoService {


fun clearCryptoCache(callback: MatrixCallback<Unit>) fun clearCryptoCache(callback: MatrixCallback<Unit>)


fun addNewSessionListener(newSessionListener: NewSessionListener)

fun removeSessionListener(listener: NewSessionListener)

} }

View File

@ -34,6 +34,7 @@ data class TimelineEvent(
val isUniqueDisplayName: Boolean, val isUniqueDisplayName: Boolean,
val senderAvatar: String?, val senderAvatar: String?,
val sendState: SendState, val sendState: SendState,
val hasClearEventFlag: Boolean = false,
val annotations: EventAnnotationsSummary? = null val annotations: EventAnnotationsSummary? = null
) { ) {



View File

@ -1063,6 +1063,13 @@ internal class CryptoManager @Inject constructor(
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }


override fun addNewSessionListener(newSessionListener: NewSessionListener) {
roomDecryptorProvider.addNewSessionListener(newSessionListener)
}

override fun removeSessionListener(listener: NewSessionListener) {
roomDecryptorProvider.removeSessionListener(listener)
}
/* ========================================================================================== /* ==========================================================================================
* DEBUG INFO * DEBUG INFO
* ========================================================================================== */ * ========================================================================================== */

View File

@ -0,0 +1,21 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto


interface NewSessionListener {
fun onNewSession(roomId: String?, senderKey: String, sessionId: String)
}

View File

@ -34,6 +34,16 @@ internal class RoomDecryptorProvider @Inject constructor(
// A map from algorithm to MXDecrypting instance, for each room // A map from algorithm to MXDecrypting instance, for each room
private val roomDecryptors: MutableMap<String /* room id */, MutableMap<String /* algorithm */, IMXDecrypting>> = HashMap() private val roomDecryptors: MutableMap<String /* room id */, MutableMap<String /* algorithm */, IMXDecrypting>> = HashMap()


private val newSessionListeners = ArrayList<NewSessionListener>()

fun addNewSessionListener(listener: NewSessionListener) {
if (!newSessionListeners.contains(listener)) newSessionListeners.add(listener)
}

fun removeSessionListener(listener: NewSessionListener) {
newSessionListeners.remove(listener)
}

/** /**
* Get a decryptor for a given room and algorithm. * Get a decryptor for a given room and algorithm.
* If we already have a decryptor for the given room and algorithm, return * If we already have a decryptor for the given room and algorithm, return
@ -64,7 +74,19 @@ internal class RoomDecryptorProvider @Inject constructor(
val decryptingClass = MXCryptoAlgorithms.hasDecryptorClassForAlgorithm(algorithm) val decryptingClass = MXCryptoAlgorithms.hasDecryptorClassForAlgorithm(algorithm)
if (decryptingClass) { if (decryptingClass) {
val alg = when (algorithm) { val alg = when (algorithm) {
MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create() MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create().apply {
this.newSessionListener = object : NewSessionListener {
override fun onNewSession(rid: String?, senderKey: String, sessionId: String) {
newSessionListeners.forEach {
try {
it.onNewSession(roomId, senderKey, sessionId)
} catch (e: Throwable) {

}
}
}
}
}
else -> olmDecryptionFactory.create() else -> olmDecryptionFactory.create()
} }
if (roomId != null && !TextUtils.isEmpty(roomId)) { if (roomId != null && !TextUtils.isEmpty(roomId)) {

View File

@ -55,6 +55,8 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
private val coroutineDispatchers: MatrixCoroutineDispatchers) private val coroutineDispatchers: MatrixCoroutineDispatchers)
: IMXDecrypting { : IMXDecrypting {


var newSessionListener: NewSessionListener? = null

/** /**
* Events which we couldn't decrypt due to unknown sessions / indexes: map from * Events which we couldn't decrypt due to unknown sessions / indexes: map from
* senderKey|sessionId to timelines to list of MatrixEvents. * senderKey|sessionId to timelines to list of MatrixEvents.
@ -203,7 +205,8 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
Timber.v("## onRoomKeyEvent(), forward adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId Timber.v("## onRoomKeyEvent(), forward adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event); + " sessionKey " + roomKeyContent.sessionKey) // from " + event);
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>() ?: return val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()
?: return
forwardingCurve25519KeyChain = if (forwardedRoomKeyContent.forwardingCurve25519KeyChain == null) { forwardingCurve25519KeyChain = if (forwardedRoomKeyContent.forwardingCurve25519KeyChain == null) {
ArrayList() ArrayList()
} else { } else {
@ -275,43 +278,8 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
* @param sessionId the session id * @param sessionId the session id
*/ */
override fun onNewSession(senderKey: String, sessionId: String) { override fun onNewSession(senderKey: String, sessionId: String) {
//TODO see how to handle this
Timber.v("ON NEW SESSION $sessionId - $senderKey") Timber.v("ON NEW SESSION $sessionId - $senderKey")
/*val k = "$senderKey|$sessionId" newSessionListener?.onNewSession(null, senderKey, sessionId)

val pending = pendingEvents[k]

if (null != pending) {
// Have another go at decrypting events sent with this session.
pendingEvents.remove(k)

val timelineIds = pending.keys

for (timelineId in timelineIds) {
val events = pending[timelineId]

for (event in events!!) {
var result: MXEventDecryptionResult? = null

try {
result = decryptEvent(event, timelineId)
} catch (e: MXDecryptionException) {
Timber.e(e, "## onNewSession() : Still can't decrypt " + event.eventId + ". Error")
event.setCryptoError(e.cryptoError)
}

if (null != result) {
val fResut = result
CryptoAsyncHelper.getUiHandler().post {
event.setClearData(fResut)
//mSession!!.onEventDecrypted(event)
}
Timber.v("## onNewSession() : successful re-decryption of " + event.eventId)
}
}
}
}
*/
} }


override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean { override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {

View File

@ -18,6 +18,7 @@
package im.vector.matrix.android.internal.crypto.store package im.vector.matrix.android.internal.crypto.store


import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
@ -376,4 +377,8 @@ internal interface IMXCryptoStore {
* @return an IncomingRoomKeyRequest if it exists, else null * @return an IncomingRoomKeyRequest if it exists, else null
*/ */
fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest? fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest?

fun addNewSessionListener(listener: NewSessionListener)

fun removeSessionListener(listener: NewSessionListener)
} }

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.store.db
import android.text.TextUtils import android.text.TextUtils
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
@ -57,6 +58,17 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
// Cache for InboundGroupSession, to release them properly // Cache for InboundGroupSession, to release them properly
private val inboundGroupSessionToRelease = HashMap<String, OlmInboundGroupSessionWrapper>() private val inboundGroupSessionToRelease = HashMap<String, OlmInboundGroupSessionWrapper>()



private val newSessionListeners = ArrayList<NewSessionListener>()

override fun addNewSessionListener(listener: NewSessionListener) {
if (!newSessionListeners.contains(listener)) newSessionListeners.add(listener)
}

override fun removeSessionListener(listener: NewSessionListener) {
newSessionListeners.remove(listener)
}

/* ========================================================================================== /* ==========================================================================================
* Other data * Other data
* ========================================================================================== */ * ========================================================================================== */
@ -718,4 +730,5 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
} }
.toMutableList() .toMutableList()
} }

} }

View File

@ -64,7 +64,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,


fun create(roomId: String): Room { fun create(roomId: String): Room {
val timelineEventFactory = InMemoryTimelineEventFactory(SenderRoomMemberExtractor(), EventRelationExtractor(), cryptoService) val timelineEventFactory = InMemoryTimelineEventFactory(SenderRoomMemberExtractor(), EventRelationExtractor(), cryptoService)
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask) val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, cryptoService, paginationTask)
val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy) val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy)
val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask) val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask) val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)

View File

@ -20,11 +20,15 @@ import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
import android.os.Looper import android.os.Looper
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.CancelableBag import im.vector.matrix.android.api.util.CancelableBag
import im.vector.matrix.android.api.util.addTo import im.vector.matrix.android.api.util.addTo
import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.internal.database.query.findIncludingEvent import im.vector.matrix.android.internal.database.query.findIncludingEvent
@ -56,6 +60,7 @@ internal class DefaultTimeline(
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val timelineEventFactory: CacheableTimelineEventFactory, private val timelineEventFactory: CacheableTimelineEventFactory,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val cryptoService: CryptoService,
private val allowedTypes: List<String>? private val allowedTypes: List<String>?
) : Timeline { ) : Timeline {


@ -159,6 +164,33 @@ internal class DefaultTimeline(
postSnapshot() postSnapshot()
} }


private val newSessionListener = object : NewSessionListener {
override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
if (roomId == this@DefaultTimeline.roomId) {
Timber.v("New session id detected for this room")
backgroundHandler.get()?.post {
val realm = backgroundRealm.get()
var hasChange = false
builtEvents.forEachIndexed { index, timelineEvent ->
if (timelineEvent.isEncrypted()) {
val eventContent = timelineEvent.root.content.toModel<EncryptedEventContent>()
if (eventContent?.sessionId == sessionId
&& (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) {
//we need to rebuild this event
EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let {
builtEvents[index] = timelineEventFactory.create(it, realm)
hasChange = true
}
}
}
}
if (hasChange) postSnapshot()
}
}
}

}

// Public methods ****************************************************************************** // Public methods ******************************************************************************


override fun paginate(direction: Timeline.Direction, count: Int) { override fun paginate(direction: Timeline.Direction, count: Int) {
@ -184,6 +216,7 @@ internal class DefaultTimeline(
val handler = Handler(handlerThread.looper) val handler = Handler(handlerThread.looper)
this.backgroundHandlerThread.set(handlerThread) this.backgroundHandlerThread.set(handlerThread)
this.backgroundHandler.set(handler) this.backgroundHandler.set(handler)
cryptoService.addNewSessionListener(newSessionListener)
handler.post { handler.post {
val realm = Realm.getInstance(realmConfiguration) val realm = Realm.getInstance(realmConfiguration)
backgroundRealm.set(realm) backgroundRealm.set(realm)
@ -211,6 +244,7 @@ internal class DefaultTimeline(


override fun dispose() { override fun dispose() {
if (isStarted.compareAndSet(true, false)) { if (isStarted.compareAndSet(true, false)) {
cryptoService.removeSessionListener(newSessionListener)
Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId") Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId")
backgroundHandler.get()?.post { backgroundHandler.get()?.post {
cancelableBag.cancel() cancelableBag.cancel()

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.timeline
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MediatorLiveData
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.timeline.TimelineService
@ -35,11 +36,20 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val timelineEventFactory: CacheableTimelineEventFactory, private val timelineEventFactory: CacheableTimelineEventFactory,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val cryptoService: CryptoService,
private val paginationTask: PaginationTask private val paginationTask: PaginationTask
) : TimelineService { ) : TimelineService {


override fun createTimeline(eventId: String?, allowedTypes: List<String>?): Timeline { override fun createTimeline(eventId: String?, allowedTypes: List<String>?): Timeline {
return DefaultTimeline(roomId, eventId, monarchy.realmConfiguration, taskExecutor, contextOfEventTask, timelineEventFactory, paginationTask, allowedTypes) return DefaultTimeline(roomId,
eventId,
monarchy.realmConfiguration,
taskExecutor,
contextOfEventTask,
timelineEventFactory,
paginationTask,
cryptoService,
allowedTypes)
} }


override fun getTimeLineEvent(eventId: String): TimelineEvent? { override fun getTimeLineEvent(eventId: String): TimelineEvent? {

View File

@ -69,6 +69,7 @@ internal class SimpleTimelineEventFactory @Inject constructor(private val roomMe
isUniqueDisplayName, isUniqueDisplayName,
senderRoomMember?.avatarUrl, senderRoomMember?.avatarUrl,
eventEntity.sendState, eventEntity.sendState,
event.mClearEvent != null,
relations relations
) )
} }
@ -120,6 +121,7 @@ internal class InMemoryTimelineEventFactory @Inject constructor(private val room
senderData.isUniqueDisplayName, senderData.isUniqueDisplayName,
senderData.senderAvatar, senderData.senderAvatar,
eventEntity.sendState, eventEntity.sendState,
event.mClearEvent != null,
relations relations
) )
} }
@ -138,7 +140,7 @@ internal class InMemoryTimelineEventFactory @Inject constructor(private val room
} }
event.setClearData(result) event.setClearData(result)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.e(failure, "Encrypted event: decryption failed") Timber.e("Encrypted event: decryption failed ${failure.localizedMessage}")
if (failure is MXDecryptionException) { if (failure is MXDecryptionException) {
event.setCryptoError(failure.cryptoError) event.setCryptoError(failure.cryptoError)
} else { } else {