diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index 9491a69ef1..8ca19c5f5c 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -16,6 +16,9 @@ package im.vector.matrix.rx +import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel +import im.vector.matrix.android.api.extensions.orFalse +import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams @@ -28,14 +31,43 @@ import im.vector.matrix.android.api.session.room.send.UserDraft import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional +import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import io.reactivex.Observable import io.reactivex.Single +import io.reactivex.functions.BiFunction -class RxRoom(private val room: Room) { +class RxRoom(private val room: Room, private val session: Session) { fun liveRoomSummary(): Observable> { - return room.getRoomSummaryLive().asObservable() + val summaryObservable = room.getRoomSummaryLive().asObservable() .startWith(room.roomSummary().toOptional()) + + val memberChangeObserver = summaryObservable.map { + it.getOrNull()?.otherMemberIds ?: emptyList() + }.distinctUntilChanged() + + val keyObservable = session.getLiveCryptoDeviceInfo().asObservable() + + val subObserver = Observable + .combineLatest, List, RoomEncryptionTrustLevel>( + memberChangeObserver, + keyObservable, + BiFunction { userList, _ -> + session.getCrossSigningService().getTrustLevelForUsers(userList) + } + ) + + + return Observable + .combineLatest, RoomEncryptionTrustLevel, Optional>( + summaryObservable, + subObserver, + BiFunction { summary, level -> + summary.getOrNull()?.copy( + roomEncryptionTrustLevel = if (summary.getOrNull()?.isEncrypted.orFalse()) level else null + ).toOptional() + } + ) } fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable> { @@ -88,6 +120,6 @@ class RxRoom(private val room: Room) { } } -fun Room.rx(): RxRoom { - return RxRoom(this) +fun Room.rx(session: Session): RxRoom { + return RxRoom(this, session) } diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 046ff55a3f..11083ca35e 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -33,12 +33,30 @@ import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import io.reactivex.Observable import io.reactivex.Single +import io.reactivex.functions.BiFunction class RxSession(private val session: Session) { fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable> { - return session.getRoomSummariesLive(queryParams).asObservable() + val summaryObservable = session.getRoomSummariesLive(queryParams).asObservable() .startWith(session.getRoomSummaries(queryParams)) + + val keyObservable = session.getLiveCryptoDeviceInfo().asObservable() + + return Observable + .combineLatest, List, List>( + summaryObservable, + keyObservable, + BiFunction { summaries, _ -> + summaries.map { + if (it.isEncrypted) it.copy( + roomEncryptionTrustLevel = session.getCrossSigningService().getTrustLevelForUsers( + it.otherMemberIds + ) + ) else it + } + } + ) } fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index 594ad39063..799fcdcd7b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -121,6 +121,7 @@ interface CryptoService { fun getCryptoDeviceInfo(userId: String): List fun getLiveCryptoDeviceInfo(userId: String): LiveData> + fun getLiveCryptoDeviceInfo(): LiveData> fun addNewSessionListener(newSessionListener: NewSessionListener) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index b7bc20a1dc..8d65b9e55a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -18,6 +18,8 @@ package im.vector.matrix.android.api.session.crypto.crosssigning import androidx.lifecycle.LiveData import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult @@ -62,4 +64,6 @@ interface CrossSigningService { fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult + + fun getTrustLevelForUsers(userList: List): RoomEncryptionTrustLevel } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index a392ee1e86..3099c2c417 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -406,6 +406,9 @@ internal class DefaultCryptoService @Inject constructor( override fun getLiveCryptoDeviceInfo(userId: String): LiveData> { return cryptoStore.getLiveDeviceList(userId) } + override fun getLiveCryptoDeviceInfo(): LiveData> { + return cryptoStore.getLiveDeviceList() + } /** * Set the devices as known diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 6214ae0321..26c222f302 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -19,6 +19,8 @@ package im.vector.matrix.android.internal.crypto.crosssigning import androidx.lifecycle.LiveData import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel +import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.util.Optional @@ -655,4 +657,27 @@ internal class DefaultCrossSigningService @Inject constructor( } } } + + override fun getTrustLevelForUsers(userList: List): RoomEncryptionTrustLevel { + val atLeastOneTrusted = userList + .filter { it != userId } + .map { getUserCrossSigningKeys(it) } + .any { it?.isTrusted() == true } + + if (!atLeastOneTrusted) { + return RoomEncryptionTrustLevel.Default + } else { + // I have verified at least one other user + val allDevices = userList.mapNotNull { + cryptoStore.getUserDeviceList(it) + }.flatten() + if (getMyCrossSigningKeys() != null) { + val hasWarning = allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() } + return if (hasWarning) RoomEncryptionTrustLevel.Warning else RoomEncryptionTrustLevel.Trusted + } else { + val hasWarningLegacy = allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() } + return if (hasWarningLegacy) RoomEncryptionTrustLevel.Warning else RoomEncryptionTrustLevel.Trusted + } + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index 022e1ba320..dfefc3b749 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -200,6 +200,8 @@ internal interface IMXCryptoStore { fun getUserDeviceList(userId: String): List? fun getLiveDeviceList(userId: String): LiveData> + //TODO temp + fun getLiveDeviceList(): LiveData> /** * Store the crypto algorithm for a room. * diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 21e52cf1b9..767a945272 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -398,6 +398,20 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati } } + override fun getLiveDeviceList(): LiveData> { + val liveData = monarchy.findAllMappedWithChanges( + { realm: Realm -> + realm.where() + }, + { entity -> + entity.devices.map { CryptoMapper.mapToModel(it) } + } + ) + return Transformations.map(liveData) { + it.firstOrNull() ?: emptyList() + } + } + override fun storeRoomAlgorithm(roomId: String, algorithm: String) { doRealmTransaction(realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).algorithm = algorithm diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 94dbcc8057..2630d4d082 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -152,7 +152,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro observeDrafts() observeUnreadState() room.getRoomSummaryLive() - room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear() + room.rx(session).loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear() // Inform the SDK that the room is displayed session.onRoomDisplayed(initialState.roomId) } @@ -233,7 +233,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun observeDrafts() { - room.rx().liveDrafts() + room.rx(session).liveDrafts() .subscribe { Timber.d("Draft update --> SetState") setState { @@ -874,7 +874,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.rx(session).liveRoomSummary() .unwrap() .execute { async -> val typingRoomMembers = @@ -892,7 +892,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro Observable .combineLatest, RoomSummary, UnreadState>( timelineEvents.observeOn(Schedulers.computation()), - room.rx().liveRoomSummary().unwrap(), + room.rx(session).liveRoomSummary().unwrap(), BiFunction { timelineEvents, roomSummary -> computeUnreadState(timelineEvents, roomSummary) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 661e7fb416..aa81658bf9 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -134,7 +134,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun observeEvent() { if (room == null) return - room.rx() + room.rx(session) .liveTimelineEvent(eventId) .unwrap() .execute { @@ -144,7 +144,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun observeReactions() { if (room == null) return - room.rx() + room.rx(session) .liveAnnotationSummary(eventId) .map { annotations -> EmojiDataSource.quickEmojis.map { emoji -> diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 05cdbc0fd8..e3d78c27b3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -86,7 +86,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted } private fun observeEventAnnotationSummaries() { - RxRoom(room) + RxRoom(room, session) .liveAnnotationSummary(eventId) .unwrap() .flatMapSingle { summaries -> diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt index 40f773b253..3108d3eafb 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt @@ -28,7 +28,7 @@ import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initialState: RoomListQuickActionsState, - session: Session + private val session: Session ) : VectorViewModel(initialState) { @AssistedInject.Factory @@ -54,7 +54,7 @@ class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initia private fun observeNotificationState() { room - .rx() + .rx(session) .liveNotificationState() .execute { copy(roomNotificationState = it) @@ -63,7 +63,7 @@ class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initia private fun observeRoomSummary() { room - .rx() + .rx(session) .liveRoomSummary() .unwrap() .execute { diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt index aba6274dff..f6fe353f29 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -153,7 +153,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v val queryParams = roomMemberQueryParams { this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE) } - room.rx().liveRoomMembers(queryParams) + room.rx(session).liveRoomMembers(queryParams) .map { it.firstOrNull()?.toMatrixItem().toOptional() } .unwrap() .execute { @@ -176,8 +176,8 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } private fun observeRoomSummaryAndPowerLevels(room: Room) { - val roomSummaryLive = room.rx().liveRoomSummary().unwrap() - val powerLevelsContentLive = room.rx().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS) + val roomSummaryLive = room.rx(session).liveRoomSummary().unwrap() + val powerLevelsContentLive = room.rx(session).liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS) .mapOptional { it.content.toModel() } .unwrap() diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt index 6c66ac67b2..b0c942cbb9 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt @@ -56,7 +56,7 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: R } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.rx(session).liveRoomSummary() .unwrap() .execute { copy(roomSummary = it) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt index 2556e3b78c..b86fed9936 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt @@ -72,8 +72,8 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } Observable .combineLatest, PowerLevelsContent, RoomMemberSummaries>( - room.rx().liveRoomMembers(roomMemberQueryParams), - room.rx() + room.rx(session).liveRoomMembers(roomMemberQueryParams), + room.rx(session) .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS) .mapOptional { it.content.toModel() } .unwrap(), @@ -87,7 +87,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.rx(session).liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt index ee6413b6a4..4194fb6e5c 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -53,7 +53,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.rx(session).liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async)