This commit is contained in:
Benoit Marty 2019-05-21 15:42:09 +02:00
parent 6d8000b957
commit 52d9adad70
24 changed files with 493 additions and 155 deletions

View File

@ -22,12 +22,15 @@ import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
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.api.session.room.Room
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
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.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody

interface CryptoService {

@ -57,7 +60,7 @@ interface CryptoService {

fun getMyDevice(): MXDeviceInfo

fun getGlobalBlacklistUnverifiedDevices() : Boolean
fun getGlobalBlacklistUnverifiedDevices(): Boolean

fun setGlobalBlacklistUnverifiedDevices(block: Boolean)

@ -83,6 +86,13 @@ interface CryptoService {

fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int

fun isRoomEncrypted(roomId: String): Boolean

fun encryptEventContent(eventContent: Content,
eventType: String,
room: Room,
callback: MatrixCallback<MXEncryptEventContentResult>)

/*
fun start(isInitialSync: Boolean, aCallback: MatrixCallback<Unit>?)

@ -92,14 +102,6 @@ interface CryptoService {

fun close()

fun encryptEventContent(eventContent: Content,
eventType: String,
room: Room,
callback: MatrixCallback<MXEncryptEventContentResult>)

fun onToDeviceEvent(event: Event)

fun onSyncCompleted(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean)

fun getOlmDevice(): MXOlmDevice?

@ -118,4 +120,8 @@ interface CryptoService {
*/

fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult?

fun getEncryptionAlgorithm(roomId: String): String?

fun shouldEncryptForInvitedMembers(roomId: String): Boolean
}

View File

@ -16,16 +16,11 @@

package im.vector.matrix.android.api.session.room.crypto

import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM

interface RoomCryptoService {

// TODO
fun isEncrypted(): Boolean = false
fun isEncrypted(): Boolean

// TODO
fun encryptionAlgorithm(): String? = MXCRYPTO_ALGORITHM_MEGOLM
fun encryptionAlgorithm(): String?

// TODO
fun shouldEncryptForInvitedMembers(): Boolean = false
fun shouldEncryptForInvitedMembers(): Boolean
}

View File

@ -54,18 +54,4 @@ interface RoomMembersService {
*/
fun invite(userId: String, callback: MatrixCallback<Unit>)

/**
* Return all the roomMembers ids which are joined or invited to the room
*
* @return a roomMember id list of joined or invited members.
*/
fun getActiveRoomMemberIds(): List<String>

/**
* Return all the roomMembers ids which are joined to the room
*
* @return a roomMember id list of joined members.
*/
fun getJoinedRoomMemberIds(): List<String>

}

View File

@ -35,8 +35,8 @@ import im.vector.matrix.android.api.session.events.model.Event
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.Room
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import im.vector.matrix.android.internal.crypto.actions.MegolmSessionDataImporter
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
@ -97,8 +97,6 @@ internal class CryptoManager(
private val mIncomingRoomKeyRequestManager: IncomingRoomKeyRequestManager,
//
private val mOutgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
// Room service
private val mRoomService: RoomService,
// Olm Manager
private val mOlmManager: OlmManager,
// Actions
@ -140,11 +138,23 @@ internal class CryptoManager(
// }
//}

fun onLiveEvent(roomId: String, event: Event) {
fun onStateEvent(roomId: String, event: Event) {
if (event.type == EventType.ENCRYPTION) {
onRoomEncryptionEvent(roomId, event)
// TODO Remove onRoomEncryptionEvent(roomId, event)
} else if (event.type == EventType.STATE_ROOM_MEMBER) {
onRoomMembershipEvent(roomId, event)
} else if (event.type == EventType.STATE_HISTORY_VISIBILITY) {
onRoomHistoryVisibilityEvent(roomId, event)
}
}

fun onLiveEvent(roomId: String, event: Event) {
if (event.type == EventType.ENCRYPTION) {
// TODO Remove onRoomEncryptionEvent(roomId, event)
} else if (event.type == EventType.STATE_ROOM_MEMBER) {
onRoomMembershipEvent(roomId, event)
} else if (event.type == EventType.STATE_HISTORY_VISIBILITY) {
onRoomHistoryVisibilityEvent(roomId, event)
}
}

@ -522,21 +532,15 @@ internal class CryptoManager(
* @param roomId the room id
* @return true if the room is encrypted
*/
fun isRoomEncrypted(roomId: String?): Boolean {
var res = false
override fun isRoomEncrypted(roomId: String): Boolean {
var res: Boolean

if (null != roomId) {
synchronized(mRoomEncryptors) {
res = mRoomEncryptors.containsKey(roomId)
synchronized(mRoomEncryptors) {
res = mRoomEncryptors.containsKey(roomId)
}

if (!res) {
val room = mRoomService.getRoom(roomId)

if (null != room) {
res = room.isEncrypted()
}
}
}
if (!res) {
res = !mCryptoStore.getRoomAlgorithm(roomId).isNullOrBlank()
}

return res
@ -564,6 +568,26 @@ internal class CryptoManager(
mEnsureOlmSessionsForDevicesAction.handle(devicesByUser, callback)
}

fun isEncryptionEnabledForInvitedUser(): Boolean {
return mCryptoConfig.mEnableEncryptionForInvitedMembers
}

override fun getEncryptionAlgorithm(roomId: String): String? {
return mCryptoStore.getRoomAlgorithm(roomId)
}

/**
* Determine whether we should encrypt messages for invited users in this room.
* <p>
* Check here whether the invited members are allowed to read messages in the room history
* from the point they were invited onwards.
*
* @return true if we should encrypt messages for invited users.
*/
override fun shouldEncryptForInvitedMembers(roomId: String): Boolean {
return mCryptoStore.shouldEncryptForInvitedMembers(roomId)
}

/**
* Encrypt an event content according to the configuration of the room.
*
@ -572,10 +596,10 @@ internal class CryptoManager(
* @param room the room the event will be sent.
* @param callback the asynchronous callback
*/
fun encryptEventContent(eventContent: Content,
eventType: String,
room: Room,
callback: MatrixCallback<MXEncryptEventContentResult>) {
override fun encryptEventContent(eventContent: Content,
eventType: String,
room: Room,
callback: MatrixCallback<MXEncryptEventContentResult>) {
// wait that the crypto is really started
if (!isStarted()) {
Timber.v("## encryptEventContent() : wait after e2e init")
@ -596,13 +620,15 @@ internal class CryptoManager(
}

// Check whether the event content must be encrypted for the invited members.
val encryptForInvitedMembers = mCryptoConfig.mEnableEncryptionForInvitedMembers && room.shouldEncryptForInvitedMembers()
val encryptForInvitedMembers = mCryptoConfig.mEnableEncryptionForInvitedMembers && shouldEncryptForInvitedMembers(room.roomId)

val userIds = if (encryptForInvitedMembers) {
room.getActiveRoomMemberIds()
} else {
room.getJoinedRoomMemberIds()
}
// TODO
//val userIds = if (encryptForInvitedMembers) {
// room.getActiveRoomMemberIds()
//} else {
// room.getJoinedRoomMemberIds()
//}
val userIds = emptyList<String>()

// just as you are sending a secret message?

@ -749,22 +775,8 @@ internal class CryptoManager(
*
* @param event the encryption event.
*/
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
// TODO Parse the event
val eventContent = event.content // wireEventContent

val room = mRoomService.getRoom(roomId)!!

// Check whether the event content must be encrypted for the invited members.
val encryptForInvitedMembers = mCryptoConfig.mEnableEncryptionForInvitedMembers && room.shouldEncryptForInvitedMembers()

val userIds = if (encryptForInvitedMembers) {
room.getActiveRoomMemberIds()
} else {
room.getJoinedRoomMemberIds()
}

setEncryptionInRoom(roomId, eventContent!!["algorithm"] as String, true, userIds)
fun onRoomEncryptionEvent(event: Event, userIds: List<String>) {
setEncryptionInRoom(event.roomId!!, event.content!!["algorithm"] as String, true, userIds)
}

/**
@ -785,6 +797,8 @@ internal class CryptoManager(
}

val userId = event.stateKey!!

/* FIXME
val room = mRoomService.getRoom(roomId)

val roomMember = room?.getRoomMember(userId)
@ -796,7 +810,7 @@ internal class CryptoManager(
// make sure we are tracking the deviceList for this user.
deviceListManager.startTrackingDeviceList(Arrays.asList(userId))
} else if (membership == Membership.INVITE
&& room.shouldEncryptForInvitedMembers()
&& shouldEncryptForInvitedMembers(roomId)
&& mCryptoConfig.mEnableEncryptionForInvitedMembers) {
// track the deviceList for this invited user.
// Caution: there's a big edge case here in that federated servers do not
@ -806,8 +820,18 @@ internal class CryptoManager(
deviceListManager.startTrackingDeviceList(Arrays.asList(userId))
}
}
*/
}

private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) {
val eventContent = event.content.toModel<RoomHistoryVisibilityContent>()

eventContent?.historyVisibility?.let {
mCryptoStore.setShouldEncryptForInvitedMembers(roomId, it != RoomHistoryVisibility.JOINED)
}
}


/**
* Upload my user's device keys.
* This method must called on getEncryptingThreadHandler() thread.
@ -996,6 +1020,7 @@ internal class CryptoManager(
* @param roomId the room id
* @return true if the client should encrypt messages only for the verified devices.
*/
// TODO add this info in CryptoRoomEntity?
override fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean {
return if (null != roomId) {
mCryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
@ -1011,13 +1036,6 @@ internal class CryptoManager(
* @param add true to add the room id to the list, false to remove it.
*/
private fun setRoomBlacklistUnverifiedDevices(roomId: String, add: Boolean) {
val room = mRoomService.getRoom(roomId)

// sanity check
if (null == room) {
return
}

val roomIds = mCryptoStore.getRoomsListBlacklistUnverifiedDevices().toMutableList()

if (add) {

View File

@ -80,7 +80,7 @@ internal class CryptoModule {

// CryptoService
scope(DefaultSession.SCOPE) {
DefaultCryptoService(get()) as CryptoService
get<CryptoManager>() as CryptoService
}

//
@ -187,7 +187,6 @@ internal class CryptoModule {
get(),
get(),
get(),
get(),
// Actions
get(),
get(),

View File

@ -1,22 +0,0 @@
/*
* 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

import im.vector.matrix.android.api.session.crypto.CryptoService

internal class DefaultCryptoService(val cryptoManager: CryptoManager)
: CryptoService by cryptoManager

View File

@ -0,0 +1,93 @@
/*
* 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.live

import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.RoomMembers
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.WorkerParamsFactory
import org.koin.standalone.inject

internal class EnableEncryptionWorker(context: Context,
workerParameters: WorkerParameters
) : Worker(context, workerParameters), MatrixKoinComponent {

private val monarchy by inject<Monarchy>()
private val cryptoManager by inject<CryptoManager>()
private val loadRoomMembersTask by inject<LoadRoomMembersTask>()
private val taskExecutor by inject<TaskExecutor>()

@JsonClass(generateAdapter = true)
internal class Params(
val eventIds: List<String>
)


override fun doWork(): Result {
val params = WorkerParamsFactory.fromData<EnableEncryptionWorker.Params>(inputData)
?: return Result.failure()


val events = monarchy.fetchAllMappedSync(
{ EventEntity.where(it, params.eventIds) },
{ it.asDomain() }
)

events.forEach {
val roomId = it.roomId!!

loadRoomMembersTask
.configureWith(LoadRoomMembersTask.Params(roomId))
.executeOn(TaskThread.CALLER)
.executeBy(taskExecutor)

var userIds: List<String> = emptyList()

monarchy.doWithRealm { realm ->
// Check whether the event content must be encrypted for the invited members.
val encryptForInvitedMembers = cryptoManager.isEncryptionEnabledForInvitedUser()
&& cryptoManager.shouldEncryptForInvitedMembers(roomId)


userIds = if (encryptForInvitedMembers) {
RoomMembers(realm, roomId).getActiveRoomMemberIds()
} else {
RoomMembers(realm, roomId).getJoinedRoomMemberIds()
}

}

cryptoManager.onRoomEncryptionEvent(it, userIds)
}

return Result.success()
}


}

View File

@ -0,0 +1,55 @@
/*
* 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.live

import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.WorkerParamsFactory
import timber.log.Timber

private const val ENABLE_ENCRYPTION_EVENT_WORKER = "ENABLE_ENCRYPTION_EVENT_WORKER"

internal class RoomEncryptionEnabler(monarchy: Monarchy) : RealmLiveEntityObserver<EventEntity>(monarchy) {

override val query: Monarchy.Query<EventEntity>
get() = Monarchy.Query<EventEntity> { EventEntity.where(it, type = EventType.ENCRYPTION) }


override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
Timber.v("RoomEncryption received")

val eventIds = inserted.mapNotNull { it.asDomain().eventId }

val workParam = EnableEncryptionWorker.Params(eventIds)
val workData = WorkerParamsFactory.toData(workParam)

val work = OneTimeWorkRequestBuilder<EnableEncryptionWorker>()
.setInputData(workData)
.build()

WorkManager.getInstance()
.beginUniqueWork(ENABLE_ENCRYPTION_EVENT_WORKER, ExistingWorkPolicy.APPEND, work)
.enqueue()
}
}

View File

@ -19,11 +19,11 @@ package im.vector.matrix.android.internal.crypto.store

import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXOlmInboundGroupSession2
import im.vector.matrix.android.internal.crypto.model.MXOlmSession
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
import org.matrix.olm.OlmAccount

/**
@ -204,6 +204,10 @@ internal interface IMXCryptoStore {
*/
fun getRoomAlgorithm(roomId: String): String?

fun shouldEncryptForInvitedMembers(roomId: String): Boolean

fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean)

/**
* Store a session between the logged-in user and another device.
*

View File

@ -20,15 +20,15 @@ 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.OutgoingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXOlmInboundGroupSession2
import im.vector.matrix.android.internal.crypto.model.MXOlmSession
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.model.*
import im.vector.matrix.android.internal.crypto.store.db.query.delete
import im.vector.matrix.android.internal.crypto.store.db.query.getById
import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXOlmInboundGroupSession2
import im.vector.matrix.android.internal.crypto.model.MXOlmSession
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import io.realm.RealmConfiguration
import io.realm.Sort
import io.realm.kotlin.where
@ -55,7 +55,7 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
// Cache for InboundGroupSession, to release them properly
private val inboundGroupSessionToRelease = HashMap<String, MXOlmInboundGroupSession2>()

/* ==========================================================================================
/* ==========================================================================================
* Other data
* ========================================================================================== */

@ -241,6 +241,19 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
?.algorithm
}

override fun shouldEncryptForInvitedMembers(roomId: String): Boolean {
return doRealmQueryAndCopy(realmConfiguration) {
CryptoRoomEntity.getById(it, roomId)
}
?.shouldEncryptForInvitedMembers ?: false
}

override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) {
doRealmTransaction(realmConfiguration) {
CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers
}
}

override fun storeSession(session: MXOlmSession, deviceKey: String) {
var sessionIdentifier: String? = null


View File

@ -22,6 +22,7 @@ import io.realm.annotations.PrimaryKey
internal open class CryptoRoomEntity(
@PrimaryKey var roomId: String? = null,
var algorithm: String? = null,
var shouldEncryptForInvitedMembers: Boolean? = null,
var blacklistUnverifiedDevices: Boolean = false)
: RealmObject() {


View File

@ -70,6 +70,9 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val m
processChanges(inserted, updated, deleted)
}

/**
* Do quick treatment or delegate on a task
*/
protected abstract fun processChanges(inserted: List<T>, updated: List<T>, deleted: List<T>)

}

View File

@ -30,6 +30,10 @@ internal fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQu
return realm.where<EventEntity>().equalTo(EventEntityFields.EVENT_ID, eventId)
}

internal fun EventEntity.Companion.where(realm: Realm, eventIds: List<String>): RealmQuery<EventEntity> {
return realm.where<EventEntity>().`in`(EventEntityFields.EVENT_ID, eventIds.toTypedArray())
}

internal fun EventEntity.Companion.where(realm: Realm,
roomId: String? = null,
type: String? = null,

View File

@ -48,6 +48,7 @@ object MoshiProvider {
return moshi
}

// TODO Move
fun <T> getCanonicalJson(type: Class<T>, o: T): String {
val adapter = moshi.adapter<T>(type)


View File

@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
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.api.session.group.Group
import im.vector.matrix.android.api.session.group.GroupService
@ -44,13 +45,14 @@ import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.MatrixCallbackDelegate
import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.crypto.CryptoModule
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
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.CryptoManager
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.di.MatrixKoinHolder
@ -321,6 +323,17 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
cryptoService.setRoomBlacklistUnverifiedDevices(roomId)
}

override fun isRoomEncrypted(roomId: String): Boolean {
return cryptoService.isRoomEncrypted(roomId)
}

override fun encryptEventContent(eventContent: Content,
eventType: String,
room: Room,
callback: MatrixCallback<MXEncryptEventContentResult>) {
cryptoService.encryptEventContent(eventContent, eventType, room, callback)
}

override fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo? {
return cryptoService.getDeviceInfo(userId, deviceId)
}
@ -341,6 +354,14 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
return cryptoService.decryptEvent(event, timeline)
}

override fun getEncryptionAlgorithm(roomId: String): String? {
return cryptoService.getEncryptionAlgorithm(roomId)
}

override fun shouldEncryptForInvitedMembers(roomId: String): Boolean {
return cryptoService.shouldEncryptForInvitedMembers(roomId)
}

// Private methods *****************************************************************************

private fun assertMainThread() {

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.internal.crypto.live.RoomEncryptionEnabler
import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.database.model.SessionRealmModule
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
@ -151,7 +152,8 @@ internal class SessionModule(private val sessionParams: SessionParams) {
val groupSummaryUpdater = GroupSummaryUpdater(get())
val eventsPruner = EventsPruner(get())
val userEntityUpdater = UserEntityUpdater(get(), get(), get())
listOf<LiveEntityObserver>(groupSummaryUpdater, eventsPruner, userEntityUpdater)
val roomEncryptionEnabler = RoomEncryptionEnabler(get())
listOf<LiveEntityObserver>(groupSummaryUpdater, eventsPruner, userEntityUpdater, roomEncryptionEnabler)
}



View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.members.RoomMembersService
import im.vector.matrix.android.api.session.room.model.RoomSummary
@ -39,7 +40,8 @@ internal class DefaultRoom(
private val sendService: SendService,
private val stateService: StateService,
private val readService: ReadService,
private val roomMembersService: RoomMembersService
private val roomMembersService: RoomMembersService,
private val cryptoService: CryptoService
) : Room,
TimelineService by timelineService,
SendService by sendService,
@ -63,4 +65,16 @@ internal class DefaultRoom(
}
}

override fun isEncrypted(): Boolean {
return cryptoService.isRoomEncrypted(roomId)
}

override fun encryptionAlgorithm(): String? {
return cryptoService.getEncryptionAlgorithm(roomId)
}

override fun shouldEncryptForInvitedMembers(): Boolean {
return cryptoService.shouldEncryptForInvitedMembers(roomId)
}

}

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.session.room

import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.internal.session.room.invite.InviteTask
import im.vector.matrix.android.internal.session.room.members.DefaultRoomMembersService
@ -42,13 +43,14 @@ internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask,
private val contextOfEventTask: GetContextOfEventTask,
private val setReadMarkersTask: SetReadMarkersTask,
private val eventFactory: LocalEchoEventFactory,
private val cryptoService: CryptoService,
private val taskExecutor: TaskExecutor) {

fun instantiate(roomId: String): Room {
val roomMemberExtractor = SenderRoomMemberExtractor(roomId)
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor)
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, timelineEventFactory, paginationTask)
val sendService = DefaultSendService(roomId, eventFactory, monarchy)
val sendService = DefaultSendService(roomId, eventFactory, cryptoService, monarchy)
val stateService = DefaultStateService(roomId, sendStateTask, taskExecutor)
val roomMembersService = DefaultRoomMembersService(roomId, monarchy, loadRoomMembersTask, inviteTask, taskExecutor)
val readService = DefaultReadService(roomId, monarchy, setReadMarkersTask, taskExecutor)
@ -60,7 +62,8 @@ internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask,
sendService,
stateService,
readService,
roomMembersService
roomMembersService,
cryptoService
)
}


View File

@ -71,7 +71,7 @@ class RoomModule {
}

scope(DefaultSession.SCOPE) {
RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get())
RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
}

scope(DefaultSession.SCOPE) {

View File

@ -68,26 +68,4 @@ internal class DefaultRoomMembersService(private val roomId: String,
.dispatchTo(callback)
.executeBy(taskExecutor)
}

override fun getActiveRoomMemberIds(): List<String> {
return getRoomMemberIdsFiltered { it.membership == Membership.JOIN || it.membership == Membership.INVITE }
}

override fun getJoinedRoomMemberIds(): List<String> {
return getRoomMemberIdsFiltered { it.membership == Membership.JOIN }
}

/* ==========================================================================================
* Private
* ========================================================================================== */

private fun getRoomMemberIdsFiltered(predicate: (RoomMember) -> Boolean): List<String> {
return monarchy.fetchAllCopiedSync { RoomMembers(it, roomId).queryRoomMembersEvent() }
.map { it.asDomain() }
.associateBy { it.stateKey!! }
.mapValues { it.value.content.toModel<RoomMember>()!! }
.filterValues { predicate(it) }
.keys
.toList()
}
}

View File

@ -95,5 +95,38 @@ internal class RoomMembers(private val realm: Realm,
return getNumberOfJoinedMembers() + getNumberOfInvitedMembers()
}

/**
* Return all the roomMembers ids which are joined or invited to the room
*
* @return a roomMember id list of joined or invited members.
*/
fun getActiveRoomMemberIds(): List<String> {
return getRoomMemberIdsFiltered { it.membership == Membership.JOIN || it.membership == Membership.INVITE }
}

/**
* Return all the roomMembers ids which are joined to the room
*
* @return a roomMember id list of joined members.
*/
fun getJoinedRoomMemberIds(): List<String> {
return getRoomMemberIdsFiltered { it.membership == Membership.JOIN }
}

/* ==========================================================================================
* Private
* ========================================================================================== */

private fun getRoomMemberIdsFiltered(predicate: (RoomMember) -> Boolean): List<String> {
return RoomMembers(realm, roomId)
.queryRoomMembersEvent()
.findAll()
.map { it.asDomain() }
.associateBy { it.stateKey!! }
.mapValues { it.value.content.toModel<RoomMember>()!! }
.filterValues { predicate(it) }
.keys
.toList()
}

}

View File

@ -16,15 +16,10 @@

package im.vector.matrix.android.internal.session.room.send

import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.*
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.api.util.Cancelable
@ -39,6 +34,7 @@ import im.vector.matrix.android.internal.session.content.UploadContentWorker
import im.vector.matrix.android.internal.util.CancelableWork
import im.vector.matrix.android.internal.util.WorkerParamsFactory
import im.vector.matrix.android.internal.util.tryTransactionAsync
import timber.log.Timber
import java.util.concurrent.TimeUnit

private const val SEND_WORK = "SEND_WORK"
@ -51,6 +47,7 @@ private val WORK_CONSTRAINTS = Constraints.Builder()

internal class DefaultSendService(private val roomId: String,
private val eventFactory: LocalEchoEventFactory,
private val cryptoService: CryptoService,
private val monarchy: Monarchy)
: SendService {

@ -59,6 +56,33 @@ internal class DefaultSendService(private val roomId: String,
val event = eventFactory.createTextEvent(roomId, msgType, text).also {
saveLocalEcho(it)
}

// Encrypted room handling
if (cryptoService.isRoomEncrypted(roomId)) {
Timber.v("Send event in encrypted room")
// Encrypt then send

val encryptWork = createEncryptEventWork(event)

val sendWork = OneTimeWorkRequestBuilder<SendEventWorker>()
.setConstraints(WORK_CONSTRAINTS)
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
.build()

WorkManager.getInstance()
// Encrypt
.beginUniqueWork(buildWorkIdentifier(SEND_WORK), ExistingWorkPolicy.APPEND, encryptWork)
// then send
.then(sendWork)
.enqueue()

return CancelableWork(encryptWork.id)
} else {
return sendEvent(event)
}
}

private fun sendEvent(event: Event): Cancelable {
val sendWork = createSendEventWork(event)
WorkManager.getInstance()
.beginUniqueWork(buildWorkIdentifier(SEND_WORK), ExistingWorkPolicy.APPEND, sendWork)
@ -93,9 +117,9 @@ internal class DefaultSendService(private val roomId: String,
private fun saveLocalEcho(event: Event) {
monarchy.tryTransactionAsync { realm ->
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
?: return@tryTransactionAsync
?: return@tryTransactionAsync
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId = roomId)
?: return@tryTransactionAsync
?: return@tryTransactionAsync

roomEntity.addSendingEvent(event, liveChunk.forwardsStateIndex ?: 0)
}
@ -105,6 +129,18 @@ internal class DefaultSendService(private val roomId: String,
return "${roomId}_$identifier"
}

private fun createEncryptEventWork(event: Event): OneTimeWorkRequest {
// Same parameter
val sendContentWorkerParams = SendEventWorker.Params(roomId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)

return OneTimeWorkRequestBuilder<EncryptEventWorker>()
.setConstraints(WORK_CONSTRAINTS)
.setInputData(sendWorkData)
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
.build()
}

private fun createSendEventWork(event: Event): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(roomId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)

View File

@ -0,0 +1,88 @@
/*
* 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.session.room.send

import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
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.Event
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.util.WorkerParamsFactory
import org.koin.standalone.inject
import java.util.concurrent.CountDownLatch

internal class EncryptEventWorker(context: Context, params: WorkerParameters)
: Worker(context, params), MatrixKoinComponent {


@JsonClass(generateAdapter = true)
internal data class Params(
val roomId: String,
val event: Event
)

private val crypto by inject<CryptoService>()
private val roomService by inject<RoomService>()

override fun doWork(): Result {

val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure()

val localEvent = params.event
if (localEvent.eventId == null) {
return Result.failure()
}

// TODO Better async handling
val latch = CountDownLatch(1)

var result: MXEncryptEventContentResult? = null
var error: Throwable? = null

crypto.encryptEventContent(localEvent.content!!, localEvent.type, roomService.getRoom(params.roomId)!!, object : MatrixCallback<MXEncryptEventContentResult> {
override fun onSuccess(data: MXEncryptEventContentResult) {
result = data
latch.countDown()
}

override fun onFailure(failure: Throwable) {
error = failure
latch.countDown()
}
})

latch.await()

// TODO Update local echo

if (error != null) {
return Result.failure() // TODO Pass error!!)
} else if (result != null) {
return Result.success(WorkerParamsFactory.toData(SendEventWorker.Params(params.roomId,
Event(type = result!!.mEventType,
content = result!!.mEventContent))))
} else {
return Result.failure()
}
}
}

View File

@ -63,9 +63,9 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,

private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy) {
val rooms = when (handlingStrategy) {
is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) }
is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) }
is HandlingStrategy.INVITED -> handlingStrategy.data.map { handleInvitedRoom(realm, it.key, it.value) }
is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) }
is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) }
}
realm.insertOrUpdate(rooms)
}
@ -91,9 +91,16 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
val numberOfStateEvents = roomSync.state?.events?.size ?: 0
val stateIndexOffset = lastStateIndex + numberOfStateEvents

// State event
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val untimelinedStateIndex = if (isInitialSync) Int.MIN_VALUE else stateIndexOffset
roomEntity.addStateEvents(roomSync.state.events, filterDuplicates = true, stateIndex = untimelinedStateIndex)

// Give info to crypto module
// TODO Remove
roomSync.state.events.forEach {
mCrypto.onStateEvent(roomId, it)
}
}

if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {