diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt index 44e4fef739..b8b2386663 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt @@ -5,6 +5,7 @@ import com.bitwarden.core.CipherListView import com.bitwarden.core.CipherView import com.bitwarden.core.Collection import com.bitwarden.core.CollectionView +import com.bitwarden.core.DateTime import com.bitwarden.core.DerivePinKeyResponse import com.bitwarden.core.Folder import com.bitwarden.core.FolderView @@ -15,6 +16,7 @@ import com.bitwarden.core.PasswordHistory import com.bitwarden.core.PasswordHistoryView import com.bitwarden.core.Send import com.bitwarden.core.SendView +import com.bitwarden.core.TotpResponse import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult /** @@ -251,4 +253,13 @@ interface VaultSdkSource { userId: String, passwordHistoryList: List, ): Result> + + /** + * Generate a verification code and the period using the totp code. + */ + suspend fun generateTotp( + userId: String, + totp: String, + time: DateTime, + ): Result } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt index 5c4da991ad..d75e491549 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt @@ -5,6 +5,7 @@ import com.bitwarden.core.CipherListView import com.bitwarden.core.CipherView import com.bitwarden.core.Collection import com.bitwarden.core.CollectionView +import com.bitwarden.core.DateTime import com.bitwarden.core.DerivePinKeyResponse import com.bitwarden.core.Folder import com.bitwarden.core.FolderView @@ -14,6 +15,7 @@ import com.bitwarden.core.PasswordHistory import com.bitwarden.core.PasswordHistoryView import com.bitwarden.core.Send import com.bitwarden.core.SendView +import com.bitwarden.core.TotpResponse import com.bitwarden.sdk.BitwardenException import com.bitwarden.sdk.Client import com.bitwarden.sdk.ClientVault @@ -252,6 +254,19 @@ class VaultSdkSourceImpl( .decryptList(passwordHistoryList) } + override suspend fun generateTotp( + userId: String, + totp: String, + time: DateTime, + ): Result = runCatching { + getClient(userId = userId) + .vault() + .generateTotp( + key = totp, + time = time, + ) + } + private fun getClient( userId: String, ): Client = sdkClientManager.getOrCreateClient(userId = userId) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt index a1807ee5fe..0b7549d974 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt @@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.repository import android.net.Uri import com.bitwarden.core.CipherView import com.bitwarden.core.CollectionView +import com.bitwarden.core.DateTime import com.bitwarden.core.FolderView import com.bitwarden.core.SendType import com.bitwarden.core.SendView @@ -13,6 +14,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult +import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult @@ -198,6 +200,11 @@ interface VaultRepository : VaultLockManager { */ suspend fun removePasswordSend(sendId: String): RemovePasswordSendResult + /** + * Attempt to get the verification code and the period. + */ + suspend fun generateTotp(totpCode: String, time: DateTime): GenerateTotpResult + /** * Attempt to delete a send. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt index 0732ef5c93..edae682d65 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt @@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.repository import android.net.Uri import com.bitwarden.core.CipherView import com.bitwarden.core.CollectionView +import com.bitwarden.core.DateTime import com.bitwarden.core.FolderView import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitUserCryptoMethod @@ -37,6 +38,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult +import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult @@ -593,6 +595,27 @@ class VaultRepositoryImpl( ) } + override suspend fun generateTotp( + totpCode: String, + time: DateTime, + ): GenerateTotpResult { + val userId = requireNotNull(activeUserId) + return vaultSdkSource.generateTotp( + time = time, + userId = userId, + totp = totpCode, + ) + .fold( + onSuccess = { + GenerateTotpResult.Success( + code = it.code, + periodSeconds = it.period.toInt(), + ) + }, + onFailure = { GenerateTotpResult.Error }, + ) + } + /** * Checks if the given [userId] has an associated encrypted PIN key but not a pin-protected user * key. This indicates a scenario in which a user has requested PIN unlocking but requires diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/GenerateTotpResult.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/GenerateTotpResult.kt new file mode 100644 index 0000000000..8410a5a72e --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/GenerateTotpResult.kt @@ -0,0 +1,20 @@ +package com.x8bit.bitwarden.data.vault.repository.model + +/** + * Models the result of generating a totp code. + */ +sealed class GenerateTotpResult { + + /** + * The code was generated successfully. + */ + data class Success( + val code: String, + val periodSeconds: Int, + ) : GenerateTotpResult() + + /** + * An error occurred while generating the code. + */ + data object Error : GenerateTotpResult() +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt index b27b9b18bf..fe14236ece 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt @@ -5,6 +5,7 @@ import com.bitwarden.core.CipherListView import com.bitwarden.core.CipherView import com.bitwarden.core.Collection import com.bitwarden.core.CollectionView +import com.bitwarden.core.DateTime import com.bitwarden.core.DerivePinKeyResponse import com.bitwarden.core.Folder import com.bitwarden.core.FolderView @@ -14,6 +15,7 @@ import com.bitwarden.core.PasswordHistory import com.bitwarden.core.PasswordHistoryView import com.bitwarden.core.Send import com.bitwarden.core.SendView +import com.bitwarden.core.TotpResponse import com.bitwarden.sdk.BitwardenException import com.bitwarden.sdk.Client import com.bitwarden.sdk.ClientCrypto @@ -31,6 +33,7 @@ import io.mockk.mockk import io.mockk.runs import io.mockk.verify import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -616,4 +619,32 @@ class VaultSdkSourceTest { } verify { sdkClientManager.getOrCreateClient(userId = userId) } } + + @Test + fun `generateTotp should call SDK and return a Result with correct data`() = + runTest { + val userId = "userId" + val totpResponse = TotpResponse("TestCode", 30u) + coEvery { clientVault.generateTotp(any(), any()) } returns totpResponse + + val time = DateTime.now() + val result = vaultSdkSource.generateTotp( + userId = userId, + totp = "Totp", + time = time, + ) + + assertEquals( + Result.success(totpResponse), + result, + ) + coVerify { + clientVault.generateTotp( + key = "Totp", + time = time, + ) + } + + verify { sdkClientManager.getOrCreateClient(userId = userId) } + } } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt index cb648a57cb..b94bc73522 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt @@ -4,12 +4,14 @@ import android.net.Uri import app.cash.turbine.test import com.bitwarden.core.CipherView import com.bitwarden.core.CollectionView +import com.bitwarden.core.DateTime import com.bitwarden.core.FolderView import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitUserCryptoMethod import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.SendType import com.bitwarden.core.SendView +import com.bitwarden.core.TotpResponse import com.bitwarden.crypto.Kdf import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson @@ -57,6 +59,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult +import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult @@ -2107,6 +2110,28 @@ class VaultRepositoryTest { ) } + @Test + fun `generateTotp should return a success result on getting a code`() = runTest { + val totpResponse = TotpResponse("Testcode", 30u) + coEvery { vaultSdkSource.generateTotp(any(), any(), any()) } returns Result.success( + totpResponse, + ) + fakeAuthDiskSource.userState = MOCK_USER_STATE + + val result = vaultRepository.generateTotp( + totpCode = "testCode", + time = DateTime.now(), + ) + + assertEquals( + GenerateTotpResult.Success( + code = totpResponse.code, + periodSeconds = totpResponse.period.toInt(), + ), + result, + ) + } + //region Helper functions /**