From 00b35bd3ab50151ef9c07f55ccb2fbe510f77157 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com> Date: Tue, 28 May 2024 13:01:07 -0400 Subject: [PATCH] [BWA-16] Import Google Authenticator exports via scanner (#101) --- app/build.gradle.kts | 32 ++++- app/proguard-rules.pro | 6 + .../authenticator/manager/TotpCodeManager.kt | 6 + .../repository/AuthenticatorRepository.kt | 5 + .../repository/AuthenticatorRepositoryImpl.kt | 9 ++ .../repository/model/TotpCodeResult.kt | 9 +- .../manager/BitwardenEncodingManager.kt | 22 +++ .../manager/BitwardenEncodingManagerImpl.kt | 16 +++ .../manager/di/PlatformManagerModule.kt | 6 + .../feature/edititem/EditItemViewModel.kt | 4 +- .../itemlisting/ItemListingViewModel.kt | 128 +++++++++++++++--- .../feature/qrcodescan/QrCodeScanViewModel.kt | 24 +++- .../qrcodescan/util/QrCodeAnalyzerImpl.kt | 26 ++-- app/src/main/proto/google_authenticator.proto | 33 +++++ .../android/authenticator/ExampleUnitTest.kt | 16 --- build.gradle.kts | 3 +- gradle/libs.versions.toml | 12 +- 17 files changed, 296 insertions(+), 61 deletions(-) create mode 100644 app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/BitwardenEncodingManager.kt create mode 100644 app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/BitwardenEncodingManagerImpl.kt create mode 100644 app/src/main/proto/google_authenticator.proto delete mode 100644 app/src/test/java/com/x8bit/bitwarden/android/authenticator/ExampleUnitTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 11a9f3b147..2abc233bc2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import com.google.protobuf.gradle.proto + plugins { alias(libs.plugins.android.application) alias(libs.plugins.crashlytics) @@ -7,6 +9,7 @@ plugins { alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlinx.kover) alias(libs.plugins.ksp) + alias(libs.plugins.google.protobuf) alias(libs.plugins.google.services) alias(libs.plugins.sonarqube) } @@ -65,6 +68,13 @@ android { excludes += "/META-INF/{AL2.0,LGPL2.1,LICENSE*.md}" } } + sourceSets { + getByName("main") { + proto { + srcDir("src/main/proto") + } + } + } } dependencies { @@ -103,10 +113,12 @@ dependencies { implementation(libs.google.firebase.crashlytics) implementation(libs.google.hilt.android) ksp(libs.google.hilt.compiler) + implementation(libs.google.guava) + implementation(libs.google.protobuf.javalite) implementation(libs.jakewharton.retrofit.kotlinx.serialization) implementation(libs.kotlinx.collections.immutable) implementation(libs.kotlinx.coroutines.android) - implementation(libs.kotlinx.serialization) + implementation(libs.kotlinx.serialization.json) implementation(libs.square.okhttp) implementation(libs.square.okhttp.logging) implementation(libs.square.retrofit) @@ -129,6 +141,19 @@ dependencies { androidTestImplementation(libs.bundles.tests.instrumented) } +protobuf { + protoc { + artifact = libs.google.protobuf.protoc.get().toString() + } + generateProtoTasks { + this.all().forEach { task -> + task.builtins.create("java") { + option("lite") + } + } + } +} + sonar { properties { property("sonar.projectKey", "bitwarden_authenticator-android") @@ -140,7 +165,10 @@ sonar { } tasks { + withType { + useJUnitPlatform() + } getByName("sonar") { dependsOn("check") } -} +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index da42a48d7a..1648caf609 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -53,6 +53,12 @@ -keep public class * implements com.bumptech.glide.module.GlideModule -keep public class * extends com.bumptech.glide.module.AppGlideModule +################################################################################ +# Google Protobuf generated files +################################################################################ + +-keep class * extends com.google.protobuf.GeneratedMessageLite { *; } + ################################################################################ # JNA ################################################################################ diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/TotpCodeManager.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/TotpCodeManager.kt index d5c7d15fec..03226dda24 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/TotpCodeManager.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/TotpCodeManager.kt @@ -31,8 +31,14 @@ interface TotpCodeManager { const val PERIOD_PARAM = "period" const val SECRET_PARAM = "secret" const val ISSUER_PARAM = "issuer" + + /** + * URI query parameter containing export data from Google Authenticator. + */ + const val DATA_PARAM = "data" const val TOTP_CODE_PREFIX = "otpauth://totp" const val STEAM_CODE_PREFIX = "steam://" + const val GOOGLE_EXPORT_PREFIX = "otpauth-migration://" const val TOTP_DIGITS_DEFAULT = 6 const val STEAM_DIGITS_DEFAULT = 5 const val PERIOD_SECONDS_DEFAULT = 30 diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepository.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepository.kt index ede3265660..2408d722d3 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepository.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepository.kt @@ -72,6 +72,11 @@ interface AuthenticatorRepository { */ suspend fun createItem(item: AuthenticatorItemEntity): CreateItemResult + /** + * Attempt to add provided [items]. + */ + suspend fun addItems(vararg items: AuthenticatorItemEntity): CreateItemResult + /** * Attempt to delete a cipher. */ diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepositoryImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepositoryImpl.kt index 012654495c..76cc42043d 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepositoryImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepositoryImpl.kt @@ -204,6 +204,15 @@ class AuthenticatorRepositoryImpl @Inject constructor( } } + override suspend fun addItems(vararg items: AuthenticatorItemEntity): CreateItemResult { + return try { + authenticatorDiskSource.saveItem(*items) + CreateItemResult.Success + } catch (e: Exception) { + CreateItemResult.Error + } + } + override suspend fun hardDeleteItem(itemId: String): DeleteItemResult { return try { authenticatorDiskSource.deleteItem(itemId) diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/model/TotpCodeResult.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/model/TotpCodeResult.kt index accdbd68a7..0cd9ea8486 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/model/TotpCodeResult.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/model/TotpCodeResult.kt @@ -6,9 +6,14 @@ package com.bitwarden.authenticator.data.authenticator.repository.model sealed class TotpCodeResult { /** - * Code has been successfully added. + * Code containing an OTP URI has been successfully scanned. */ - data class Success(val code: String) : TotpCodeResult() + data class TotpCodeScan(val code: String) : TotpCodeResult() + + /** + * Code containing exported data from Google Authenticator was scanned. + */ + data class GoogleExportScan(val data: String) : TotpCodeResult() /** * There was an error scanning the code. diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/BitwardenEncodingManager.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/BitwardenEncodingManager.kt new file mode 100644 index 0000000000..6d1fd320e9 --- /dev/null +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/BitwardenEncodingManager.kt @@ -0,0 +1,22 @@ +package com.bitwarden.authenticator.data.platform.manager + +/** + * An interface for encoding and decoding data. + */ +interface BitwardenEncodingManager { + + /** + * Decodes '%'-escaped octets in the given string. + */ + fun uriDecode(value: String): String + + /** + * Decodes the specified [value], and returns the resulting [ByteArray]. + */ + fun base64Decode(value: String): ByteArray + + /** + * Encodes the specified [byteArray], and returns the encoded String. + */ + fun base32Encode(byteArray: ByteArray): String +} diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/BitwardenEncodingManagerImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/BitwardenEncodingManagerImpl.kt new file mode 100644 index 0000000000..76bad481da --- /dev/null +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/BitwardenEncodingManagerImpl.kt @@ -0,0 +1,16 @@ +package com.bitwarden.authenticator.data.platform.manager + +import android.net.Uri +import com.google.common.io.BaseEncoding + +/** + * Default implementation of [BitwardenEncodingManager]. + */ +class BitwardenEncodingManagerImpl : BitwardenEncodingManager { + override fun uriDecode(value: String): String = Uri.decode(value) + + override fun base64Decode(value: String): ByteArray = BaseEncoding.base64().decode(value) + + override fun base32Encode(byteArray: ByteArray): String = + BaseEncoding.base32().encode(byteArray) +} diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/di/PlatformManagerModule.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/di/PlatformManagerModule.kt index 8b177ed538..5575df95d8 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/di/PlatformManagerModule.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/di/PlatformManagerModule.kt @@ -5,6 +5,8 @@ import com.bitwarden.authenticator.data.authenticator.datasource.disk.Authentica import com.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSource import com.bitwarden.authenticator.data.platform.manager.BiometricsEncryptionManager import com.bitwarden.authenticator.data.platform.manager.BiometricsEncryptionManagerImpl +import com.bitwarden.authenticator.data.platform.manager.BitwardenEncodingManager +import com.bitwarden.authenticator.data.platform.manager.BitwardenEncodingManagerImpl import com.bitwarden.authenticator.data.platform.manager.CrashLogsManager import com.bitwarden.authenticator.data.platform.manager.CrashLogsManagerImpl import com.bitwarden.authenticator.data.platform.manager.DispatcherManager @@ -67,4 +69,8 @@ object PlatformManagerModule { fun provideImportManager( authenticatorDiskSource: AuthenticatorDiskSource, ): ImportManager = ImportManagerImpl(authenticatorDiskSource) + + @Provides + @Singleton + fun provideEncodingManager(): BitwardenEncodingManager = BitwardenEncodingManagerImpl() } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt index 2b0da2d6b9..c2dabce67f 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt @@ -87,7 +87,7 @@ class EditItemViewModel @Inject constructor( it.copy( dialog = EditItemState.DialogState.Generic( title = R.string.an_error_has_occurred.asText(), - message = R.string.validation_field_required.asText(R.string.name), + message = R.string.validation_field_required.asText(R.string.name.asText()), ) ) } @@ -97,7 +97,7 @@ class EditItemViewModel @Inject constructor( it.copy( dialog = EditItemState.DialogState.Generic( title = R.string.an_error_has_occurred.asText(), - message = R.string.validation_field_required.asText(R.string.key), + message = R.string.validation_field_required.asText(R.string.key.asText()), ) ) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt index 9268980531..0323a49241 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt @@ -7,12 +7,15 @@ import com.bitwarden.authenticator.R import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemAlgorithm import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemEntity import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemType +import com.bitwarden.authenticator.data.authenticator.manager.TotpCodeManager import com.bitwarden.authenticator.data.authenticator.manager.model.VerificationCodeItem import com.bitwarden.authenticator.data.authenticator.repository.AuthenticatorRepository import com.bitwarden.authenticator.data.authenticator.repository.model.CreateItemResult import com.bitwarden.authenticator.data.authenticator.repository.model.DeleteItemResult import com.bitwarden.authenticator.data.authenticator.repository.model.TotpCodeResult +import com.bitwarden.authenticator.data.platform.manager.BitwardenEncodingManager import com.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManager +import com.bitwarden.authenticator.data.platform.manager.imports.model.GoogleAuthenticatorProtos import com.bitwarden.authenticator.data.platform.repository.SettingsRepository import com.bitwarden.authenticator.data.platform.repository.model.DataState import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VerificationCodeDisplayItem @@ -39,6 +42,7 @@ import javax.inject.Inject class ItemListingViewModel @Inject constructor( private val authenticatorRepository: AuthenticatorRepository, private val clipboardManager: BitwardenClipboardManager, + private val encodingManager: BitwardenEncodingManager, settingsRepository: SettingsRepository, ) : BaseViewModel( initialState = ItemListingState( @@ -261,32 +265,120 @@ class ItemListingViewModel @Inject constructor( viewModelScope.launch { when (val totpResult = action.totpResult) { TotpCodeResult.CodeScanningError -> { - sendAction( - action = ItemListingAction.Internal.CreateItemResultReceive( - result = CreateItemResult.Error, - ), - ) + handleCodeScanningErrorReceive() } - is TotpCodeResult.Success -> { + is TotpCodeResult.TotpCodeScan -> { + handleTotpCodeScanReceive(totpResult) + } - val item = totpResult.code.toAuthenticatorEntityOrNull() - ?: run { - sendAction( - action = ItemListingAction.Internal.CreateItemResultReceive( - result = CreateItemResult.Error, - ), - ) - return@launch - } - - val result = authenticatorRepository.createItem(item) - sendAction(ItemListingAction.Internal.CreateItemResultReceive(result)) + is TotpCodeResult.GoogleExportScan -> { + handleGoogleExportScan(totpResult) } } } } + private suspend fun handleTotpCodeScanReceive( + totpResult: TotpCodeResult.TotpCodeScan, + ) { + val item = totpResult.code.toAuthenticatorEntityOrNull() + ?: run { + handleCodeScanningErrorReceive() + return + } + + val result = authenticatorRepository.createItem(item) + sendAction(ItemListingAction.Internal.CreateItemResultReceive(result)) + } + + private suspend fun handleGoogleExportScan( + totpResult: TotpCodeResult.GoogleExportScan, + ) { + val base64EncodedMigrationData = encodingManager.uriDecode( + value = totpResult.data, + ) + + val decodedMigrationData = encodingManager.base64Decode( + value = base64EncodedMigrationData, + ) + + val payload = GoogleAuthenticatorProtos.MigrationPayload + .parseFrom(decodedMigrationData) + + val entries = payload + .otpParametersList + .mapNotNull { otpParam -> + val secret = encodingManager.base32Encode( + byteArray = otpParam.secret.toByteArray() + ) + + // Google Authenticator only supports TOTP and HOTP codes. We do not support HOTP + // codes so we skip over codes that are not TOTP. + val type = when (otpParam.type) { + GoogleAuthenticatorProtos.MigrationPayload.OtpType.OTP_TOTP -> { + AuthenticatorItemType.TOTP + } + + else -> return@mapNotNull null + } + + // Google Authenticator does not always provide a valid digits value so we double + // check it and fallback to the default value if it is not within our valid range. + val digits = if (otpParam.digits in 5..10) { + otpParam.digits + } else { + TotpCodeManager.TOTP_DIGITS_DEFAULT + } + + // Google Authenticator only supports SHA1 algorithms. + val algorithm = AuthenticatorItemAlgorithm.SHA1 + + // Google Authenticator ignores period so we always set it to our default. + val period = TotpCodeManager.PERIOD_SECONDS_DEFAULT + + val accountName: String = when { + otpParam.issuer.isNullOrEmpty().not() && + otpParam.name.startsWith("${otpParam.issuer}:") -> { + otpParam.name.replace("${otpParam.issuer}:", "") + } + + else -> otpParam.name + } + + // If the issuer is not provided fallback to the token name since issuer is required + // in our database + val issuer = when { + otpParam.issuer.isNullOrEmpty() -> otpParam.name + else -> otpParam.issuer + } + + AuthenticatorItemEntity( + id = UUID.randomUUID().toString(), + key = secret, + type = type, + algorithm = algorithm, + period = period, + digits = digits, + issuer = issuer, + accountName = accountName, + userId = null, + favorite = false, + ) + } + + val result = authenticatorRepository.addItems(*entries.toTypedArray()) + sendAction(ItemListingAction.Internal.CreateItemResultReceive(result)) + } + + private suspend fun handleCodeScanningErrorReceive() { + sendAction( + action = ItemListingAction.Internal.CreateItemResultReceive( + result = CreateItemResult.Error, + ), + ) + } + private fun handleAlertThresholdSecondsReceive( action: ItemListingAction.Internal.AlertThresholdSecondsReceive, ) { diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt index 466fc8f53b..365bf73939 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt @@ -43,17 +43,22 @@ class QrCodeScanViewModel @Inject constructor( ) } - // For more information: https://bitwarden.com/help/authenticator-keys/#support-for-more-parameters private fun handleQrCodeScanReceive(action: QrCodeScanAction.QrCodeScanReceive) { - var result: TotpCodeResult = TotpCodeResult.Success(action.qrCode) val scannedCode = action.qrCode - - if (scannedCode.isBlank() || !scannedCode.startsWith(TotpCodeManager.TOTP_CODE_PREFIX)) { + if (scannedCode.startsWith(TotpCodeManager.TOTP_CODE_PREFIX)) { + handleTotpUriReceive(scannedCode) + } else if (scannedCode.startsWith(TotpCodeManager.GOOGLE_EXPORT_PREFIX)) { + handleGoogleExportUriReceive(scannedCode) + } else { authenticatorRepository.emitTotpCodeResult(TotpCodeResult.CodeScanningError) sendEvent(QrCodeScanEvent.NavigateBack) return } + } + // For more information: https://bitwarden.com/help/authenticator-keys/#support-for-more-parameters + private fun handleTotpUriReceive(scannedCode: String) { + var result: TotpCodeResult = TotpCodeResult.TotpCodeScan(scannedCode) val scannedCodeUri = Uri.parse(scannedCode) val secretValue = scannedCodeUri .getQueryParameter(TotpCodeManager.SECRET_PARAM) @@ -70,7 +75,18 @@ class QrCodeScanViewModel @Inject constructor( if (!areParametersValid(scannedCode, values)) { result = TotpCodeResult.CodeScanningError } + authenticatorRepository.emitTotpCodeResult(result) + sendEvent(QrCodeScanEvent.NavigateBack) + } + private fun handleGoogleExportUriReceive(scannedCode: String) { + val uri = Uri.parse(scannedCode) + val encodedData = uri.getQueryParameter(TotpCodeManager.DATA_PARAM) + val result: TotpCodeResult = if (encodedData.isNullOrEmpty()) { + TotpCodeResult.CodeScanningError + } else { + TotpCodeResult.GoogleExportScan(encodedData) + } authenticatorRepository.emitTotpCodeResult(result) sendEvent(QrCodeScanEvent.NavigateBack) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt index b6384fd2dd..fba887d4ff 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt @@ -31,7 +31,7 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer { val source = PlanarYUVLuminanceSource( image.planes[0].buffer.toByteArray(), - image.width, + image.planes[0].rowStride, image.height, 0, 0, @@ -39,24 +39,22 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer { image.height, false, ) + val binaryBitmap = BinaryBitmap(HybridBinarizer(source)) try { - val result = MultiFormatReader() - .apply { - setHints( - mapOf( - DecodeHintType.POSSIBLE_FORMATS to arrayListOf( - BarcodeFormat.QR_CODE, - ), - ), - ) - } - .decode(binaryBitmap) + val result = MultiFormatReader().decode( + /* image = */ binaryBitmap, + /* hints = */ + mapOf( + DecodeHintType.POSSIBLE_FORMATS to arrayListOf(BarcodeFormat.QR_CODE), + DecodeHintType.ALSO_INVERTED to true + ), + ) qrCodeRead = true onQrCodeScanned(result.text) - } catch (e: NotFoundException) { - return + } catch (ignored: NotFoundException) { + } finally { image.close() } diff --git a/app/src/main/proto/google_authenticator.proto b/app/src/main/proto/google_authenticator.proto new file mode 100644 index 0000000000..9e622d028b --- /dev/null +++ b/app/src/main/proto/google_authenticator.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; + +option java_package = "com.bitwarden.authenticator.data.platform.manager.imports.model"; +option java_outer_classname = "GoogleAuthenticatorProtos"; + +message MigrationPayload { + enum Algorithm { + ALGO_INVALID = 0; + ALGO_SHA1 = 1; + } + + enum OtpType { + OTP_INVALID = 0; + OTP_HOTP = 1; + OTP_TOTP = 2; + } + + message OtpParameters { + bytes secret = 1; + string name = 2; + string issuer = 3; + Algorithm algorithm = 4; + int32 digits = 5; + OtpType type = 6; + int64 counter = 7; + } + + repeated OtpParameters otp_parameters = 1; + int32 version = 2; + int32 batch_size = 3; + int32 batch_index = 4; + int32 batch_id = 5; +} diff --git a/app/src/test/java/com/x8bit/bitwarden/android/authenticator/ExampleUnitTest.kt b/app/src/test/java/com/x8bit/bitwarden/android/authenticator/ExampleUnitTest.kt deleted file mode 100644 index 196b5bd274..0000000000 --- a/app/src/test/java/com/x8bit/bitwarden/android/authenticator/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.bitwarden.android.authenticator - -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 4746dfa74d..e9f5de4180 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,12 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { alias(libs.plugins.android.application) apply false + alias(libs.plugins.google.protobuf) apply false + alias(libs.plugins.google.services) apply false alias(libs.plugins.hilt) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.parcelize) apply false alias(libs.plugins.kotlinx.kover) apply false alias(libs.plugins.ksp) apply false - alias(libs.plugins.google.services) apply false alias(libs.plugins.sonarqube) apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3e47821bba..0dcd8d65fd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,9 @@ espresso = "3.5.1" fastlaneScreengrab = "2.1.1" firebaseBom = "33.0.0" glide = "1.0.0-beta01" +googleGuava = "33.0.0-android" +googleProtoBufJava = "3.25.1" +googleProtoBufPlugin = "0.9.4" googleServices = "4.4.1" hilt = "2.51.1" junit5 = "5.10.2" @@ -94,20 +97,24 @@ androidx-test-espresso = { module = "androidx.test.espresso:espresso-core", vers androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidxWork" } bitwarden-sdk = { module = "com.bitwarden:sdk-android", version.ref = "bitwardenSdk" } bumptech-glide = { module = "com.github.bumptech.glide:compose", version.ref = "glide" } -fastlane-screengrab = { module = "tools.fastlane:screengrab", version.ref = "fastlaneScreengrab"} +fastlane-screengrab = { module = "tools.fastlane:screengrab", version.ref = "fastlaneScreengrab" } google-firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } google-firebase-cloud-messaging = { module = "com.google.firebase:firebase-messaging-ktx" } google-firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" } +google-guava = { module = "com.google.guava:guava", version.ref = "googleGuava" } google-hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } google-hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" } google-hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } +google-protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "googleProtoBufJava" } +# Included so that Renovate tracks updates since protoc is not referenced directly in `dependency {}` blocks. +google-protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "googleProtoBufJava" } jakewharton-retrofit-kotlinx-serialization = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinxSerialization" } junit-junit5 = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit5" } junit-vintage = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit5" } kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } -kotlinx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" } mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" } mockk-mockk = { module = "io.mockk:mockk", version.ref = "mockk" } robolectric-robolectric = { module = "org.robolectric:robolectric", version.ref = "roboelectric" } @@ -121,6 +128,7 @@ zxing-zxing-core = { module = "com.google.zxing:core", version.ref = "zxing" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlytics" } +google-protobuf = { id = "com.google.protobuf", version.ref = "googleProtoBufPlugin" } google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }