mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 09:56:45 -06:00
[PM-23681] Update TotpCodeManager to use CipherListView (#5532)
This commit is contained in:
parent
2d2a5e74da
commit
fca4ebe023
@ -0,0 +1,18 @@
|
||||
package com.x8bit.bitwarden.data.autofill.util
|
||||
|
||||
import com.bitwarden.vault.CardListView
|
||||
import com.bitwarden.vault.CipherListView
|
||||
import com.bitwarden.vault.CipherListViewType
|
||||
import com.bitwarden.vault.LoginListView
|
||||
|
||||
/**
|
||||
* Returns the [LoginListView] if the cipher is of type [CipherListViewType.Login], otherwise null.
|
||||
*/
|
||||
val CipherListView.login: LoginListView?
|
||||
get() = (this.type as? CipherListViewType.Login)?.v1
|
||||
|
||||
/**
|
||||
* Returns the [CardListView] if the cipher is of type [CipherListViewType.Card], otherwise null.
|
||||
*/
|
||||
val CipherListView.card: CardListView?
|
||||
get() = (this.type as? CipherListViewType.Card)?.v1
|
||||
@ -1,6 +1,7 @@
|
||||
package com.x8bit.bitwarden.data.vault.manager
|
||||
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.vault.CipherListView
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@ -18,6 +19,15 @@ interface TotpCodeManager {
|
||||
cipherList: List<CipherView>,
|
||||
): StateFlow<DataState<List<VerificationCodeItem>>>
|
||||
|
||||
/**
|
||||
* Flow for getting a DataState with multiple verification code items for the given
|
||||
* [cipherListViews].
|
||||
*/
|
||||
fun getTotpCodesForCipherListViewsStateFlow(
|
||||
userId: String,
|
||||
cipherListViews: List<CipherListView>,
|
||||
): StateFlow<DataState<List<VerificationCodeItem>>>
|
||||
|
||||
/**
|
||||
* Flow for getting a DataState with a single verification code item.
|
||||
*/
|
||||
@ -25,4 +35,13 @@ interface TotpCodeManager {
|
||||
userId: String,
|
||||
cipher: CipherView,
|
||||
): StateFlow<DataState<VerificationCodeItem?>>
|
||||
|
||||
/**
|
||||
* Flow for getting a DataState with a single verification code item for the given
|
||||
* [cipherListView].
|
||||
*/
|
||||
fun getTotpCodeStateFlow(
|
||||
userId: String,
|
||||
cipherListView: CipherListView,
|
||||
): StateFlow<DataState<VerificationCodeItem?>>
|
||||
}
|
||||
|
||||
@ -3,8 +3,10 @@ package com.x8bit.bitwarden.data.vault.manager
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.data.manager.DispatcherManager
|
||||
import com.bitwarden.vault.CipherListView
|
||||
import com.bitwarden.vault.CipherRepromptType
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.data.autofill.util.login
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -36,6 +38,9 @@ class TotpCodeManagerImpl(
|
||||
private val mutableVerificationCodeStateFlowMap =
|
||||
mutableMapOf<CipherView, StateFlow<DataState<VerificationCodeItem?>>>()
|
||||
|
||||
private val mutableCipherListViewVerificationCodeStateFlowMap =
|
||||
mutableMapOf<CipherListView, StateFlow<DataState<VerificationCodeItem?>>>()
|
||||
|
||||
override fun getTotpCodesStateFlow(
|
||||
userId: String,
|
||||
cipherList: List<CipherView>,
|
||||
@ -73,6 +78,43 @@ class TotpCodeManagerImpl(
|
||||
cipher = cipher,
|
||||
)
|
||||
|
||||
override fun getTotpCodesForCipherListViewsStateFlow(
|
||||
userId: String,
|
||||
cipherListViews: List<CipherListView>,
|
||||
): StateFlow<DataState<List<VerificationCodeItem>>> {
|
||||
// Generate state flows
|
||||
val stateFlows = cipherListViews.map { cipherListView ->
|
||||
getTotpCodeStateFlowInternal(userId, cipherListView)
|
||||
}
|
||||
return combine(stateFlows) { results ->
|
||||
when {
|
||||
results.any { it is DataState.Loading } -> {
|
||||
DataState.Loading
|
||||
}
|
||||
|
||||
else -> {
|
||||
DataState.Loaded(
|
||||
data = results.mapNotNull { (it as DataState.Loaded).data },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = unconfinedScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = DataState.Loading,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getTotpCodeStateFlow(
|
||||
userId: String,
|
||||
cipherListView: CipherListView,
|
||||
): StateFlow<DataState<VerificationCodeItem?>> =
|
||||
getTotpCodeStateFlowInternal(
|
||||
userId = userId,
|
||||
cipherListView = cipherListView,
|
||||
)
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun getTotpCodeStateFlowInternal(
|
||||
userId: String,
|
||||
@ -108,7 +150,6 @@ class TotpCodeManagerImpl(
|
||||
.onSuccess { response ->
|
||||
item = VerificationCodeItem(
|
||||
code = response.code,
|
||||
totpCode = totpCode,
|
||||
periodSeconds = response.period.toInt(),
|
||||
timeLeftSeconds = response.period.toInt() -
|
||||
time % response.period.toInt(),
|
||||
@ -129,12 +170,9 @@ class TotpCodeManagerImpl(
|
||||
return@flow
|
||||
}
|
||||
} else {
|
||||
item?.let {
|
||||
item = it.copy(
|
||||
timeLeftSeconds = it.periodSeconds -
|
||||
(time % it.periodSeconds),
|
||||
)
|
||||
}
|
||||
item = item.copy(
|
||||
timeLeftSeconds = item.periodSeconds - (time % item.periodSeconds),
|
||||
)
|
||||
}
|
||||
|
||||
item?.let {
|
||||
@ -154,6 +192,78 @@ class TotpCodeManagerImpl(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun getTotpCodeStateFlowInternal(
|
||||
userId: String,
|
||||
cipherListView: CipherListView?,
|
||||
): StateFlow<DataState<VerificationCodeItem?>> {
|
||||
val cipherId = cipherListView?.id ?: return MutableStateFlow(DataState.Loaded(null))
|
||||
cipherListView.login?.totp ?: return MutableStateFlow(DataState.Loaded(null))
|
||||
|
||||
return mutableCipherListViewVerificationCodeStateFlowMap.getOrPut(cipherListView) {
|
||||
// Define a per-item scope so that we can clear the Flow from the scope when it is
|
||||
// no longer needed.
|
||||
val itemScope = CoroutineScope(dispatcherManager.unconfined)
|
||||
|
||||
flow<DataState<VerificationCodeItem?>> {
|
||||
|
||||
var item: VerificationCodeItem? = null
|
||||
while (currentCoroutineContext().isActive) {
|
||||
val dateTime = clock.instant()
|
||||
val time = dateTime.epochSecond.toInt()
|
||||
if (item == null || item.isExpired(clock = clock)) {
|
||||
vaultSdkSource
|
||||
.generateTotpForCipherListView(
|
||||
cipherListView = cipherListView,
|
||||
userId = userId,
|
||||
time = dateTime,
|
||||
)
|
||||
.onSuccess { response ->
|
||||
item = VerificationCodeItem(
|
||||
code = response.code,
|
||||
periodSeconds = response.period.toInt(),
|
||||
timeLeftSeconds = response.period.toInt() -
|
||||
time % response.period.toInt(),
|
||||
issueTime = clock.millis(),
|
||||
uriLoginViewList = cipherListView.login?.uris,
|
||||
id = cipherId,
|
||||
name = cipherListView.name,
|
||||
username = cipherListView.login?.username,
|
||||
hasPasswordReprompt = when (cipherListView.reprompt) {
|
||||
CipherRepromptType.PASSWORD -> true
|
||||
CipherRepromptType.NONE -> false
|
||||
},
|
||||
orgUsesTotp = cipherListView.organizationUseTotp,
|
||||
)
|
||||
}
|
||||
.onFailure {
|
||||
emit(DataState.Loaded(null))
|
||||
return@flow
|
||||
}
|
||||
} else {
|
||||
item = item.copy(
|
||||
timeLeftSeconds = item.periodSeconds - (time % item.periodSeconds),
|
||||
)
|
||||
}
|
||||
|
||||
item?.let {
|
||||
emit(DataState.Loaded(it))
|
||||
}
|
||||
delay(ONE_SECOND_MILLISECOND)
|
||||
}
|
||||
}
|
||||
.onCompletion {
|
||||
mutableCipherListViewVerificationCodeStateFlowMap.remove(cipherListView)
|
||||
itemScope.cancel()
|
||||
}
|
||||
.stateIn(
|
||||
scope = itemScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = DataState.Loading,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun VerificationCodeItem.isExpired(clock: Clock): Boolean {
|
||||
|
||||
@ -6,7 +6,6 @@ import com.bitwarden.vault.LoginUriView
|
||||
* Models the items returned by the TotpCodeManager.
|
||||
*
|
||||
* @property code The verification code for the item.
|
||||
* @property totpCode The totp code for the item.
|
||||
* @property periodSeconds The time span where the code is valid in seconds.
|
||||
* @property timeLeftSeconds The seconds remaining until a new code is required.
|
||||
* @property issueTime The time the verification code was issued.
|
||||
@ -19,7 +18,6 @@ import com.bitwarden.vault.LoginUriView
|
||||
*/
|
||||
data class VerificationCodeItem(
|
||||
val code: String,
|
||||
val totpCode: String,
|
||||
val periodSeconds: Int,
|
||||
val timeLeftSeconds: Int,
|
||||
val issueTime: Long,
|
||||
|
||||
@ -120,7 +120,6 @@ class VaultItemViewModel @Inject constructor(
|
||||
TotpCodeItemData(
|
||||
periodSeconds = it.periodSeconds,
|
||||
timeLeftSeconds = it.timeLeftSeconds,
|
||||
totpCode = it.totpCode,
|
||||
verificationCode = it.code,
|
||||
)
|
||||
}
|
||||
|
||||
@ -9,12 +9,10 @@ import kotlinx.parcelize.Parcelize
|
||||
* @property periodSeconds The period for the verification code.
|
||||
* @property timeLeftSeconds The time left for the verification timer.
|
||||
* @property verificationCode The verification code for the item.
|
||||
* @property totpCode The totp code for the item.
|
||||
*/
|
||||
@Parcelize
|
||||
data class TotpCodeItemData(
|
||||
val periodSeconds: Int,
|
||||
val timeLeftSeconds: Int,
|
||||
val verificationCode: String,
|
||||
val totpCode: String,
|
||||
) : Parcelable
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
package com.x8bit.bitwarden.data.autofill.util
|
||||
|
||||
import com.bitwarden.vault.CipherListViewType
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCardListView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherListView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockLoginListView
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertNotNull
|
||||
import org.junit.jupiter.api.assertNull
|
||||
|
||||
class CipherListViewExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `login should return LoginListView when type is Login`() {
|
||||
val cipherListView = createMockCipherListView(
|
||||
number = 1,
|
||||
type = CipherListViewType.Login(createMockLoginListView(1)),
|
||||
)
|
||||
val loginListView = cipherListView.login
|
||||
assertNotNull(loginListView)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login should return null when type is not Login`() {
|
||||
val cipherListViews = listOf(
|
||||
createMockCipherListView(number = 1, type = CipherListViewType.SecureNote),
|
||||
createMockCipherListView(
|
||||
number = 2,
|
||||
type = CipherListViewType.Card(createMockCardListView(number = 2)),
|
||||
),
|
||||
createMockCipherListView(number = 3, type = CipherListViewType.SshKey),
|
||||
createMockCipherListView(number = 4, type = CipherListViewType.Identity),
|
||||
)
|
||||
cipherListViews.forEach { assertNull(it.login) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `card should return CardListView when type is Card`() {
|
||||
val cipherListView = createMockCipherListView(
|
||||
number = 1,
|
||||
type = CipherListViewType.Card(createMockCardListView(number = 1)),
|
||||
)
|
||||
val cardListView = cipherListView.card
|
||||
assertNotNull(cardListView)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `card should return null when type is not Card`() {
|
||||
listOf(
|
||||
createMockCipherListView(
|
||||
number = 1,
|
||||
type = CipherListViewType.Login(createMockLoginListView(1)),
|
||||
),
|
||||
createMockCipherListView(number = 2, type = CipherListViewType.SecureNote),
|
||||
createMockCipherListView(number = 3, type = CipherListViewType.SshKey),
|
||||
createMockCipherListView(number = 4, type = CipherListViewType.Identity),
|
||||
)
|
||||
.forEach { assertNull(it.card) }
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
package com.x8bit.bitwarden.data.vault.datasource.sdk
|
||||
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.DeriveKeyConnectorRequest
|
||||
import com.bitwarden.core.DerivePinKeyResponse
|
||||
import com.bitwarden.core.InitOrgCryptoRequest
|
||||
@ -71,6 +70,9 @@ import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.security.MessageDigest
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class VaultSdkSourceTest {
|
||||
@ -1030,7 +1032,7 @@ class VaultSdkSourceTest {
|
||||
val totpResponse = TotpResponse("TestCode", 30u)
|
||||
coEvery { clientVault.generateTotp(any(), any()) } returns totpResponse
|
||||
|
||||
val time = DateTime.now()
|
||||
val time = FIXED_CLOCK.instant()
|
||||
val result = vaultSdkSource.generateTotp(
|
||||
userId = userId,
|
||||
totp = "Totp",
|
||||
@ -1469,3 +1471,7 @@ private val DEFAULT_FIDO_2_AUTH_REQUEST = AuthenticateFido2CredentialRequest(
|
||||
isUserVerificationSupported = true,
|
||||
selectedCipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
@ -0,0 +1,123 @@
|
||||
package com.x8bit.bitwarden.data.vault.datasource.sdk.model
|
||||
|
||||
import com.bitwarden.vault.CardListView
|
||||
import com.bitwarden.vault.CipherListView
|
||||
import com.bitwarden.vault.CipherListViewType
|
||||
import com.bitwarden.vault.CipherPermissions
|
||||
import com.bitwarden.vault.CipherRepromptType
|
||||
import com.bitwarden.vault.CopyableCipherFields
|
||||
import com.bitwarden.vault.Fido2CredentialListView
|
||||
import com.bitwarden.vault.LocalDataView
|
||||
import com.bitwarden.vault.LoginListView
|
||||
import com.bitwarden.vault.LoginUriView
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Default date time used for [ZonedDateTime] properties of mock objects.
|
||||
*/
|
||||
private const val DEFAULT_TIMESTAMP = "2023-10-27T12:00:00Z"
|
||||
|
||||
/**
|
||||
* Creates a mock [CipherListView] for testing. Defaults to a Login cipher. Set [type] to override
|
||||
* the default behavior.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
fun createMockCipherListView(
|
||||
number: Int,
|
||||
id: String = "mockId-$number",
|
||||
organizationId: String? = "mockOrganizationId-$number",
|
||||
folderId: String? = "mockId-$number",
|
||||
type: CipherListViewType = CipherListViewType.Login(
|
||||
createMockLoginListView(number = 1),
|
||||
),
|
||||
reprompt: CipherRepromptType = CipherRepromptType.NONE,
|
||||
name: String = "mockName-$number",
|
||||
favorite: Boolean = false,
|
||||
collectionIds: List<String> = listOf("mockId-$number"),
|
||||
revisionDate: Instant = Instant.parse(DEFAULT_TIMESTAMP),
|
||||
creationDate: Instant = Instant.parse(DEFAULT_TIMESTAMP),
|
||||
attachments: UInt = 0U,
|
||||
organizationUseTotp: Boolean = false,
|
||||
edit: Boolean = false,
|
||||
viewPassword: Boolean = false,
|
||||
permissions: CipherPermissions? = createMockSdkCipherPermissions(),
|
||||
localData: LocalDataView? = null,
|
||||
key: String = "mockKey-$number",
|
||||
subtitle: String = "mockSubtitle-$number",
|
||||
hasOldAttachments: Boolean = false,
|
||||
copyableFields: List<CopyableCipherFields> = emptyList(),
|
||||
isDeleted: Boolean = false,
|
||||
): CipherListView = CipherListView(
|
||||
id = id,
|
||||
organizationId = organizationId,
|
||||
folderId = folderId,
|
||||
type = type,
|
||||
reprompt = reprompt,
|
||||
name = name,
|
||||
favorite = favorite,
|
||||
collectionIds = collectionIds,
|
||||
revisionDate = revisionDate,
|
||||
creationDate = creationDate,
|
||||
deletedDate = if (isDeleted) Instant.parse(DEFAULT_TIMESTAMP) else null,
|
||||
attachments = attachments,
|
||||
organizationUseTotp = organizationUseTotp,
|
||||
edit = edit,
|
||||
viewPassword = viewPassword,
|
||||
permissions = permissions,
|
||||
localData = localData,
|
||||
key = key,
|
||||
subtitle = subtitle,
|
||||
hasOldAttachments = hasOldAttachments,
|
||||
copyableFields = copyableFields,
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a mock [LoginListView] for testing.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
fun createMockLoginListView(
|
||||
number: Int,
|
||||
fido2Credentials: List<Fido2CredentialListView> = listOf(
|
||||
createMockFido2CredentialListView(number = 1),
|
||||
),
|
||||
hasFido2: Boolean = true,
|
||||
username: String = "mockUsername-$number",
|
||||
totp: String? = "mockTotp-$number",
|
||||
uris: List<LoginUriView> = listOf(createMockUriView(number = 1)),
|
||||
): LoginListView = LoginListView(
|
||||
fido2Credentials = fido2Credentials,
|
||||
hasFido2 = hasFido2,
|
||||
username = username,
|
||||
totp = totp,
|
||||
uris = uris,
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a mock [Fido2CredentialListView] for testing.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
fun createMockFido2CredentialListView(
|
||||
number: Int,
|
||||
credentialId: String = "mockCredentialId-$number",
|
||||
rpId: String = "mockRpId-$number",
|
||||
userHandle: String = "mockUserHandle-$number",
|
||||
userName: String = "mockUserName-$number",
|
||||
userDisplayName: String = "mockUserDisplayName-$number",
|
||||
): Fido2CredentialListView = Fido2CredentialListView(
|
||||
credentialId = credentialId,
|
||||
rpId = rpId,
|
||||
userHandle = userHandle,
|
||||
userName = userName,
|
||||
userDisplayName = userDisplayName,
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a mock [CardListView] for testing.
|
||||
*/
|
||||
fun createMockCardListView(
|
||||
number: Int,
|
||||
brand: String = "mockBrand-$number",
|
||||
): CardListView = CardListView(
|
||||
brand = brand,
|
||||
)
|
||||
@ -6,10 +6,13 @@ import com.bitwarden.core.data.util.asFailure
|
||||
import com.bitwarden.core.data.util.asSuccess
|
||||
import com.bitwarden.data.datasource.disk.base.FakeDispatcherManager
|
||||
import com.bitwarden.data.manager.DispatcherManager
|
||||
import com.bitwarden.vault.CipherListViewType
|
||||
import com.bitwarden.vault.CipherRepromptType
|
||||
import com.bitwarden.vault.TotpResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherListView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockLoginListView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockLoginView
|
||||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||
import com.x8bit.bitwarden.ui.vault.feature.verificationcode.util.createVerificationCodeItem
|
||||
@ -129,4 +132,125 @@ class TotpCodeManagerTest {
|
||||
assertEquals(DataState.Loaded(null), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `getTotpCodesForCipherListViewsStateFlow should have loaded data with a valid values passed in`() =
|
||||
runTest {
|
||||
val cipherListViews = listOf(
|
||||
createMockCipherListView(number = 1),
|
||||
)
|
||||
val totpResponse = TotpResponse("123456", 30u)
|
||||
coEvery {
|
||||
vaultSdkSource.generateTotpForCipherListView(
|
||||
userId = any(),
|
||||
cipherListView = any(),
|
||||
time = any(),
|
||||
)
|
||||
} returns totpResponse.asSuccess()
|
||||
|
||||
val expected = createVerificationCodeItem()
|
||||
|
||||
totpCodeManager.getTotpCodesForCipherListViewsStateFlow(userId, cipherListViews).test {
|
||||
assertEquals(DataState.Loaded(listOf(expected)), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `getTotpCodesForCipherListViewsStateFlow should have loaded data with empty list if no totp code is provided`() =
|
||||
runTest {
|
||||
val totpResponse = TotpResponse("123456", 30u)
|
||||
coEvery {
|
||||
vaultSdkSource.generateTotpForCipherListView(
|
||||
userId = any(),
|
||||
cipherListView = any(),
|
||||
time = any(),
|
||||
)
|
||||
} returns totpResponse.asSuccess()
|
||||
|
||||
val cipherListView = createMockCipherListView(
|
||||
number = 1,
|
||||
type = CipherListViewType.Login(
|
||||
createMockLoginListView(
|
||||
number = 1,
|
||||
totp = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
totpCodeManager.getTotpCodesForCipherListViewsStateFlow(userId, listOf(cipherListView))
|
||||
.test {
|
||||
assertEquals(DataState.Loaded(emptyList<VerificationCodeItem>()), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `getTotpCodesForCipherListViewsStateFlow should have loaded data with empty list if unable to generate auth code`() =
|
||||
runTest {
|
||||
coEvery {
|
||||
vaultSdkSource.generateTotpForCipherListView(
|
||||
userId = any(),
|
||||
cipherListView = any(),
|
||||
time = any(),
|
||||
)
|
||||
} returns Exception().asFailure()
|
||||
|
||||
val cipherListView = createMockCipherListView(
|
||||
number = 1,
|
||||
type = CipherListViewType.Login(createMockLoginListView(number = 1)),
|
||||
)
|
||||
|
||||
totpCodeManager.getTotpCodesForCipherListViewsStateFlow(userId, listOf(cipherListView)).test {
|
||||
assertEquals(DataState.Loaded(emptyList<VerificationCodeItem>()), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `getTotpCodeStateFlow from CipherListView should have loaded item with valid data passed in`() =
|
||||
runTest {
|
||||
val totpResponse = TotpResponse("123456", 30u)
|
||||
coEvery {
|
||||
vaultSdkSource.generateTotpForCipherListView(
|
||||
userId = any(),
|
||||
cipherListView = any(),
|
||||
time = any(),
|
||||
)
|
||||
} returns totpResponse.asSuccess()
|
||||
|
||||
val cipherListView = createMockCipherListView(
|
||||
number = 1,
|
||||
reprompt = CipherRepromptType.PASSWORD,
|
||||
)
|
||||
|
||||
val expected = createVerificationCodeItem().copy(hasPasswordReprompt = true)
|
||||
|
||||
totpCodeManager.getTotpCodeStateFlow(userId, cipherListView).test {
|
||||
assertEquals(DataState.Loaded(expected), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getTotpCodeFlow from CipherListView should have null data if unable to get item`() =
|
||||
runTest {
|
||||
val totpResponse = TotpResponse("123456", 30u)
|
||||
coEvery {
|
||||
vaultSdkSource.generateTotp(
|
||||
userId = any(),
|
||||
totp = any(),
|
||||
time = any(),
|
||||
)
|
||||
} returns totpResponse.asSuccess()
|
||||
|
||||
val cipherListView = createMockCipherListView(
|
||||
number = 1,
|
||||
type = CipherListViewType.SshKey,
|
||||
)
|
||||
|
||||
totpCodeManager.getTotpCodeStateFlow(userId, cipherListView).test {
|
||||
assertEquals(DataState.Loaded(null), awaitItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3180,7 +3180,7 @@ class VaultRepositoryTest {
|
||||
)
|
||||
|
||||
every {
|
||||
totpCodeManager.getTotpCodeStateFlow(userId = userId, any())
|
||||
totpCodeManager.getTotpCodeStateFlow(userId = userId, cipher = any())
|
||||
} returns stateFlow
|
||||
|
||||
setupDataStateFlow(userId = userId)
|
||||
|
||||
@ -2103,7 +2103,6 @@ class VaultItemScreenTest : BitwardenComposeTest() {
|
||||
periodSeconds = 30,
|
||||
timeLeftSeconds = 15,
|
||||
verificationCode = "123456",
|
||||
totpCode = "testCode",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -3226,7 +3225,6 @@ private val DEFAULT_LOGIN: VaultItemState.ViewState.Content.ItemType.Login =
|
||||
periodSeconds = 30,
|
||||
timeLeftSeconds = 15,
|
||||
verificationCode = "123456",
|
||||
totpCode = "testCode",
|
||||
),
|
||||
fido2CredentialCreationDateText = null,
|
||||
canViewTotpCode = true,
|
||||
|
||||
@ -2482,7 +2482,6 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
||||
periodSeconds = 30,
|
||||
timeLeftSeconds = 30,
|
||||
verificationCode = "123456",
|
||||
totpCode = "mockTotp-1",
|
||||
)
|
||||
|
||||
private fun setupMockUri() {
|
||||
@ -2549,8 +2548,6 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
||||
passwordRevisionDate = R.string.password_last_updated.asText("12/31/69 06:16 PM"),
|
||||
isPremiumUser = true,
|
||||
totpCodeItemData = TotpCodeItemData(
|
||||
totpCode = "otpauth://totp/Example:alice@google.com" +
|
||||
"?secret=JBSWY3DPEHPK3PXP&issuer=Example",
|
||||
verificationCode = "123456",
|
||||
timeLeftSeconds = 15,
|
||||
periodSeconds = 30,
|
||||
|
||||
@ -48,7 +48,6 @@ class CipherViewExtensionsTest {
|
||||
periodSeconds = 30,
|
||||
timeLeftSeconds = 15,
|
||||
verificationCode = "123456",
|
||||
totpCode = "testCode",
|
||||
),
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
@ -82,7 +81,6 @@ class CipherViewExtensionsTest {
|
||||
periodSeconds = 30,
|
||||
timeLeftSeconds = 15,
|
||||
verificationCode = "123456",
|
||||
totpCode = "testCode",
|
||||
),
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
@ -122,7 +120,6 @@ class CipherViewExtensionsTest {
|
||||
periodSeconds = 30,
|
||||
timeLeftSeconds = 15,
|
||||
verificationCode = "123456",
|
||||
totpCode = "testCode",
|
||||
),
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
|
||||
@ -278,7 +278,6 @@ fun createLoginContent(isEmpty: Boolean): VaultItemState.ViewState.Content.ItemT
|
||||
periodSeconds = 30,
|
||||
timeLeftSeconds = 15,
|
||||
verificationCode = "123456",
|
||||
totpCode = "testCode",
|
||||
)
|
||||
.takeUnless { isEmpty },
|
||||
fido2CredentialCreationDateText = R.string.created_x
|
||||
|
||||
@ -6,7 +6,6 @@ import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||
fun createVerificationCodeItem(number: Int = 1) =
|
||||
VerificationCodeItem(
|
||||
code = "123456",
|
||||
totpCode = "mockTotp-$number",
|
||||
periodSeconds = 30,
|
||||
id = "mockId-$number",
|
||||
issueTime = 1698408000000,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user