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:
Maxime NATUREL 2023-01-05 17:42:08 +01:00 committed by GitHub
commit f1bd9b2cf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 166 additions and 74 deletions

1
changelog.d/7853.bugfix Normal file
View File

@ -0,0 +1 @@
[Session manager] Missing info when a session does not support encryption

View File

@ -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)
} }
} }

View File

@ -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()
} }

View File

@ -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,
)

View File

@ -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

View File

@ -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()

View File

@ -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
} }
} }

View File

@ -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())
}
} }
} }

View File

@ -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)

View File

@ -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,

View File

@ -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() }

View File

@ -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,
) )
} }

View File

@ -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

View File

@ -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()
} }
} }

View File

@ -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