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.Event
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.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
@ -107,4 +108,8 @@ interface CryptoService {

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 senderAvatar: String?,
val sendState: SendState,
val hasClearEventFlag: Boolean = false,
val annotations: EventAnnotationsSummary? = null
) {


View File

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

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

override fun removeSessionListener(listener: NewSessionListener) {
roomDecryptorProvider.removeSessionListener(listener)
}
/* ==========================================================================================
* 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
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.
* 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)
if (decryptingClass) {
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()
}
if (roomId != null && !TextUtils.isEmpty(roomId)) {

View File

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

var newSessionListener: NewSessionListener? = null

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

override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {

View File

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

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.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
@ -376,4 +377,8 @@ internal interface IMXCryptoStore {
* @return an IncomingRoomKeyRequest if it exists, else null
*/
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 im.vector.matrix.android.api.auth.data.Credentials
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.model.MXDeviceInfo
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
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
* ========================================================================================== */
@ -718,4 +730,5 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
}
.toMutableList()
}

}

View File

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

fun create(roomId: String): Room {
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 stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
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.Looper
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.toModel
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.util.CancelableBag
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.model.*
import im.vector.matrix.android.internal.database.query.findIncludingEvent
@ -56,6 +60,7 @@ internal class DefaultTimeline(
private val contextOfEventTask: GetContextOfEventTask,
private val timelineEventFactory: CacheableTimelineEventFactory,
private val paginationTask: PaginationTask,
private val cryptoService: CryptoService,
private val allowedTypes: List<String>?
) : Timeline {

@ -159,6 +164,33 @@ internal class DefaultTimeline(
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 ******************************************************************************

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

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

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.timeline
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
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.TimelineEvent
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 timelineEventFactory: CacheableTimelineEventFactory,
private val contextOfEventTask: GetContextOfEventTask,
private val cryptoService: CryptoService,
private val paginationTask: PaginationTask
) : TimelineService {

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? {

View File

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