Better session detection

This commit is contained in:
Valere 2020-04-29 18:46:11 +02:00
parent 8955e5461c
commit f0a9be2ec7
7 changed files with 53 additions and 16 deletions

View file

@ -32,6 +32,7 @@ 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 im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
import io.reactivex.Observable
import io.reactivex.Single
@ -131,6 +132,13 @@ class RxSession(private val session: Session) {
}
}
fun liveCrossSigningPrivateKeys(): Observable<Optional<PrivateKeysInfo>> {
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asObservable()
.startWithCallable {
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional()
}
}
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
return session.getLiveAccountDataEvents(types).asObservable()
.startWithCallable {

View file

@ -55,6 +55,8 @@ interface CrossSigningService {
fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
fun canCrossSign(): Boolean
fun trustUser(otherUserId: String,

View file

@ -470,6 +470,10 @@ internal class DefaultCrossSigningService @Inject constructor(
return cryptoStore.getCrossSigningPrivateKeys()
}
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
return cryptoStore.getLiveCrossSigningPrivateKeys()
}
override fun canCrossSign(): Boolean {
return checkSelfTrust().isVerified() && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null
&& cryptoStore.getCrossSigningPrivateKeys()?.user != null

View file

@ -409,6 +409,7 @@ internal interface IMXCryptoStore {
fun storeUSKPrivateKey(usk: String?)
fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo?

View file

@ -366,6 +366,25 @@ internal class RealmCryptoStore @Inject constructor(
}
}
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm
.where<CryptoMetadataEntity>()
},
{
PrivateKeysInfo(
master = it.xSignMasterPrivateKey,
selfSigned = it.xSignSelfSignedPrivateKey,
user = it.xSignUserPrivateKey
)
}
)
return Transformations.map(liveData) {
it.firstOrNull().toOptional()
}
}
override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) {
doRealmTransaction(realmConfiguration) { realm ->
realm.where<CryptoMetadataEntity>().findFirst()?.apply {

View file

@ -90,7 +90,8 @@ class HomeDetailFragment @Inject constructor(
unknownDeviceDetectorSharedViewModel.subscribe { state ->
state.unknownSessions.invoke()?.let { unknownDevices ->
if (state.canCrossSign && unknownDevices.isNotEmpty()) {
// Timber.v("## Detector Triggerred in fragment - ${unknownDevices.firstOrNull()}")
if (unknownDevices.firstOrNull()?.currentSessionTrust == true) {
Timber.v("## Detector - ${unknownDevices.size} Unknown sessions")
unknownDevices.forEachIndexed { index, detectionInfo ->
Timber.v("## Detector - #$index deviceId:${detectionInfo.deviceInfo.deviceId} lastSeenTs:${detectionInfo.deviceInfo.lastSeenTs} is New: ${detectionInfo.isNew}")
@ -125,6 +126,9 @@ class HomeDetailFragment @Inject constructor(
(weakCurrentActivity?.get() as? VectorBaseActivity)
?.navigator
?.requestSessionVerification(requireContext(), newest.deviceId ?: "")
unknownDeviceDetectorSharedViewModel.handle(
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) } ?: emptyList())
)
}
dismissedAction = Runnable {
unknownDeviceDetectorSharedViewModel.handle(

View file

@ -26,9 +26,11 @@ import im.vector.matrix.android.api.NoOpMatrixCallback
import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
import im.vector.matrix.rx.rx
import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyViewEvents
@ -36,19 +38,19 @@ import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.platform.VectorViewModelAction
import im.vector.riotx.features.settings.VectorPreferences
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import io.reactivex.functions.Function3
import timber.log.Timber
import java.util.concurrent.TimeUnit
data class UnknownDevicesState(
val myMatrixItem: MatrixItem.UserItem? = null,
val unknownSessions: Async<List<DeviceDetectionInfo>> = Uninitialized,
val canCrossSign: Boolean = false
val unknownSessions: Async<List<DeviceDetectionInfo>> = Uninitialized
) : MvRxState
data class DeviceDetectionInfo(
val deviceInfo: DeviceInfo,
val isNew: Boolean
val isNew: Boolean,
val currentSessionTrust: Boolean
)
class UnknownDeviceDetectorSharedViewModel(
@ -76,10 +78,13 @@ class UnknownDeviceDetectorSharedViewModel(
}
)
Observable.combineLatest<List<CryptoDeviceInfo>, List<DeviceInfo>, List<DeviceDetectionInfo>>(
Observable.combineLatest<List<CryptoDeviceInfo>, List<DeviceInfo>, Optional<PrivateKeysInfo>, List<DeviceDetectionInfo>>(
session.rx().liveUserCryptoDevices(session.myUserId),
session.rx().liveMyDeviceInfo(),
BiFunction { cryptoList, infoList ->
session.rx().liveCrossSigningPrivateKeys(),
Function3 { cryptoList, infoList, pInfo ->
// Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}")
// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}")
infoList
.filter { info ->
// filter verified session, by checking the crypto device info
@ -92,16 +97,15 @@ class UnknownDeviceDetectorSharedViewModel(
val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firsTimeSeenLocalTs ?: 0
DeviceDetectionInfo(
deviceInfo,
deviceKnownSince > currentSessionTs + 60_000 // short window to avoid false positive
deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive,
pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change
)
// .also {
// Timber.v("## Detector - first seen Difference with ${deviceInfo.deviceId} is ${deviceKnownSince - currentSessionTs}")
// }
}
}
)
.distinct()
.execute { async ->
// Timber.v("## Detector trigger passed distinct")
copy(
myMatrixItem = session.getUser(session.myUserId)?.toMatrixItem(),
unknownSessions = async
@ -116,11 +120,6 @@ class UnknownDeviceDetectorSharedViewModel(
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
}.disposeOnClear()
session.rx().liveCrossSigningInfo(session.myUserId)
.execute {
copy(canCrossSign = session.cryptoService().crossSigningService().canCrossSign())
}
// trigger a refresh of lastSeen / last Ip
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
}