Merge pull request #7139 from vector-im/feature/mna/device-manager-verify-current-session

[Device management] Verify current session (PSG-722)
This commit is contained in:
Benoit Marty 2022-09-22 11:46:07 +02:00 committed by GitHub
commit 4e30bc86b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 504 additions and 30 deletions

1
changelog.d/7114.wip Normal file
View file

@ -0,0 +1 @@
[Device management] Verify current session

View file

@ -85,8 +85,7 @@ class VectorSettingsDevicesFragment :
).show(childFragmentManager, "REQPOP")
}
is DevicesViewEvents.SelfVerification -> {
VerificationBottomSheet.forSelfVerification(it.session)
.show(childFragmentManager, "REQPOP")
navigator.requestSelfSessionVerification(requireActivity())
}
is DevicesViewEvents.ShowManuallyVerify -> {
ManuallyVerifyDialog.show(requireActivity(), it.cryptoDeviceInfo) {

View file

@ -20,5 +20,6 @@ import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
sealed class DevicesAction : VectorViewModelAction {
object VerifyCurrentSession : DevicesAction()
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
}

View file

@ -18,7 +18,6 @@ package im.vector.app.features.settings.devices.v2
import im.vector.app.core.platform.VectorViewEvents
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
@ -28,7 +27,7 @@ sealed class DevicesViewEvent : VectorViewEvents {
data class RequestReAuth(val registrationFlowResponse: RegistrationFlowResponse, val lastErrorCode: String?) : DevicesViewEvent()
data class PromptRenameDevice(val deviceInfo: DeviceInfo) : DevicesViewEvent()
data class ShowVerifyDevice(val userId: String, val transactionId: String?) : DevicesViewEvent()
data class SelfVerification(val session: Session) : DevicesViewEvent()
object SelfVerification : DevicesViewEvent()
data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvent()
object PromptResetSecrets : DevicesViewEvent()
}

View file

@ -25,6 +25,7 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@ -36,6 +37,7 @@ class DevicesViewModel @AssistedInject constructor(
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
refreshDevicesUseCase: RefreshDevicesUseCase,
) : VectorSessionsListViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState, activeSessionHolder, refreshDevicesUseCase) {
@ -94,10 +96,22 @@ class DevicesViewModel @AssistedInject constructor(
override fun handle(action: DevicesAction) {
when (action) {
is DevicesAction.VerifyCurrentSession -> handleVerifyCurrentSessionAction()
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
}
}
private fun handleVerifyCurrentSessionAction() {
viewModelScope.launch {
val currentSessionCanBeVerified = checkIfCurrentSessionCanBeVerifiedUseCase.execute()
if (currentSessionCanBeVerified) {
_viewEvents.post(DevicesViewEvent.SelfVerification)
} else {
_viewEvents.post(DevicesViewEvent.PromptResetSecrets)
}
}
}
private fun handleMarkAsManuallyVerifiedAction() {
// TODO implement when needed
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2022 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.app.features.settings.devices.v2
import im.vector.app.core.di.ActiveSessionHolder
import javax.inject.Inject
class IsCurrentSessionUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder,
) {
fun execute(deviceId: String): Boolean {
val currentDeviceId = activeSessionHolder.getSafeActiveSession()?.sessionParams?.deviceId.orEmpty()
return deviceId.isNotEmpty() && deviceId == currentDeviceId
}
}

View file

@ -101,8 +101,7 @@ class VectorSettingsDevicesFragment :
).show(childFragmentManager, "REQPOP")
}
is DevicesViewEvent.SelfVerification -> {
VerificationBottomSheet.forSelfVerification(it.session)
.show(childFragmentManager, "REQPOP")
navigator.requestSelfSessionVerification(requireActivity())
}
is DevicesViewEvent.ShowManuallyVerify -> {
ManuallyVerifyDialog.show(requireActivity(), it.cryptoDeviceInfo) {
@ -227,6 +226,9 @@ class VectorSettingsDevicesFragment :
views.deviceListCurrentSession.viewDetailsButton.debouncedClicks {
currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) }
}
views.deviceListCurrentSession.viewVerifyButton.debouncedClicks {
viewModel.handle(DevicesAction.VerifyCurrentSession)
}
} ?: run {
hideCurrentSessionView()
}

View file

@ -49,6 +49,7 @@ class SessionInfoView @JvmOverloads constructor(
}
val viewDetailsButton = views.sessionInfoViewDetailsButton
val viewVerifyButton = views.sessionInfoVerifySessionButton
fun render(
sessionInfoViewState: SessionInfoViewState,

View file

@ -18,4 +18,6 @@ package im.vector.app.features.settings.devices.v2.overview
import im.vector.app.core.platform.VectorViewModelAction
sealed class SessionOverviewAction : VectorViewModelAction
sealed class SessionOverviewAction : VectorViewModelAction {
object VerifySession : SessionOverviewAction()
}

View file

@ -34,6 +34,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.DrawableProvider
import im.vector.app.databinding.FragmentSessionOverviewBinding
import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
import javax.inject.Inject
@ -61,7 +62,9 @@ class SessionOverviewFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observeViewEvents()
initSessionInfoView()
initVerifyButton()
}
private fun initSessionInfoView() {
@ -70,6 +73,25 @@ class SessionOverviewFragment :
}
}
private fun initVerifyButton() {
views.sessionOverviewInfo.viewVerifyButton.debouncedClicks {
viewModel.handle(SessionOverviewAction.VerifySession)
}
}
private fun observeViewEvents() {
viewModel.observeViewEvents {
when (it) {
is SessionOverviewViewEvent.SelfVerification -> {
navigator.requestSelfSessionVerification(requireActivity())
}
is SessionOverviewViewEvent.PromptResetSecrets -> {
navigator.open4SSetup(requireActivity(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
}
}
}
}
override fun onDestroyView() {
cleanUpSessionInfoView()
super.onDestroyView()

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2022 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.app.features.settings.devices.v2.overview
import im.vector.app.core.platform.VectorViewEvents
sealed class SessionOverviewViewEvent : VectorViewEvents {
object SelfVerification : SessionOverviewViewEvent()
object PromptResetSecrets : SessionOverviewViewEvent()
}

View file

@ -23,17 +23,19 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.settings.devices.v2.IsCurrentSessionUseCase
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.Session
import kotlinx.coroutines.launch
class SessionOverviewViewModel @AssistedInject constructor(
@Assisted val initialState: SessionOverviewViewState,
session: Session,
private val isCurrentSessionUseCase: IsCurrentSessionUseCase,
private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase,
) : VectorViewModel<SessionOverviewViewState, SessionOverviewAction, EmptyViewEvents>(initialState) {
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
) : VectorViewModel<SessionOverviewViewState, SessionOverviewAction, SessionOverviewViewEvent>(initialState) {
companion object : MavericksViewModelFactory<SessionOverviewViewModel, SessionOverviewViewState> by hiltMavericksViewModelFactory()
@ -43,14 +45,16 @@ class SessionOverviewViewModel @AssistedInject constructor(
}
init {
val currentDeviceId = session.sessionParams.deviceId.orEmpty()
setState {
copy(isCurrentSession = deviceId.isNotEmpty() && deviceId == currentDeviceId)
copy(isCurrentSession = isCurrentSession(deviceId))
}
observeSessionInfo(initialState.deviceId)
}
private fun isCurrentSession(deviceId: String): Boolean {
return isCurrentSessionUseCase.execute(deviceId)
}
private fun observeSessionInfo(deviceId: String) {
getDeviceFullInfoUseCase.execute(deviceId)
.onEach { setState { copy(deviceInfo = Success(it)) } }
@ -58,6 +62,25 @@ class SessionOverviewViewModel @AssistedInject constructor(
}
override fun handle(action: SessionOverviewAction) {
TODO("Implement when adding the first action")
when (action) {
is SessionOverviewAction.VerifySession -> handleVerifySessionAction()
}
}
private fun handleVerifySessionAction() = withState { viewState ->
if (isCurrentSession(viewState.deviceId)) {
handleVerifyCurrentSession()
}
}
private fun handleVerifyCurrentSession() {
viewModelScope.launch {
val currentSessionCanBeVerified = checkIfCurrentSessionCanBeVerifiedUseCase.execute()
if (currentSessionCanBeVerified) {
_viewEvents.post(SessionOverviewViewEvent.SelfVerification)
} else {
_viewEvents.post(SessionOverviewViewEvent.PromptResetSecrets)
}
}
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2022 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.app.features.settings.devices.v2.verification
import im.vector.app.core.di.ActiveSessionHolder
import kotlinx.coroutines.flow.firstOrNull
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.flow.flow
import timber.log.Timber
import javax.inject.Inject
class CheckIfCurrentSessionCanBeVerifiedUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder,
) {
suspend fun execute(): Boolean {
val session = activeSessionHolder.getSafeActiveSession()
val cryptoSessionsCount = session?.flow()
?.liveUserCryptoDevices(session.myUserId)
?.firstOrNull()
?.size
?: 0
val hasOtherSessions = cryptoSessionsCount > 1
val isRecoverySetup = session
?.sharedSecretStorageService()
?.isRecoverySetup()
.orFalse()
Timber.d("hasOtherSessions=$hasOtherSessions (otherSessionsCount=$cryptoSessionsCount), isRecoverySetup=$isRecoverySetup")
return hasOtherSessions || isRecoverySetup
}
}

View file

@ -18,6 +18,7 @@ package im.vector.app.features.settings.devices.v2
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.test.MvRxTestRule
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakeVerificationService
import im.vector.app.test.test
@ -45,6 +46,7 @@ class DevicesViewModelTest {
private val getDeviceFullInfoListUseCase = mockk<GetDeviceFullInfoListUseCase>()
private val refreshDevicesUseCase = mockk<RefreshDevicesUseCase>()
private val refreshDevicesOnCryptoDevicesChangeUseCase = mockk<RefreshDevicesOnCryptoDevicesChangeUseCase>()
private val checkIfCurrentSessionCanBeVerifiedUseCase = mockk<CheckIfCurrentSessionCanBeVerifiedUseCase>()
private fun createViewModel(): DevicesViewModel {
return DevicesViewModel(
@ -53,6 +55,7 @@ class DevicesViewModelTest {
getCurrentSessionCrossSigningInfoUseCase,
getDeviceFullInfoListUseCase,
refreshDevicesOnCryptoDevicesChangeUseCase,
checkIfCurrentSessionCanBeVerifiedUseCase,
refreshDevicesUseCase,
)
}
@ -142,6 +145,54 @@ class DevicesViewModelTest {
coVerify { refreshDevicesOnCryptoDevicesChangeUseCase.execute() }
}
@Test
fun `given current session can be verified when handling verify current session action then self verification event is posted`() {
// Given
givenVerificationService()
givenCurrentSessionCrossSigningInfo()
givenDeviceFullInfoList()
givenRefreshDevicesOnCryptoDevicesChange()
val verifyCurrentSessionAction = DevicesAction.VerifyCurrentSession
coEvery { checkIfCurrentSessionCanBeVerifiedUseCase.execute() } returns true
// When
val viewModel = createViewModel()
val viewModelTest = viewModel.test()
viewModel.handle(verifyCurrentSessionAction)
// Then
viewModelTest
.assertEvent { it is DevicesViewEvent.SelfVerification }
.finish()
coVerify {
checkIfCurrentSessionCanBeVerifiedUseCase.execute()
}
}
@Test
fun `given current session cannot be verified when handling verify current session action then reset secrets event is posted`() {
// Given
givenVerificationService()
givenCurrentSessionCrossSigningInfo()
givenDeviceFullInfoList()
givenRefreshDevicesOnCryptoDevicesChange()
val verifyCurrentSessionAction = DevicesAction.VerifyCurrentSession
coEvery { checkIfCurrentSessionCanBeVerifiedUseCase.execute() } returns false
// When
val viewModel = createViewModel()
val viewModelTest = viewModel.test()
viewModel.handle(verifyCurrentSessionAction)
// Then
viewModelTest
.assertEvent { it is DevicesViewEvent.PromptResetSecrets }
.finish()
coVerify {
checkIfCurrentSessionCanBeVerifiedUseCase.execute()
}
}
private fun givenVerificationService(): FakeVerificationService {
val fakeVerificationService = fakeActiveSessionHolder
.fakeSession

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2022 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.app.features.settings.devices.v2
import im.vector.app.test.fakes.FakeActiveSessionHolder
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.amshove.kluent.shouldBe
import org.junit.Test
import org.matrix.android.sdk.api.auth.data.SessionParams
private const val A_SESSION_ID_1 = "session-id-1"
private const val A_SESSION_ID_2 = "session-id-2"
class IsCurrentSessionUseCaseTest {
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
private val isCurrentSessionUseCase = IsCurrentSessionUseCase(
activeSessionHolder = fakeActiveSessionHolder.instance,
)
@Test
fun `given the session id of the current session when checking if id is current session then result is true`() {
// Given
val sessionParams = givenIdForCurrentSession(A_SESSION_ID_1)
// When
val result = isCurrentSessionUseCase.execute(A_SESSION_ID_1)
// Then
result shouldBe true
verify { sessionParams.deviceId }
}
@Test
fun `given a session id different from the current session id when checking if id is current session then result is false`() {
// Given
val sessionParams = givenIdForCurrentSession(A_SESSION_ID_1)
// When
val result = isCurrentSessionUseCase.execute(A_SESSION_ID_2)
// Then
result shouldBe false
verify { sessionParams.deviceId }
}
@Test
fun `given no current active session when checking if id is current session then result is false`() {
// Given
fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
// When
val result = isCurrentSessionUseCase.execute(A_SESSION_ID_1)
// Then
result shouldBe false
}
private fun givenIdForCurrentSession(deviceId: String): SessionParams {
val sessionParams = mockk<SessionParams>()
every { sessionParams.deviceId } returns deviceId
fakeActiveSessionHolder.fakeSession.givenSessionParams(sessionParams)
return sessionParams
}
}

View file

@ -19,16 +19,18 @@ package im.vector.app.features.settings.devices.v2.overview
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.test.MvRxTestRule
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.test.fakes.FakeSession
import im.vector.app.features.settings.devices.v2.IsCurrentSessionUseCase
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
import im.vector.app.test.test
import im.vector.app.test.testDispatcher
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.flowOf
import org.junit.Rule
import org.junit.Test
import org.matrix.android.sdk.api.auth.data.SessionParams
private const val A_SESSION_ID = "session-id"
@ -40,24 +42,27 @@ class SessionOverviewViewModelTest {
private val args = SessionOverviewArgs(
deviceId = A_SESSION_ID
)
private val fakeSession = FakeSession()
private val isCurrentSessionUseCase = mockk<IsCurrentSessionUseCase>()
private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>()
private val checkIfCurrentSessionCanBeVerifiedUseCase = mockk<CheckIfCurrentSessionCanBeVerifiedUseCase>()
private fun createViewModel() = SessionOverviewViewModel(
initialState = SessionOverviewViewState(args),
session = fakeSession,
getDeviceFullInfoUseCase = getDeviceFullInfoUseCase
isCurrentSessionUseCase = isCurrentSessionUseCase,
getDeviceFullInfoUseCase = getDeviceFullInfoUseCase,
checkIfCurrentSessionCanBeVerifiedUseCase = checkIfCurrentSessionCanBeVerifiedUseCase,
)
@Test
fun `given the viewModel has been initialized then viewState is updated with session info`() {
// Given
val sessionParams = givenIdForSession(A_SESSION_ID)
val deviceFullInfo = mockk<DeviceFullInfo>()
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(deviceFullInfo)
val isCurrentSession = true
every { isCurrentSessionUseCase.execute(any()) } returns isCurrentSession
val expectedState = SessionOverviewViewState(
deviceId = A_SESSION_ID,
isCurrentSession = true,
isCurrentSession = isCurrentSession,
deviceInfo = Success(deviceFullInfo)
)
@ -68,14 +73,55 @@ class SessionOverviewViewModelTest {
viewModel.test()
.assertLatestState { state -> state == expectedState }
.finish()
verify { sessionParams.deviceId }
verify { getDeviceFullInfoUseCase.execute(A_SESSION_ID) }
verify {
isCurrentSessionUseCase.execute(A_SESSION_ID)
getDeviceFullInfoUseCase.execute(A_SESSION_ID)
}
}
private fun givenIdForSession(deviceId: String): SessionParams {
val sessionParams = mockk<SessionParams>()
every { sessionParams.deviceId } returns deviceId
fakeSession.givenSessionParams(sessionParams)
return sessionParams
@Test
fun `given current session can be verified when handling verify current session action then self verification event is posted`() {
// Given
val deviceFullInfo = mockk<DeviceFullInfo>()
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(deviceFullInfo)
every { isCurrentSessionUseCase.execute(any()) } returns true
val verifySessionAction = SessionOverviewAction.VerifySession
coEvery { checkIfCurrentSessionCanBeVerifiedUseCase.execute() } returns true
// When
val viewModel = createViewModel()
val viewModelTest = viewModel.test()
viewModel.handle(verifySessionAction)
// Then
viewModelTest
.assertEvent { it is SessionOverviewViewEvent.SelfVerification }
.finish()
coVerify {
checkIfCurrentSessionCanBeVerifiedUseCase.execute()
}
}
@Test
fun `given current session cannot be verified when handling verify current session action then reset secrets event is posted`() {
// Given
val deviceFullInfo = mockk<DeviceFullInfo>()
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(deviceFullInfo)
every { isCurrentSessionUseCase.execute(any()) } returns true
val verifySessionAction = SessionOverviewAction.VerifySession
coEvery { checkIfCurrentSessionCanBeVerifiedUseCase.execute() } returns false
// When
val viewModel = createViewModel()
val viewModelTest = viewModel.test()
viewModel.handle(verifySessionAction)
// Then
viewModelTest
.assertEvent { it is SessionOverviewViewEvent.PromptResetSecrets }
.finish()
coVerify {
checkIfCurrentSessionCanBeVerifiedUseCase.execute()
}
}
}

View file

@ -0,0 +1,124 @@
/*
* Copyright (c) 2022 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.app.features.settings.devices.v2.verification
import im.vector.app.test.fakes.FakeActiveSessionHolder
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import io.mockk.verify
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.flow.FlowSession
import org.matrix.android.sdk.flow.flow
class CheckIfCurrentSessionCanBeVerifiedUseCaseTest {
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
private val checkIfCurrentSessionCanBeVerifiedUseCase = CheckIfCurrentSessionCanBeVerifiedUseCase(
activeSessionHolder = fakeActiveSessionHolder.instance
)
@Before
fun setUp() {
mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt")
}
@After
fun tearDown() {
unmockkAll()
}
@Test
fun `given there are other sessions when checking if session can be verified then result is true`() = runTest {
// Given
val device1 = givenACryptoDevice()
val device2 = givenACryptoDevice()
val devices = listOf(device1, device2)
val fakeSession = fakeActiveSessionHolder.fakeSession
val flowSession = mockk<FlowSession>()
every { fakeSession.flow() } returns flowSession
every { flowSession.liveUserCryptoDevices(any()) } returns flowOf(devices)
fakeSession.fakeSharedSecretStorageService.givenIsRecoverySetupReturns(false)
// When
val result = checkIfCurrentSessionCanBeVerifiedUseCase.execute()
// Then
result shouldBeEqualTo true
verify {
flowSession.liveUserCryptoDevices(fakeSession.myUserId)
fakeSession.fakeSharedSecretStorageService.isRecoverySetup()
}
}
@Test
fun `given recovery is setup when checking if session can be verified then result is true`() = runTest {
// Given
val device1 = givenACryptoDevice()
val devices = listOf(device1)
val fakeSession = fakeActiveSessionHolder.fakeSession
val flowSession = mockk<FlowSession>()
every { fakeSession.flow() } returns flowSession
every { flowSession.liveUserCryptoDevices(any()) } returns flowOf(devices)
fakeSession.fakeSharedSecretStorageService.givenIsRecoverySetupReturns(true)
// When
val result = checkIfCurrentSessionCanBeVerifiedUseCase.execute()
// Then
result shouldBeEqualTo true
verify {
flowSession.liveUserCryptoDevices(fakeSession.myUserId)
fakeSession.fakeSharedSecretStorageService.isRecoverySetup()
}
}
@Test
fun `given recovery is not setup and there are no other sessions when checking if session can be verified then result is false`() = runTest {
// Given
val device1 = givenACryptoDevice()
val devices = listOf(device1)
val fakeSession = fakeActiveSessionHolder.fakeSession
val flowSession = mockk<FlowSession>()
every { fakeSession.flow() } returns flowSession
every { flowSession.liveUserCryptoDevices(any()) } returns flowOf(devices)
fakeSession.fakeSharedSecretStorageService.givenIsRecoverySetupReturns(false)
// When
val result = checkIfCurrentSessionCanBeVerifiedUseCase.execute()
// Then
result shouldBeEqualTo false
verify {
flowSession.liveUserCryptoDevices(fakeSession.myUserId)
fakeSession.fakeSharedSecretStorageService.isRecoverySetup()
}
}
private fun givenACryptoDevice(): CryptoDeviceInfo = mockk()
}

View file

@ -16,6 +16,8 @@
package im.vector.app.test.fakes
import io.mockk.every
import io.mockk.mockk
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.securestorage.IntegrityResult
import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult
@ -26,7 +28,7 @@ import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageServi
import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
class FakeSharedSecretStorageService : SharedSecretStorageService {
class FakeSharedSecretStorageService : SharedSecretStorageService by mockk() {
var integrityResult: IntegrityResult = IntegrityResult.Error(SharedSecretStorageError.OtherError(IllegalStateException()))
var _defaultKey: KeyInfoResult = KeyInfoResult.Error(SharedSecretStorageError.OtherError(IllegalStateException()))
@ -76,4 +78,8 @@ class FakeSharedSecretStorageService : SharedSecretStorageService {
override suspend fun requestSecret(name: String, myOtherDeviceId: String) {
TODO("Not yet implemented")
}
fun givenIsRecoverySetupReturns(isRecoverySetup: Boolean) {
every { isRecoverySetup() } returns isRecoverySetup
}
}