mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-1213: Add real password check to vault item screen (#844)
This commit is contained in:
parent
2d652c8a2e
commit
91207df3fa
3 changed files with 164 additions and 82 deletions
|
@ -1,19 +0,0 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
/**
|
||||
* Models result of verifying the master password.
|
||||
*/
|
||||
sealed class VerifyPasswordResult {
|
||||
|
||||
/**
|
||||
* Master password is successfully verified.
|
||||
*/
|
||||
data class Success(
|
||||
val isVerified: Boolean,
|
||||
) : VerifyPasswordResult()
|
||||
|
||||
/**
|
||||
* An error occurred while trying to verify the master password.
|
||||
*/
|
||||
data object Error : VerifyPasswordResult()
|
||||
}
|
|
@ -8,6 +8,7 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.combineDataStates
|
||||
|
@ -15,7 +16,6 @@ import com.x8bit.bitwarden.data.platform.repository.util.map
|
|||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RestoreCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VerifyPasswordResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
@ -26,7 +26,6 @@ import com.x8bit.bitwarden.ui.vault.feature.item.util.toViewState
|
|||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -160,17 +159,8 @@ class VaultItemViewModel @Inject constructor(
|
|||
it.copy(dialog = VaultItemState.DialogState.Loading(R.string.loading.asText()))
|
||||
}
|
||||
viewModelScope.launch {
|
||||
@Suppress("MagicNumber")
|
||||
delay(2_000)
|
||||
// TODO: Actually verify the password (BIT-1213)
|
||||
sendAction(
|
||||
VaultItemAction.Internal.VerifyPasswordReceive(
|
||||
VerifyPasswordResult.Success(isVerified = true),
|
||||
),
|
||||
)
|
||||
sendEvent(
|
||||
VaultItemEvent.ShowToast("Password verification not yet implemented.".asText()),
|
||||
)
|
||||
val result = authRepository.validatePassword(action.masterPassword)
|
||||
sendAction(VaultItemAction.Internal.ValidatePasswordReceive(result))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -479,7 +469,10 @@ class VaultItemViewModel @Inject constructor(
|
|||
when (action) {
|
||||
is VaultItemAction.Internal.PasswordBreachReceive -> handlePasswordBreachReceive(action)
|
||||
is VaultItemAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
|
||||
is VaultItemAction.Internal.VerifyPasswordReceive -> handleVerifyPasswordReceive(action)
|
||||
is VaultItemAction.Internal.ValidatePasswordReceive -> handleValidatePasswordReceive(
|
||||
action,
|
||||
)
|
||||
|
||||
is VaultItemAction.Internal.DeleteCipherReceive -> handleDeleteCipherReceive(action)
|
||||
is VaultItemAction.Internal.RestoreCipherReceive -> handleRestoreCipherReceive(action)
|
||||
}
|
||||
|
@ -574,29 +567,37 @@ class VaultItemViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleVerifyPasswordReceive(
|
||||
action: VaultItemAction.Internal.VerifyPasswordReceive,
|
||||
private fun handleValidatePasswordReceive(
|
||||
action: VaultItemAction.Internal.ValidatePasswordReceive,
|
||||
) {
|
||||
when (val result = action.result) {
|
||||
VerifyPasswordResult.Error -> {
|
||||
ValidatePasswordResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = VaultItemState.DialogState.Generic(
|
||||
message = R.string.invalid_master_password.asText(),
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is VerifyPasswordResult.Success -> {
|
||||
onContent { content ->
|
||||
is ValidatePasswordResult.Success -> {
|
||||
if (result.isValid) {
|
||||
onContent { content ->
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = null,
|
||||
viewState = content.copy(
|
||||
common = content.common.copy(requiresReprompt = false),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = null,
|
||||
viewState = content.copy(
|
||||
common = content.common.copy(
|
||||
requiresReprompt = !result.isVerified,
|
||||
),
|
||||
dialog = VaultItemState.DialogState.Generic(
|
||||
message = R.string.invalid_master_password.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1198,8 +1199,8 @@ sealed class VaultItemAction {
|
|||
/**
|
||||
* Indicates that the verify password result has been received.
|
||||
*/
|
||||
data class VerifyPasswordReceive(
|
||||
val result: VerifyPasswordResult,
|
||||
data class ValidatePasswordReceive(
|
||||
val result: ValidatePasswordResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
@ -337,46 +338,145 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on MasterPasswordSubmit should verify the password`() = runTest {
|
||||
val loginViewState = createViewState(
|
||||
common = DEFAULT_COMMON.copy(requiresReprompt = false),
|
||||
)
|
||||
val mockCipherView = mockk<CipherView> {
|
||||
every {
|
||||
toViewState(
|
||||
isPremiumUser = true,
|
||||
totpCodeItemData = null,
|
||||
fun `on MasterPasswordSubmit should disabled required prompt when validatePassword success with valid password`() =
|
||||
runTest {
|
||||
val loginViewState = createViewState(
|
||||
common = DEFAULT_COMMON.copy(requiresReprompt = false),
|
||||
)
|
||||
val mockCipherView = mockk<CipherView> {
|
||||
every {
|
||||
toViewState(
|
||||
isPremiumUser = true,
|
||||
totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
val password = "password"
|
||||
coEvery {
|
||||
authRepo.validatePassword(password)
|
||||
} returns ValidatePasswordResult.Success(isValid = true)
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
|
||||
mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
|
||||
|
||||
val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
|
||||
val viewModel = createViewModel(state = loginState)
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(loginState, awaitItem())
|
||||
viewModel.trySendAction(VaultItemAction.Common.MasterPasswordSubmit(password))
|
||||
assertEquals(
|
||||
loginState.copy(
|
||||
dialog = VaultItemState.DialogState.Loading(
|
||||
message = R.string.loading.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
|
||||
mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
|
||||
|
||||
val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
|
||||
val viewModel = createViewModel(state = loginState)
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(loginState, awaitItem())
|
||||
viewModel.trySendAction(VaultItemAction.Common.MasterPasswordSubmit("password"))
|
||||
assertEquals(
|
||||
loginState.copy(
|
||||
dialog = VaultItemState.DialogState.Loading(
|
||||
message = R.string.loading.asText(),
|
||||
assertEquals(
|
||||
loginState.copy(
|
||||
viewState = loginViewState.copy(
|
||||
common = DEFAULT_COMMON.copy(requiresReprompt = false),
|
||||
),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
loginState.copy(
|
||||
viewState = loginViewState.copy(
|
||||
common = DEFAULT_COMMON.copy(requiresReprompt = false),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on MasterPasswordSubmit should show incorrect password dialog when validatePassword success with invalid password`() =
|
||||
runTest {
|
||||
val loginViewState = createViewState(
|
||||
common = DEFAULT_COMMON.copy(requiresReprompt = false),
|
||||
)
|
||||
val mockCipherView = mockk<CipherView> {
|
||||
every {
|
||||
toViewState(
|
||||
isPremiumUser = true,
|
||||
totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
val password = "password"
|
||||
coEvery {
|
||||
authRepo.validatePassword(password)
|
||||
} returns ValidatePasswordResult.Success(isValid = false)
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
|
||||
mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
|
||||
|
||||
val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
|
||||
val viewModel = createViewModel(state = loginState)
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(loginState, awaitItem())
|
||||
viewModel.trySendAction(VaultItemAction.Common.MasterPasswordSubmit(password))
|
||||
assertEquals(
|
||||
loginState.copy(
|
||||
dialog = VaultItemState.DialogState.Loading(
|
||||
message = R.string.loading.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
loginState.copy(
|
||||
dialog = VaultItemState.DialogState.Generic(
|
||||
message = R.string.invalid_master_password.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on MasterPasswordSubmit should show error dialog when validatePassword Error`() =
|
||||
runTest {
|
||||
val loginViewState = createViewState(
|
||||
common = DEFAULT_COMMON.copy(requiresReprompt = false),
|
||||
)
|
||||
val mockCipherView = mockk<CipherView> {
|
||||
every {
|
||||
toViewState(
|
||||
isPremiumUser = true,
|
||||
totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
val password = "password"
|
||||
coEvery {
|
||||
authRepo.validatePassword(password)
|
||||
} returns ValidatePasswordResult.Error
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
|
||||
mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
|
||||
|
||||
val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
|
||||
val viewModel = createViewModel(state = loginState)
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(loginState, awaitItem())
|
||||
viewModel.trySendAction(VaultItemAction.Common.MasterPasswordSubmit(password))
|
||||
assertEquals(
|
||||
loginState.copy(
|
||||
dialog = VaultItemState.DialogState.Loading(
|
||||
message = R.string.loading.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
loginState.copy(
|
||||
dialog = VaultItemState.DialogState.Generic(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on RefreshClick should sync`() = runTest {
|
||||
|
|
Loading…
Add table
Reference in a new issue