diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/MainViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/MainViewModel.kt index 456eb513ed..20b2438ae5 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/MainViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/MainViewModel.kt @@ -11,6 +11,7 @@ import com.bitwarden.cxf.util.getProviderImportCredentialsRequest import com.bitwarden.ui.platform.base.BaseViewModel import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme import com.bitwarden.ui.platform.manager.share.ShareManager +import com.bitwarden.ui.platform.model.TotpData import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.vault.CipherView import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManager @@ -46,7 +47,6 @@ import com.x8bit.bitwarden.ui.platform.model.FeatureFlagsState import com.x8bit.bitwarden.ui.platform.util.isAccountSecurityShortcut import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut -import com.x8bit.bitwarden.ui.vault.model.TotpData import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.FlowPreview diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorManager.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorManager.kt index 522fe0ad51..57533cdf8b 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorManager.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorManager.kt @@ -1,6 +1,6 @@ package com.x8bit.bitwarden.data.auth.manager -import com.x8bit.bitwarden.ui.vault.model.TotpData +import com.bitwarden.ui.platform.model.TotpData /** * Manager for keeping track of requests from the Bitwarden Authenticator app to add a TOTP diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorManagerImpl.kt index a8c4eeba9a..ac5d5a917b 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorManagerImpl.kt @@ -1,6 +1,6 @@ package com.x8bit.bitwarden.data.auth.manager -import com.x8bit.bitwarden.ui.vault.model.TotpData +import com.bitwarden.ui.platform.model.TotpData /** * Default in memory implementation for [AddTotpItemFromAuthenticatorManager]. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SpecialCircumstance.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SpecialCircumstance.kt index 87a4578acb..03709fc3d2 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SpecialCircumstance.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SpecialCircumstance.kt @@ -4,13 +4,13 @@ import android.os.Parcelable import androidx.credentials.CredentialManager import com.bitwarden.cxf.model.ImportCredentialsRequestData import com.bitwarden.ui.platform.manager.share.model.ShareData +import com.bitwarden.ui.platform.model.TotpData import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.credentials.model.CreateCredentialRequest import com.x8bit.bitwarden.data.credentials.model.Fido2CredentialAssertionRequest import com.x8bit.bitwarden.data.credentials.model.GetCredentialsRequest import com.x8bit.bitwarden.data.credentials.model.ProviderGetPasswordCredentialRequest -import com.x8bit.bitwarden.ui.vault.model.TotpData import kotlinx.parcelize.Parcelize /** diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensions.kt index 01451f3ac9..2f4e812e57 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensions.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.data.platform.manager.util import com.bitwarden.cxf.model.ImportCredentialsRequestData +import com.bitwarden.ui.platform.model.TotpData import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.credentials.model.CreateCredentialRequest @@ -8,7 +9,6 @@ import com.x8bit.bitwarden.data.credentials.model.Fido2CredentialAssertionReques import com.x8bit.bitwarden.data.credentials.model.GetCredentialsRequest import com.x8bit.bitwarden.data.credentials.model.ProviderGetPasswordCredentialRequest import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance -import com.x8bit.bitwarden.ui.vault.model.TotpData /** * Returns [AutofillSaveItem] when contained in the given [SpecialCircumstance]. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/processor/AuthenticatorBridgeProcessorImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/processor/AuthenticatorBridgeProcessorImpl.kt index 491e6c3dce..540b29580c 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/processor/AuthenticatorBridgeProcessorImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/processor/AuthenticatorBridgeProcessorImpl.kt @@ -17,10 +17,10 @@ import com.bitwarden.authenticatorbridge.util.toFingerprint import com.bitwarden.authenticatorbridge.util.toSymmetricEncryptionKeyData import com.bitwarden.core.util.isBuildVersionAtLeast import com.bitwarden.data.manager.DispatcherManager +import com.bitwarden.ui.platform.util.getTotpDataOrNull import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManager import com.x8bit.bitwarden.data.platform.repository.AuthenticatorBridgeRepository import com.x8bit.bitwarden.data.platform.util.createAddTotpItemFromAuthenticatorIntent -import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import timber.log.Timber diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt index 87160815ee..9340a04b60 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt @@ -13,6 +13,7 @@ import com.bitwarden.ui.platform.base.BackgroundEvent import com.bitwarden.ui.platform.base.BaseViewModel import com.bitwarden.ui.platform.components.icon.model.IconData import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData +import com.bitwarden.ui.platform.model.TotpData import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.util.Text import com.bitwarden.ui.util.asText @@ -59,7 +60,6 @@ import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType import com.x8bit.bitwarden.ui.vault.feature.vault.util.applyRestrictItemTypesPolicy import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList import com.x8bit.bitwarden.ui.vault.feature.vault.util.toVaultFilterData -import com.x8bit.bitwarden.ui.vault.model.TotpData import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType import com.x8bit.bitwarden.ui.vault.util.toVaultItemCipherType import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index c767e8de00..a992d0ad9d 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -14,6 +14,7 @@ import com.bitwarden.network.model.PolicyTypeJson import com.bitwarden.ui.platform.base.BackgroundEvent import com.bitwarden.ui.platform.base.BaseViewModel import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData +import com.bitwarden.ui.platform.model.TotpData import com.bitwarden.ui.platform.resource.BitwardenPlurals import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.util.Text @@ -76,7 +77,6 @@ import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.messageResourceId import com.x8bit.bitwarden.ui.vault.feature.util.canAssignToCollections import com.x8bit.bitwarden.ui.vault.feature.util.hasDeletePermissionInAtLeastOneCollection import com.x8bit.bitwarden.ui.vault.feature.vault.util.toCipherView -import com.x8bit.bitwarden.ui.vault.model.TotpData import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth @@ -2320,8 +2320,8 @@ data class VaultAddEditState( val shouldShowMoveToOrganization: Boolean get() = !isAddItemMode && - !isCipherInCollection && - hasOrganizations + !isCipherInCollection && + hasOrganizations /** * Enum representing the main type options for the vault, such as LOGIN, CARD, etc. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt index 190c54fd6a..521f33f80a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt @@ -5,6 +5,7 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit.util import com.bitwarden.collections.CollectionType import com.bitwarden.collections.CollectionView import com.bitwarden.core.data.util.toFormattedDateTimeStyle +import com.bitwarden.ui.platform.model.TotpData import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.util.asText import com.bitwarden.vault.CipherRepromptType @@ -19,7 +20,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem -import com.x8bit.bitwarden.ui.vault.model.TotpData import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/TotpDataExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/TotpDataExtensions.kt index 8f11ec8fd1..d430b777db 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/TotpDataExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/TotpDataExtensions.kt @@ -1,7 +1,7 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit.util +import com.bitwarden.ui.platform.model.TotpData import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState -import com.x8bit.bitwarden.ui.vault.model.TotpData /** * Returns pre-filled content that may be used for an "add" type diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index e806b10a85..cea5caf51f 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -23,6 +23,7 @@ import com.bitwarden.ui.platform.base.util.toHostOrPathOrNull import com.bitwarden.ui.platform.components.account.model.AccountSummary import com.bitwarden.ui.platform.components.icon.model.IconData import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData +import com.bitwarden.ui.platform.model.TotpData import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.util.Text import com.bitwarden.ui.util.asText @@ -100,7 +101,6 @@ import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries import com.x8bit.bitwarden.ui.vault.feature.vault.util.toActiveAccountSummary import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList -import com.x8bit.bitwarden.ui.vault.model.TotpData import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType import com.x8bit.bitwarden.ui.vault.util.toVaultItemCipherType import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt index 243bf2cb6a..5f44b3acca 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt @@ -9,6 +9,7 @@ import com.bitwarden.send.SendType import com.bitwarden.send.SendView import com.bitwarden.ui.platform.base.util.toHostOrPathOrNull import com.bitwarden.ui.platform.components.icon.model.IconData +import com.bitwarden.ui.platform.model.TotpData import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.util.asText @@ -36,7 +37,6 @@ import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType import com.x8bit.bitwarden.ui.vault.feature.vault.util.applyRestrictItemTypesPolicy import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList import com.x8bit.bitwarden.ui.vault.feature.vault.util.toLoginIconData -import com.x8bit.bitwarden.ui.vault.model.TotpData import com.x8bit.bitwarden.ui.vault.util.toSdkCipherType import java.time.Clock import java.time.format.FormatStyle diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpIntentUtils.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpIntentUtils.kt index d19fd0ef8e..152ea6a15f 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpIntentUtils.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpIntentUtils.kt @@ -1,7 +1,8 @@ package com.x8bit.bitwarden.ui.vault.util import android.content.Intent -import com.x8bit.bitwarden.ui.vault.model.TotpData +import com.bitwarden.ui.platform.model.TotpData +import com.bitwarden.ui.platform.util.getTotpDataOrNull /** * Checks if the given [Intent] contains data for a TOTP. The [TotpData] will be returned when the diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpUriUtils.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpUriUtils.kt deleted file mode 100644 index ca9ee78b1d..0000000000 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpUriUtils.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.x8bit.bitwarden.ui.vault.util - -import android.net.Uri -import com.x8bit.bitwarden.ui.vault.model.TotpData - -private const val TOTP_HOST_NAME: String = "totp" -private const val TOTP_SCHEME_NAME: String = "otpauth" -private const val PARAM_NAME_ALGORITHM: String = "algorithm" -private const val PARAM_NAME_DIGITS: String = "digits" -private const val PARAM_NAME_ISSUER: String = "issuer" -private const val PARAM_NAME_PERIOD: String = "period" -private const val PARAM_NAME_SECRET: String = "secret" - -/** - * Checks if the given [Uri] contains valid data for a TOTP. The [TotpData] will be returned when - * the correct data is present or `null` if data is invalid or missing. - */ -fun Uri.getTotpDataOrNull(): TotpData? { - // Must be a "otpauth" scheme - if (!this.scheme.equals(other = TOTP_SCHEME_NAME, ignoreCase = true)) return null - // Must be a "totp" host - if (!this.host.equals(other = TOTP_HOST_NAME, ignoreCase = true)) return null - // Must contain a "secret" - val secret = this.getQueryParameter(PARAM_NAME_SECRET)?.trim() ?: return null - val segments = this.pathSegments?.firstOrNull()?.split(":") - val segmentCount = segments?.size ?: 0 - return TotpData( - uri = this.toString(), - issuer = this.getQueryParameter(PARAM_NAME_ISSUER) - ?: segments?.firstOrNull()?.trim()?.takeIf { segmentCount > 1 }, - accountName = if (segmentCount > 1) { - segments?.getOrNull(index = 1)?.trim() - } else { - segments?.firstOrNull()?.trim() - }, - secret = secret, - digits = this.getQueryParameter(PARAM_NAME_DIGITS)?.trim()?.toIntOrNull() ?: 6, - period = this - .getQueryParameter(PARAM_NAME_PERIOD) - ?.trim() - ?.toIntOrNull() - ?.takeUnless { it <= 0 } - ?: 30, - algorithm = TotpData.CryptoHashAlgorithm - .parse(value = this.getQueryParameter(PARAM_NAME_ALGORITHM)?.trim()) - ?: TotpData.CryptoHashAlgorithm.SHA_1, - ) -} diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/MainViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/MainViewModelTest.kt index bc490c9def..c5ff58e05b 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/MainViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/MainViewModelTest.kt @@ -21,6 +21,7 @@ import com.bitwarden.ui.platform.base.BaseViewModelTest import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme import com.bitwarden.ui.platform.manager.share.ShareManager import com.bitwarden.ui.platform.manager.share.model.ShareData +import com.bitwarden.ui.platform.model.TotpData import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.vault.CipherView import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus @@ -76,7 +77,6 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLang import com.x8bit.bitwarden.ui.platform.util.isAccountSecurityShortcut import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut -import com.x8bit.bitwarden.ui.vault.model.TotpData import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull import io.mockk.coEvery import io.mockk.every diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorTest.kt index a60233c235..4de356d6e4 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/AddTotpItemFromAuthenticatorTest.kt @@ -1,6 +1,6 @@ package com.x8bit.bitwarden.data.auth.manager -import com.x8bit.bitwarden.ui.vault.model.TotpData +import com.bitwarden.ui.platform.model.TotpData import io.mockk.mockk import org.junit.Test import org.junit.jupiter.api.Assertions.assertEquals diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt index 7bd7e1ccee..31d7d0c5c9 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.platform.manager.util import androidx.core.os.bundleOf import com.bitwarden.cxf.model.ImportCredentialsRequestData +import com.bitwarden.ui.platform.model.TotpData import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.credentials.model.CreateCredentialRequest @@ -9,7 +10,6 @@ import com.x8bit.bitwarden.data.credentials.model.createMockFido2CredentialAsser import com.x8bit.bitwarden.data.credentials.model.createMockGetCredentialsRequest import com.x8bit.bitwarden.data.credentials.model.createMockProviderGetPasswordCredentialRequest import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance -import com.x8bit.bitwarden.ui.vault.model.TotpData import io.mockk.mockk import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/processor/AuthenticatorBridgeProcessorTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/processor/AuthenticatorBridgeProcessorTest.kt index ac18b0b558..d8bfbfe38c 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/processor/AuthenticatorBridgeProcessorTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/processor/AuthenticatorBridgeProcessorTest.kt @@ -20,11 +20,11 @@ import com.bitwarden.authenticatorbridge.util.toSymmetricEncryptionKeyData import com.bitwarden.core.data.util.asSuccess import com.bitwarden.core.util.isBuildVersionAtLeast import com.bitwarden.data.datasource.disk.base.FakeDispatcherManager +import com.bitwarden.ui.platform.model.TotpData +import com.bitwarden.ui.platform.util.getTotpDataOrNull import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManagerImpl import com.x8bit.bitwarden.data.platform.repository.AuthenticatorBridgeRepository import com.x8bit.bitwarden.data.platform.util.createAddTotpItemFromAuthenticatorIntent -import com.x8bit.bitwarden.ui.vault.model.TotpData -import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt index baba75b44f..39f1b83f0d 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt @@ -20,6 +20,7 @@ import com.bitwarden.network.model.SyncResponseJson import com.bitwarden.send.SendView import com.bitwarden.ui.platform.base.BaseViewModelTest import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData +import com.bitwarden.ui.platform.model.TotpData import com.bitwarden.ui.platform.resource.BitwardenPlurals import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.util.Text @@ -87,7 +88,6 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem import com.x8bit.bitwarden.ui.vault.feature.addedit.util.createMockPasskeyAttestationOptions import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toDefaultAddTypeContent import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState -import com.x8bit.bitwarden.ui.vault.model.TotpData import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/TotpDataExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/TotpDataExtensionsTest.kt index e17177fb33..5fb6ab3d06 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/TotpDataExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/TotpDataExtensionsTest.kt @@ -1,7 +1,7 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit.util +import com.bitwarden.ui.platform.model.TotpData import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState -import com.x8bit.bitwarden.ui.vault.model.TotpData import io.mockk.every import io.mockk.mockkStatic import io.mockk.unmockkStatic diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index 78e09ee31d..50183e43fe 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -31,6 +31,7 @@ import com.bitwarden.ui.platform.base.BaseViewModelTest import com.bitwarden.ui.platform.components.account.model.AccountSummary import com.bitwarden.ui.platform.components.icon.model.IconData import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData +import com.bitwarden.ui.platform.model.TotpData import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.util.Text @@ -110,7 +111,6 @@ import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.createMockDisplayIt import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries import com.x8bit.bitwarden.ui.vault.feature.vault.util.toActiveAccountSummary -import com.x8bit.bitwarden.ui.vault.model.TotpData import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType import io.mockk.Ordering diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt index 9ed628fde2..f48e3f1c05 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt @@ -10,6 +10,7 @@ import com.bitwarden.data.repository.util.baseWebSendUrl import com.bitwarden.send.SendType import com.bitwarden.send.SendView import com.bitwarden.ui.platform.components.icon.model.IconData +import com.bitwarden.ui.platform.model.TotpData import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.util.asText @@ -33,7 +34,6 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType -import com.x8bit.bitwarden.ui.vault.model.TotpData import io.mockk.every import io.mockk.mockk import io.mockk.mockkObject diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpIntentUtilsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpIntentUtilsTest.kt index ace7c1a3f3..e372dfa6b5 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpIntentUtilsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpIntentUtilsTest.kt @@ -2,7 +2,8 @@ package com.x8bit.bitwarden.ui.vault.util import android.content.Intent import android.net.Uri -import com.x8bit.bitwarden.ui.vault.model.TotpData +import com.bitwarden.ui.platform.model.TotpData +import com.bitwarden.ui.platform.util.getTotpDataOrNull import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/StringExtensions.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/StringExtensions.kt index 60682bd036..00ff9947e9 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/StringExtensions.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/StringExtensions.kt @@ -242,7 +242,4 @@ fun String.prefixHttpsIfNecessary(): String = /** * Checks if a string is using base32 digits. */ -fun String.isBase32(): Boolean { - val regex = ("^[A-Z2-7]+=*$").toRegex() - return regex.matches(this) -} +fun String.isBase32(): Boolean = "^[A-Za-z2-7]+=*$".toRegex().matches(this) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/model/TotpData.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/model/TotpData.kt similarity index 97% rename from app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/model/TotpData.kt rename to ui/src/main/kotlin/com/bitwarden/ui/platform/model/TotpData.kt index b6e3dccf52..4a63dd84c7 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/model/TotpData.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/model/TotpData.kt @@ -1,4 +1,4 @@ -package com.x8bit.bitwarden.ui.vault.model +package com.bitwarden.ui.platform.model import android.os.Parcelable import kotlinx.parcelize.Parcelize diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/util/TotpUriUtils.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/util/TotpUriUtils.kt new file mode 100644 index 0000000000..db0dadf4d5 --- /dev/null +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/util/TotpUriUtils.kt @@ -0,0 +1,109 @@ +package com.bitwarden.ui.platform.util + +import android.net.Uri +import com.bitwarden.ui.platform.base.util.isBase32 +import com.bitwarden.ui.platform.model.TotpData + +private const val TOTP_HOST_NAME: String = "totp" +private const val TOTP_SCHEME_NAME: String = "otpauth" +private const val PARAM_NAME_ALGORITHM: String = "algorithm" +private const val PARAM_NAME_DIGITS: String = "digits" +private const val PARAM_NAME_ISSUER: String = "issuer" +private const val PARAM_NAME_PERIOD: String = "period" +private const val PARAM_NAME_SECRET: String = "secret" + +/** + * Checks if the given [Uri] contains valid data for a TOTP. The [TotpData] will be returned when + * the correct data is present or `null` if data is invalid or missing. + */ +fun Uri.getTotpDataOrNull(): TotpData? { + // Must be a "otpauth" scheme + if (!this.scheme.equals(other = TOTP_SCHEME_NAME, ignoreCase = true)) return null + // Must be a "totp" host + if (!this.host.equals(other = TOTP_HOST_NAME, ignoreCase = true)) return null + val secret = this.getSecret() ?: return null + val digits = this.getDigits() ?: return null + val period = this.getPeriod() ?: return null + val algorithm = this.getAlgorithm() ?: return null + val segments = this.pathSegments?.firstOrNull()?.split(":") + val segmentCount = segments?.size ?: 0 + return TotpData( + uri = this.toString(), + issuer = this.getQueryParameter(PARAM_NAME_ISSUER) + ?: segments?.firstOrNull()?.trim()?.takeIf { segmentCount > 1 }, + accountName = if (segmentCount > 1) { + segments?.getOrNull(index = 1)?.trim() + } else { + segments?.firstOrNull()?.trim() + }, + secret = secret, + digits = digits, + period = period, + algorithm = algorithm, + ) +} + +/** + * Attempts to extract the algorithm from the given totp [Uri]. + */ +private fun Uri.getAlgorithm(): TotpData.CryptoHashAlgorithm? { + val algorithm = this + .getQueryParameter(PARAM_NAME_ALGORITHM) + ?.trim() + ?.lowercase() + return if (algorithm == null) { + // If no value was provided, then we'll default to SHA_1. + TotpData.CryptoHashAlgorithm.SHA_1 + } else { + // If the value is unidentifiable, then it's invalid. + // If it's identifiable, then we return the valid value. + // We specifically do not use a `let` here, since we do not want to map an unidentified + // value to the default value. + TotpData.CryptoHashAlgorithm.parse(value = algorithm) + } +} + +/** + * Attempts to extract the digits from the given totp [Uri]. + */ +@Suppress("MagicNumber") +private fun Uri.getDigits(): Int? { + val digits = this.getQueryParameter(PARAM_NAME_DIGITS)?.trim()?.toIntOrNull() + return if (digits == null) { + // If no value was provided, then we'll default to 6. + 6 + } else if (digits < 1 || digits > 10) { + // If the value is less than 1 or greater than 10, then it's invalid. + null + } else { + // If the value is valid, then we'll return it. + digits + } +} + +/** + * Attempts to extract the period from the given totp [Uri]. + */ +@Suppress("MagicNumber") +private fun Uri.getPeriod(): Int? { + val period = this.getQueryParameter(PARAM_NAME_PERIOD)?.trim()?.toIntOrNull() + return if (period == null) { + // If no value was provided, then we'll default to 30. + 30 + } else if (period < 1) { + // If the value is less than 1, then it's invalid. + null + } else { + // If the value is valid, then we'll return it. + period + } +} + +/** + * Attempts to extract the secret from the given totp [Uri]. + */ +private fun Uri.getSecret(): String? = + this + .getQueryParameter(PARAM_NAME_SECRET) + ?.trim() + ?.takeIf { it.isNotEmpty() && it.isBase32() } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpUriUtilsTest.kt b/ui/src/test/kotlin/com/bitwarden/ui/platform/util/TotpUriUtilsTest.kt similarity index 61% rename from app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpUriUtilsTest.kt rename to ui/src/test/kotlin/com/bitwarden/ui/platform/util/TotpUriUtilsTest.kt index f5ac5773a8..df8d5cb854 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/util/TotpUriUtilsTest.kt +++ b/ui/src/test/kotlin/com/bitwarden/ui/platform/util/TotpUriUtilsTest.kt @@ -1,8 +1,7 @@ -package com.x8bit.bitwarden.ui.vault.util +package com.bitwarden.ui.platform.util import android.net.Uri -import com.x8bit.bitwarden.ui.vault.model.TotpData -import com.x8bit.bitwarden.ui.vault.model.TotpData.CryptoHashAlgorithm +import com.bitwarden.ui.platform.model.TotpData import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.Assertions.assertEquals @@ -41,6 +40,56 @@ class TotpUriUtilsTest { assertNull(uri.getTotpDataOrNull()) } + @Test + fun `getTotpDataOrNull with invalid secret returns null`() { + val uri = mockk { + every { scheme } returns "otpauth" + every { host } returns "totp" + every { getQueryParameter("secret") } returns "1234567890qwertyuiop" + } + + assertNull(uri.getTotpDataOrNull()) + } + + @Test + fun `getTotpDataOrNull with invalid digits returns null`() { + val uri = mockk { + every { scheme } returns "otpauth" + every { host } returns "totp" + every { getQueryParameter("secret") } returns "secret" + every { getQueryParameter("digits") } returns "11" + } + + assertNull(uri.getTotpDataOrNull()) + } + + @Test + fun `getTotpDataOrNull with invalid period returns null`() { + val uri = mockk { + every { scheme } returns "otpauth" + every { host } returns "totp" + every { getQueryParameter("secret") } returns "secret" + every { getQueryParameter("digits") } returns "5" + every { getQueryParameter("period") } returns "0" + } + + assertNull(uri.getTotpDataOrNull()) + } + + @Test + fun `getTotpDataOrNull with invalid algorithm returns null`() { + val uri = mockk { + every { scheme } returns "otpauth" + every { host } returns "totp" + every { getQueryParameter("secret") } returns "secret" + every { getQueryParameter("digits") } returns "5" + every { getQueryParameter("period") } returns "10" + every { getQueryParameter("algorithm") } returns "sha22" + } + + assertNull(uri.getTotpDataOrNull()) + } + @Test fun `getTotpDataOrNull with minimum required values returns TotpData with defaults`() { val secret = "secret" @@ -62,7 +111,7 @@ class TotpUriUtilsTest { secret = secret, digits = 6, period = 30, - algorithm = CryptoHashAlgorithm.SHA_1, + algorithm = TotpData.CryptoHashAlgorithm.SHA_1, ) assertEquals(expectedResult, uri.getTotpDataOrNull()) @@ -93,7 +142,7 @@ class TotpUriUtilsTest { secret = secret, digits = digits, period = period, - algorithm = CryptoHashAlgorithm.SHA_256, + algorithm = TotpData.CryptoHashAlgorithm.SHA_256, ) assertEquals(expectedResult, uri.getTotpDataOrNull())