Merge pull request #7854 from vector-im/fix/mna/info-session-without-crypto-support
[Session manager] Missing info when a session does not support encryption (PSG-1074)
This commit is contained in:
commit
f1bd9b2cf3
|
@ -0,0 +1 @@
|
||||||
|
[Session manager] Missing info when a session does not support encryption
|
|
@ -142,7 +142,7 @@ class DevicesViewModel @AssistedInject constructor(
|
||||||
.map { deviceInfo ->
|
.map { deviceInfo ->
|
||||||
val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
|
val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
|
||||||
val trustLevelForShield = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
val trustLevelForShield = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||||
val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0)
|
val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs)
|
||||||
DeviceFullInfo(deviceInfo, cryptoDeviceInfo, trustLevelForShield, isInactive)
|
DeviceFullInfo(deviceInfo, cryptoDeviceInfo, trustLevelForShield, isInactive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
@ -32,10 +31,10 @@ import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthN
|
||||||
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase
|
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase
|
||||||
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
||||||
import im.vector.app.features.settings.devices.v2.verification.GetCurrentSessionCrossSigningInfoUseCase
|
import im.vector.app.features.settings.devices.v2.verification.GetCurrentSessionCrossSigningInfoUseCase
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
|
||||||
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@ -103,27 +102,27 @@ class DevicesViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeDevices() {
|
private fun observeDevices() {
|
||||||
getDeviceFullInfoListUseCase.execute(
|
val allSessionsFlow = getDeviceFullInfoListUseCase.execute(
|
||||||
filterType = DeviceManagerFilterType.ALL_SESSIONS,
|
filterType = DeviceManagerFilterType.ALL_SESSIONS,
|
||||||
excludeCurrentDevice = false
|
excludeCurrentDevice = false,
|
||||||
|
)
|
||||||
|
val unverifiedSessionsFlow = getDeviceFullInfoListUseCase.execute(
|
||||||
|
filterType = DeviceManagerFilterType.UNVERIFIED,
|
||||||
|
excludeCurrentDevice = true,
|
||||||
|
)
|
||||||
|
val inactiveSessionsFlow = getDeviceFullInfoListUseCase.execute(
|
||||||
|
filterType = DeviceManagerFilterType.INACTIVE,
|
||||||
|
excludeCurrentDevice = true,
|
||||||
)
|
)
|
||||||
.execute { async ->
|
|
||||||
if (async is Success) {
|
|
||||||
val deviceFullInfoList = async.invoke()
|
|
||||||
val unverifiedSessionsCount = deviceFullInfoList.count { !it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse() }
|
|
||||||
val inactiveSessionsCount = deviceFullInfoList.count { it.isInactive }
|
|
||||||
|
|
||||||
copy(
|
combine(allSessionsFlow, unverifiedSessionsFlow, inactiveSessionsFlow) { allSessions, unverifiedSessions, inactiveSessions ->
|
||||||
devices = async,
|
DeviceFullInfoList(
|
||||||
unverifiedSessionsCount = unverifiedSessionsCount,
|
allSessions = allSessions,
|
||||||
inactiveSessionsCount = inactiveSessionsCount,
|
unverifiedSessionsCount = unverifiedSessions.size,
|
||||||
)
|
inactiveSessionsCount = inactiveSessions.size,
|
||||||
} else {
|
)
|
||||||
copy(
|
}
|
||||||
devices = async
|
.execute { async -> copy(devices = async) }
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshDevicesOnCryptoDevicesChange() {
|
private fun refreshDevicesOnCryptoDevicesChange() {
|
||||||
|
@ -185,6 +184,7 @@ class DevicesViewModel @AssistedInject constructor(
|
||||||
private fun getDeviceIdsOfOtherSessions(state: DevicesViewState): List<String> {
|
private fun getDeviceIdsOfOtherSessions(state: DevicesViewState): List<String> {
|
||||||
val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId
|
val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId
|
||||||
return state.devices()
|
return state.devices()
|
||||||
|
?.allSessions
|
||||||
?.mapNotNull { fullInfo -> fullInfo.deviceInfo.deviceId.takeUnless { it == currentDeviceId } }
|
?.mapNotNull { fullInfo -> fullInfo.deviceInfo.deviceId.takeUnless { it == currentDeviceId } }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,13 @@ import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCro
|
||||||
|
|
||||||
data class DevicesViewState(
|
data class DevicesViewState(
|
||||||
val currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(),
|
val currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(),
|
||||||
val devices: Async<List<DeviceFullInfo>> = Uninitialized,
|
val devices: Async<DeviceFullInfoList> = Uninitialized,
|
||||||
val unverifiedSessionsCount: Int = 0,
|
|
||||||
val inactiveSessionsCount: Int = 0,
|
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val isShowingIpAddress: Boolean = false,
|
val isShowingIpAddress: Boolean = false,
|
||||||
) : MavericksState
|
) : MavericksState
|
||||||
|
|
||||||
|
data class DeviceFullInfoList(
|
||||||
|
val allSessions: List<DeviceFullInfo>,
|
||||||
|
val unverifiedSessionsCount: Int,
|
||||||
|
val inactiveSessionsCount: Int,
|
||||||
|
)
|
||||||
|
|
|
@ -75,7 +75,7 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
|
||||||
.map { deviceInfo ->
|
.map { deviceInfo ->
|
||||||
val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
|
val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
|
||||||
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||||
val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0)
|
val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs)
|
||||||
val isCurrentDevice = currentSessionCrossSigningInfo.deviceId == cryptoDeviceInfo?.deviceId
|
val isCurrentDevice = currentSessionCrossSigningInfo.deviceId == cryptoDeviceInfo?.deviceId
|
||||||
val deviceExtendedInfo = parseDeviceUserAgentUseCase.execute(deviceInfo.getBestLastSeenUserAgent())
|
val deviceExtendedInfo = parseDeviceUserAgentUseCase.execute(deviceInfo.getBestLastSeenUserAgent())
|
||||||
val matrixClientInfo = deviceInfo.deviceId
|
val matrixClientInfo = deviceInfo.deviceId
|
||||||
|
|
|
@ -55,7 +55,6 @@ import im.vector.app.features.settings.devices.v2.signout.BuildConfirmSignoutDia
|
||||||
import im.vector.app.features.workers.signout.SignOutUiWorker
|
import im.vector.app.features.workers.signout.SignOutUiWorker
|
||||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -282,13 +281,15 @@ class VectorSettingsDevicesFragment :
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
if (state.devices is Success) {
|
if (state.devices is Success) {
|
||||||
val devices = state.devices()
|
val deviceFullInfoList = state.devices()
|
||||||
|
val devices = deviceFullInfoList?.allSessions
|
||||||
val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId
|
val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId
|
||||||
val currentDeviceInfo = devices?.firstOrNull { it.deviceInfo.deviceId == currentDeviceId }
|
val currentDeviceInfo = devices?.firstOrNull { it.deviceInfo.deviceId == currentDeviceId }
|
||||||
val isCurrentSessionVerified = currentDeviceInfo?.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted
|
|
||||||
val otherDevices = devices?.filter { it.deviceInfo.deviceId != currentDeviceId }
|
val otherDevices = devices?.filter { it.deviceInfo.deviceId != currentDeviceId }
|
||||||
|
val inactiveSessionsCount = deviceFullInfoList?.inactiveSessionsCount ?: 0
|
||||||
|
val unverifiedSessionsCount = deviceFullInfoList?.unverifiedSessionsCount ?: 0
|
||||||
|
|
||||||
renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount, isCurrentSessionVerified)
|
renderSecurityRecommendations(inactiveSessionsCount, unverifiedSessionsCount)
|
||||||
renderCurrentSessionView(currentDeviceInfo, hasOtherDevices = otherDevices?.isNotEmpty().orFalse())
|
renderCurrentSessionView(currentDeviceInfo, hasOtherDevices = otherDevices?.isNotEmpty().orFalse())
|
||||||
renderOtherSessionsView(otherDevices, state.isShowingIpAddress)
|
renderOtherSessionsView(otherDevices, state.isShowingIpAddress)
|
||||||
} else {
|
} else {
|
||||||
|
@ -303,9 +304,8 @@ class VectorSettingsDevicesFragment :
|
||||||
private fun renderSecurityRecommendations(
|
private fun renderSecurityRecommendations(
|
||||||
inactiveSessionsCount: Int,
|
inactiveSessionsCount: Int,
|
||||||
unverifiedSessionsCount: Int,
|
unverifiedSessionsCount: Int,
|
||||||
isCurrentSessionVerified: Boolean,
|
|
||||||
) {
|
) {
|
||||||
val isUnverifiedSectionVisible = unverifiedSessionsCount > 0 && isCurrentSessionVerified
|
val isUnverifiedSectionVisible = unverifiedSessionsCount > 0
|
||||||
val isInactiveSectionVisible = inactiveSessionsCount > 0
|
val isInactiveSectionVisible = inactiveSessionsCount > 0
|
||||||
if (isUnverifiedSectionVisible.not() && isInactiveSectionVisible.not()) {
|
if (isUnverifiedSectionVisible.not() && isInactiveSectionVisible.not()) {
|
||||||
hideSecurityRecommendations()
|
hideSecurityRecommendations()
|
||||||
|
|
|
@ -37,7 +37,9 @@ class FilterDevicesUseCase @Inject constructor() {
|
||||||
// when current session is not verified, other session status cannot be trusted
|
// when current session is not verified, other session status cannot be trusted
|
||||||
DeviceManagerFilterType.VERIFIED -> isCurrentSessionVerified && it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
|
DeviceManagerFilterType.VERIFIED -> isCurrentSessionVerified && it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
|
||||||
// when current session is not verified, other session status cannot be trusted
|
// when current session is not verified, other session status cannot be trusted
|
||||||
DeviceManagerFilterType.UNVERIFIED -> isCurrentSessionVerified && !it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
|
DeviceManagerFilterType.UNVERIFIED ->
|
||||||
|
(isCurrentSessionVerified && !it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()) ||
|
||||||
|
it.cryptoDeviceInfo == null
|
||||||
DeviceManagerFilterType.INACTIVE -> it.isInactive
|
DeviceManagerFilterType.INACTIVE -> it.isInactive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,13 @@ class CheckIfSessionIsInactiveUseCase @Inject constructor(
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(lastSeenTs: Long): Boolean {
|
fun execute(lastSeenTsMillis: Long?): Boolean {
|
||||||
// In case of the server doesn't send the last seen date.
|
return if (lastSeenTsMillis == null || lastSeenTsMillis <= 0) {
|
||||||
if (lastSeenTs == 0L) return true
|
// in these situations we cannot say anything about the inactivity of the session
|
||||||
|
false
|
||||||
val diffMilliseconds = clock.epochMillis() - lastSeenTs
|
} else {
|
||||||
return diffMilliseconds >= TimeUnit.DAYS.toMillis(SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS.toLong())
|
val diffMilliseconds = clock.epochMillis() - lastSeenTsMillis
|
||||||
|
diffMilliseconds >= TimeUnit.DAYS.toMillis(SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS.toLong())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,12 +63,13 @@ class OtherSessionsController @Inject constructor(
|
||||||
}
|
}
|
||||||
val drawableColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
val drawableColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
||||||
val descriptionDrawable = if (device.isInactive) host.drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor) else null
|
val descriptionDrawable = if (device.isInactive) host.drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor) else null
|
||||||
|
val sessionName = device.deviceInfo.displayName ?: device.deviceInfo.deviceId
|
||||||
|
|
||||||
otherSessionItem {
|
otherSessionItem {
|
||||||
id(device.deviceInfo.deviceId)
|
id(device.deviceInfo.deviceId)
|
||||||
deviceType(device.deviceExtendedInfo.deviceType)
|
deviceType(device.deviceExtendedInfo.deviceType)
|
||||||
roomEncryptionTrustLevel(device.roomEncryptionTrustLevel)
|
roomEncryptionTrustLevel(device.roomEncryptionTrustLevel)
|
||||||
sessionName(device.deviceInfo.displayName)
|
sessionName(sessionName)
|
||||||
sessionDescription(description)
|
sessionDescription(description)
|
||||||
sessionDescriptionDrawable(descriptionDrawable)
|
sessionDescriptionDrawable(descriptionDrawable)
|
||||||
sessionDescriptionColor(descriptionColor)
|
sessionDescriptionColor(descriptionColor)
|
||||||
|
|
|
@ -62,9 +62,10 @@ class SessionInfoView @JvmOverloads constructor(
|
||||||
stringProvider: StringProvider,
|
stringProvider: StringProvider,
|
||||||
) {
|
) {
|
||||||
renderDeviceInfo(
|
renderDeviceInfo(
|
||||||
sessionInfoViewState.deviceFullInfo.deviceInfo.displayName.orEmpty(),
|
sessionName = sessionInfoViewState.deviceFullInfo.deviceInfo.displayName
|
||||||
sessionInfoViewState.deviceFullInfo.deviceExtendedInfo.deviceType,
|
?: sessionInfoViewState.deviceFullInfo.deviceInfo.deviceId.orEmpty(),
|
||||||
stringProvider,
|
deviceType = sessionInfoViewState.deviceFullInfo.deviceExtendedInfo.deviceType,
|
||||||
|
stringProvider = stringProvider,
|
||||||
)
|
)
|
||||||
renderVerificationStatus(
|
renderVerificationStatus(
|
||||||
sessionInfoViewState.deviceFullInfo.roomEncryptionTrustLevel,
|
sessionInfoViewState.deviceFullInfo.roomEncryptionTrustLevel,
|
||||||
|
|
|
@ -49,10 +49,10 @@ class GetDeviceFullInfoUseCase @Inject constructor(
|
||||||
) { currentSessionCrossSigningInfo, deviceInfo, cryptoDeviceInfo ->
|
) { currentSessionCrossSigningInfo, deviceInfo, cryptoDeviceInfo ->
|
||||||
val info = deviceInfo.getOrNull()
|
val info = deviceInfo.getOrNull()
|
||||||
val cryptoInfo = cryptoDeviceInfo.getOrNull()
|
val cryptoInfo = cryptoDeviceInfo.getOrNull()
|
||||||
val fullInfo = if (info != null && cryptoInfo != null) {
|
val fullInfo = if (info != null) {
|
||||||
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoInfo)
|
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoInfo)
|
||||||
val isInactive = checkIfSessionIsInactiveUseCase.execute(info.lastSeenTs ?: 0)
|
val isInactive = checkIfSessionIsInactiveUseCase.execute(info.lastSeenTs)
|
||||||
val isCurrentDevice = currentSessionCrossSigningInfo.deviceId == cryptoInfo.deviceId
|
val isCurrentDevice = currentSessionCrossSigningInfo.deviceId == info.deviceId
|
||||||
val deviceUserAgent = parseDeviceUserAgentUseCase.execute(info.getBestLastSeenUserAgent())
|
val deviceUserAgent = parseDeviceUserAgentUseCase.execute(info.getBestLastSeenUserAgent())
|
||||||
val matrixClientInfo = info.deviceId
|
val matrixClientInfo = info.deviceId
|
||||||
?.takeIf { it.isNotEmpty() }
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.test.MavericksTestRule
|
import com.airbnb.mvrx.test.MavericksTestRule
|
||||||
import im.vector.app.core.session.clientinfo.MatrixClientInfoContent
|
import im.vector.app.core.session.clientinfo.MatrixClientInfoContent
|
||||||
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
|
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
|
||||||
|
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||||
import im.vector.app.features.settings.devices.v2.list.DeviceType
|
import im.vector.app.features.settings.devices.v2.list.DeviceType
|
||||||
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
||||||
import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCrossSigningInfo
|
import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCrossSigningInfo
|
||||||
|
@ -176,10 +177,7 @@ class DevicesViewModelTest {
|
||||||
val viewModelTest = createViewModel().test()
|
val viewModelTest = createViewModel().test()
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
viewModelTest.assertLatestState {
|
viewModelTest.assertLatestState { it.devices is Success && it.devices.invoke() == deviceFullInfoList }
|
||||||
it.devices is Success && it.devices.invoke() == deviceFullInfoList &&
|
|
||||||
it.inactiveSessionsCount == 1 && it.unverifiedSessionsCount == 1
|
|
||||||
}
|
|
||||||
viewModelTest.finish()
|
viewModelTest.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,7 +401,7 @@ class DevicesViewModelTest {
|
||||||
/**
|
/**
|
||||||
* Generate mocked deviceFullInfo list with 1 unverified and inactive + 1 verified and active.
|
* Generate mocked deviceFullInfo list with 1 unverified and inactive + 1 verified and active.
|
||||||
*/
|
*/
|
||||||
private fun givenDeviceFullInfoList(deviceId1: String, deviceId2: String): List<DeviceFullInfo> {
|
private fun givenDeviceFullInfoList(deviceId1: String, deviceId2: String): DeviceFullInfoList {
|
||||||
val verifiedCryptoDeviceInfo = mockk<CryptoDeviceInfo>()
|
val verifiedCryptoDeviceInfo = mockk<CryptoDeviceInfo>()
|
||||||
every { verifiedCryptoDeviceInfo.trustLevel } returns DeviceTrustLevel(crossSigningVerified = true, locallyVerified = true)
|
every { verifiedCryptoDeviceInfo.trustLevel } returns DeviceTrustLevel(crossSigningVerified = true, locallyVerified = true)
|
||||||
val unverifiedCryptoDeviceInfo = mockk<CryptoDeviceInfo>()
|
val unverifiedCryptoDeviceInfo = mockk<CryptoDeviceInfo>()
|
||||||
|
@ -432,10 +430,15 @@ class DevicesViewModelTest {
|
||||||
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
|
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
|
||||||
matrixClientInfo = MatrixClientInfoContent(),
|
matrixClientInfo = MatrixClientInfoContent(),
|
||||||
)
|
)
|
||||||
val deviceFullInfoList = listOf(deviceFullInfo1, deviceFullInfo2)
|
val devices = listOf(deviceFullInfo1, deviceFullInfo2)
|
||||||
val deviceFullInfoListFlow = flowOf(deviceFullInfoList)
|
every { getDeviceFullInfoListUseCase.execute(DeviceManagerFilterType.ALL_SESSIONS, any()) } returns flowOf(devices)
|
||||||
every { getDeviceFullInfoListUseCase.execute(any(), any()) } returns deviceFullInfoListFlow
|
every { getDeviceFullInfoListUseCase.execute(DeviceManagerFilterType.UNVERIFIED, any()) } returns flowOf(listOf(deviceFullInfo2))
|
||||||
return deviceFullInfoList
|
every { getDeviceFullInfoListUseCase.execute(DeviceManagerFilterType.INACTIVE, any()) } returns flowOf(listOf(deviceFullInfo1))
|
||||||
|
return DeviceFullInfoList(
|
||||||
|
allSessions = devices,
|
||||||
|
unverifiedSessionsCount = 1,
|
||||||
|
inactiveSessionsCount = 1,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun givenInitialViewState(deviceId1: String, deviceId2: String): DevicesViewState {
|
private fun givenInitialViewState(deviceId1: String, deviceId2: String): DevicesViewState {
|
||||||
|
@ -444,8 +447,6 @@ class DevicesViewModelTest {
|
||||||
return DevicesViewState(
|
return DevicesViewState(
|
||||||
currentSessionCrossSigningInfo = currentSessionCrossSigningInfo,
|
currentSessionCrossSigningInfo = currentSessionCrossSigningInfo,
|
||||||
devices = Success(deviceFullInfoList),
|
devices = Success(deviceFullInfoList),
|
||||||
unverifiedSessionsCount = 1,
|
|
||||||
inactiveSessionsCount = 1,
|
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtende
|
||||||
import im.vector.app.features.settings.devices.v2.list.DeviceType
|
import im.vector.app.features.settings.devices.v2.list.DeviceType
|
||||||
import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCrossSigningInfo
|
import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCrossSigningInfo
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldContain
|
||||||
import org.amshove.kluent.shouldContainAll
|
import org.amshove.kluent.shouldContainAll
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
@ -82,11 +83,22 @@ private val inactiveUnverifiedDevice = DeviceFullInfo(
|
||||||
matrixClientInfo = MatrixClientInfoContent(),
|
matrixClientInfo = MatrixClientInfoContent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val deviceWithoutEncryptionSupport = DeviceFullInfo(
|
||||||
|
deviceInfo = DeviceInfo(deviceId = "DEVICE_WITHOUT_ENCRYPTION_SUPPORT"),
|
||||||
|
cryptoDeviceInfo = null,
|
||||||
|
roomEncryptionTrustLevel = null,
|
||||||
|
isInactive = false,
|
||||||
|
isCurrentDevice = false,
|
||||||
|
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.UNKNOWN),
|
||||||
|
matrixClientInfo = MatrixClientInfoContent(),
|
||||||
|
)
|
||||||
|
|
||||||
private val devices = listOf(
|
private val devices = listOf(
|
||||||
activeVerifiedDevice,
|
activeVerifiedDevice,
|
||||||
inactiveVerifiedDevice,
|
inactiveVerifiedDevice,
|
||||||
activeUnverifiedDevice,
|
activeUnverifiedDevice,
|
||||||
inactiveUnverifiedDevice,
|
inactiveUnverifiedDevice,
|
||||||
|
deviceWithoutEncryptionSupport,
|
||||||
)
|
)
|
||||||
|
|
||||||
class FilterDevicesUseCaseTest {
|
class FilterDevicesUseCaseTest {
|
||||||
|
@ -123,8 +135,8 @@ class FilterDevicesUseCaseTest {
|
||||||
val currentSessionCrossSigningInfo = givenCurrentSessionVerified(true)
|
val currentSessionCrossSigningInfo = givenCurrentSessionVerified(true)
|
||||||
val filteredDeviceList = filterDevicesUseCase.execute(currentSessionCrossSigningInfo, devices, DeviceManagerFilterType.UNVERIFIED, emptyList())
|
val filteredDeviceList = filterDevicesUseCase.execute(currentSessionCrossSigningInfo, devices, DeviceManagerFilterType.UNVERIFIED, emptyList())
|
||||||
|
|
||||||
filteredDeviceList.size shouldBeEqualTo 2
|
filteredDeviceList.size shouldBeEqualTo 3
|
||||||
filteredDeviceList shouldContainAll listOf(activeUnverifiedDevice, inactiveUnverifiedDevice)
|
filteredDeviceList shouldContainAll listOf(activeUnverifiedDevice, inactiveUnverifiedDevice, deviceWithoutEncryptionSupport)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -132,7 +144,8 @@ class FilterDevicesUseCaseTest {
|
||||||
val currentSessionCrossSigningInfo = givenCurrentSessionVerified(false)
|
val currentSessionCrossSigningInfo = givenCurrentSessionVerified(false)
|
||||||
val filteredDeviceList = filterDevicesUseCase.execute(currentSessionCrossSigningInfo, devices, DeviceManagerFilterType.UNVERIFIED, emptyList())
|
val filteredDeviceList = filterDevicesUseCase.execute(currentSessionCrossSigningInfo, devices, DeviceManagerFilterType.UNVERIFIED, emptyList())
|
||||||
|
|
||||||
filteredDeviceList.size shouldBeEqualTo 0
|
filteredDeviceList.size shouldBeEqualTo 1
|
||||||
|
filteredDeviceList shouldContain deviceWithoutEncryptionSupport
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -17,43 +17,69 @@
|
||||||
package im.vector.app.features.settings.devices.v2.list
|
package im.vector.app.features.settings.devices.v2.list
|
||||||
|
|
||||||
import im.vector.app.test.fakes.FakeClock
|
import im.vector.app.test.fakes.FakeClock
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeFalse
|
||||||
|
import org.amshove.kluent.shouldBeTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
private const val A_TIMESTAMP = 1654689143L
|
private const val A_TIMESTAMP_MILLIS = 1654689143000L
|
||||||
|
|
||||||
class CheckIfSessionIsInactiveUseCaseTest {
|
class CheckIfSessionIsInactiveUseCaseTest {
|
||||||
|
|
||||||
private val clock = FakeClock().apply { givenEpoch(A_TIMESTAMP) }
|
private val clock = FakeClock().apply { givenEpoch(A_TIMESTAMP_MILLIS) }
|
||||||
private val checkIfSessionIsInactiveUseCase = CheckIfSessionIsInactiveUseCase(clock)
|
private val checkIfSessionIsInactiveUseCase = CheckIfSessionIsInactiveUseCase(clock)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given an old last seen date then session is inactive`() {
|
fun `given an old last seen date then session is inactive`() {
|
||||||
val lastSeenDate = A_TIMESTAMP - TimeUnit.DAYS.toMillis(SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS.toLong()) - 1
|
val lastSeenDate = A_TIMESTAMP_MILLIS - TimeUnit.DAYS.toMillis(SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS.toLong()) - 1
|
||||||
|
|
||||||
checkIfSessionIsInactiveUseCase.execute(lastSeenDate) shouldBeEqualTo true
|
val result = checkIfSessionIsInactiveUseCase.execute(lastSeenDate)
|
||||||
|
|
||||||
|
result.shouldBeTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a last seen date equal to the threshold then session is inactive`() {
|
fun `given a last seen date equal to the threshold then session is inactive`() {
|
||||||
val lastSeenDate = A_TIMESTAMP - TimeUnit.DAYS.toMillis(SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS.toLong())
|
val lastSeenDate = A_TIMESTAMP_MILLIS - TimeUnit.DAYS.toMillis(SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS.toLong())
|
||||||
|
|
||||||
checkIfSessionIsInactiveUseCase.execute(lastSeenDate) shouldBeEqualTo true
|
val result = checkIfSessionIsInactiveUseCase.execute(lastSeenDate)
|
||||||
|
|
||||||
|
result.shouldBeTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a recent last seen date then session is active`() {
|
fun `given a recent last seen date then session is active`() {
|
||||||
val lastSeenDate = A_TIMESTAMP - TimeUnit.DAYS.toMillis(SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS.toLong()) + 1
|
val lastSeenDate = A_TIMESTAMP_MILLIS - TimeUnit.DAYS.toMillis(SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS.toLong()) + 1
|
||||||
|
|
||||||
checkIfSessionIsInactiveUseCase.execute(lastSeenDate) shouldBeEqualTo false
|
val result = checkIfSessionIsInactiveUseCase.execute(lastSeenDate)
|
||||||
|
|
||||||
|
result.shouldBeFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a last seen date as zero then session is inactive`() {
|
fun `given a last seen date as zero then session is not inactive`() {
|
||||||
// In case of the server doesn't send the last seen date.
|
|
||||||
val lastSeenDate = 0L
|
val lastSeenDate = 0L
|
||||||
|
|
||||||
checkIfSessionIsInactiveUseCase.execute(lastSeenDate) shouldBeEqualTo true
|
val result = checkIfSessionIsInactiveUseCase.execute(lastSeenDate)
|
||||||
|
|
||||||
|
result.shouldBeFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a last seen date as null then session is not inactive`() {
|
||||||
|
val lastSeenDate = null
|
||||||
|
|
||||||
|
val result = checkIfSessionIsInactiveUseCase.execute(lastSeenDate)
|
||||||
|
|
||||||
|
result.shouldBeFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a last seen date as negative then session is not inactive`() {
|
||||||
|
val lastSeenDate = -3L
|
||||||
|
|
||||||
|
val result = checkIfSessionIsInactiveUseCase.execute(lastSeenDate)
|
||||||
|
|
||||||
|
result.shouldBeFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,45 @@ class GetDeviceFullInfoUseCaseTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given current session and no crypto info for device when getting device info then the result is correct`() = runTest {
|
||||||
|
// Given
|
||||||
|
val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo()
|
||||||
|
val deviceInfo = givenADeviceInfo()
|
||||||
|
val cryptoDeviceInfo = null
|
||||||
|
val trustLevel = givenTrustLevel(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||||
|
val isInactive = false
|
||||||
|
val isCurrentDevice = true
|
||||||
|
every { checkIfSessionIsInactiveUseCase.execute(any()) } returns isInactive
|
||||||
|
every { parseDeviceUserAgentUseCase.execute(any()) } returns DeviceExtendedInfo(DeviceType.MOBILE)
|
||||||
|
val matrixClientInfo = givenAMatrixClientInfo()
|
||||||
|
fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(null))
|
||||||
|
fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
deviceFullInfo shouldBeEqualTo DeviceFullInfo(
|
||||||
|
deviceInfo = deviceInfo,
|
||||||
|
cryptoDeviceInfo = cryptoDeviceInfo,
|
||||||
|
roomEncryptionTrustLevel = trustLevel,
|
||||||
|
isInactive = isInactive,
|
||||||
|
isCurrentDevice = isCurrentDevice,
|
||||||
|
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
|
||||||
|
matrixClientInfo = matrixClientInfo,
|
||||||
|
)
|
||||||
|
verify {
|
||||||
|
fakeActiveSessionHolder.instance.getSafeActiveSession()
|
||||||
|
getCurrentSessionCrossSigningInfoUseCase.execute()
|
||||||
|
getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||||
|
fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow()
|
||||||
|
fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow()
|
||||||
|
checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP)
|
||||||
|
getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given current session and no info for device when getting device info then the result is empty`() = runTest {
|
fun `given current session and no info for device when getting device info then the result is empty`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
|
@ -131,9 +170,11 @@ class GetDeviceFullInfoUseCaseTest {
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
deviceFullInfo.shouldBeNull()
|
deviceFullInfo.shouldBeNull()
|
||||||
verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
|
verify {
|
||||||
verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow() }
|
fakeActiveSessionHolder.instance.getSafeActiveSession()
|
||||||
verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow() }
|
fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow()
|
||||||
|
fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue